mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-02 02:58:10 +02:00
feat(ts): add real quality gates — Biome lint + effect-law ratchet + class inventory
- biome.json (2.4.16, linter-only) wired as "lint" in all six packages - scripts/check-effect-laws.ts: Effect-native law enforcement encoding the adapted beep-effect effect-first/schema-first laws (no native JSON/switch/ sort/fetch/timers, no process.env, no throw new, no Effect.run* outside boundaries, no Schema-suffixed constants, no node:fs/path, AST-based pure-data interface detection per law 38/39) - ratcheting baseline allowlist (95 entries / 290 findings) that must shrink to documented exemptions only; stale counts fail the gate - root lint chains turbo lint + law check + native-class inventory - fix all 163 initial Biome findings: import-type style, templates, two `any`s, ten non-null assertions (librarian getService gate, A.matchRight in atoms, ensureNode returning nodes, main.tsx mount guard) Gates: lint, check:tsgo, build, test (force, 11 tasks) all green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
cf12defcd8
commit
0746d7ffd5
109 changed files with 951 additions and 611 deletions
|
|
@ -7,7 +7,8 @@
|
|||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"qa:browser": "playwright test"
|
||||
"qa:browser": "playwright test",
|
||||
"lint": "bunx --bun biome check src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@effect/atom-react": "4.0.0-beta.78",
|
||||
|
|
|
|||
|
|
@ -1,25 +1,29 @@
|
|||
import { Clipboard as BrowserClipboard } from "@effect/platform-browser";
|
||||
import * as BrowserHttpClient from "@effect/platform-browser/BrowserHttpClient";
|
||||
import * as BrowserKeyValueStore from "@effect/platform-browser/BrowserKeyValueStore";
|
||||
import type {
|
||||
GraphRagOptions,
|
||||
BaseApi,
|
||||
BeginUploadResponse,
|
||||
ChunkedUploadDocumentMetadata,
|
||||
CompleteUploadResponse,
|
||||
ConnectionState,
|
||||
DocumentMetadata,
|
||||
ExplainEvent,
|
||||
StreamingMetadata,
|
||||
Term,
|
||||
Triple,
|
||||
UploadChunkResponse,
|
||||
} from "@trustgraph/client";
|
||||
import {
|
||||
DispatchPayload,
|
||||
GatewayWorkbenchHttpApi,
|
||||
type GraphRagOptions,
|
||||
makeBaseApi,
|
||||
TrustGraphRpcs,
|
||||
type BaseApi,
|
||||
type BeginUploadResponse,
|
||||
type ChunkedUploadDocumentMetadata,
|
||||
type CompleteUploadResponse,
|
||||
type ConnectionState,
|
||||
type DocumentMetadata,
|
||||
type ExplainEvent,
|
||||
type StreamingMetadata,
|
||||
type Term,
|
||||
type Triple,
|
||||
type UploadChunkResponse,
|
||||
} from "@trustgraph/client";
|
||||
import { Cause, Clock, Context, Effect, Layer, Match, Metric, Option, Random, Schema as S, Scope, Stream } from "effect";
|
||||
import type { Scope, } from "effect";
|
||||
import { Cause, Clock, Context, Effect, Layer, Match, Metric, Option, Random, Schema as S, Stream } from "effect";
|
||||
import * as A from "effect/Array";
|
||||
import * as MutableHashMap from "effect/MutableHashMap";
|
||||
import * as Predicate from "effect/Predicate";
|
||||
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
||||
|
|
@ -28,7 +32,7 @@ import * as RpcClient from "effect/unstable/rpc/RpcClient";
|
|||
import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization";
|
||||
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
|
||||
import * as Atom from "effect/unstable/reactivity/Atom";
|
||||
import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry";
|
||||
import type * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry";
|
||||
import * as AtomHttpApi from "effect/unstable/reactivity/AtomHttpApi";
|
||||
import * as AtomRpc from "effect/unstable/reactivity/AtomRpc";
|
||||
import * as Reactivity from "effect/unstable/reactivity/Reactivity";
|
||||
|
|
@ -1490,14 +1494,15 @@ function updateConversation(get: Atom.FnContext, f: (current: ConversationState)
|
|||
}
|
||||
|
||||
function updateLastMessage(get: Atom.FnContext, updater: (prev: ChatMessage) => ChatMessage): void {
|
||||
updateConversation(get, (current) => {
|
||||
if (current.messages.length === 0) return current;
|
||||
const last = current.messages[current.messages.length - 1]!;
|
||||
return {
|
||||
...current,
|
||||
messages: [...current.messages.slice(0, -1), updater(last)],
|
||||
};
|
||||
});
|
||||
updateConversation(get, (current) =>
|
||||
A.matchRight(current.messages, {
|
||||
onEmpty: () => current,
|
||||
onNonEmpty: (init, last) => ({
|
||||
...current,
|
||||
messages: [...init, updater(last)],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function refreshConfigAtoms(get: Atom.FnContext): void {
|
||||
|
|
|
|||
|
|
@ -9,11 +9,13 @@ import {
|
|||
resultError,
|
||||
resultLoading,
|
||||
} from "@/atoms/workbench";
|
||||
import type {
|
||||
GraphNode,
|
||||
GraphLink,
|
||||
} from "@/lib/graph-utils";
|
||||
import {
|
||||
triplesToGraph,
|
||||
localName,
|
||||
type GraphNode,
|
||||
type GraphLink,
|
||||
directedGraphLinkProps,
|
||||
DEFAULT_GRAPH_NODE_COLOR,
|
||||
} from "@/lib/graph-utils";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import type { ReactNode } from "react";
|
||||
import type {
|
||||
FallbackProps,
|
||||
} from "react-error-boundary";
|
||||
import {
|
||||
ErrorBoundary as ReactErrorBoundary,
|
||||
type FallbackProps,
|
||||
} from "react-error-boundary";
|
||||
import { AlertTriangle, RefreshCw } from "lucide-react";
|
||||
import { Effect } from "effect";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { useAtomSet, useAtomValue } from "@effect/atom-react";
|
||||
import { X } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { notificationsAtom, removeNotificationAtom, type Notification } from "@/atoms/workbench";
|
||||
import type { Notification } from "@/atoms/workbench";
|
||||
import { notificationsAtom, removeNotificationAtom, } from "@/atoms/workbench";
|
||||
|
||||
const typeStyles: Record<Notification["type"], string> = {
|
||||
success: "border-success/40 bg-success/10 text-success",
|
||||
|
|
|
|||
|
|
@ -134,8 +134,8 @@
|
|||
.animate-\[glow-drift-2_15s_ease-in-out_infinite\],
|
||||
.animate-\[glow-drift-3_25s_ease-in-out_infinite\],
|
||||
.animate-\[glow-fade-in_1\.2s_ease-out_forwards\] {
|
||||
animation: none !important;
|
||||
opacity: 0.7 !important;
|
||||
animation: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -123,16 +123,18 @@ export function triplesToGraph(triples: Triple[]): {
|
|||
const nodeMap = new Map<string, GraphNode>();
|
||||
const links: GraphLink[] = [];
|
||||
|
||||
const ensureNode = (uri: string): void => {
|
||||
if (!nodeMap.has(uri)) {
|
||||
const type = typeMap.get(uri);
|
||||
nodeMap.set(uri, {
|
||||
id: uri,
|
||||
label: labelMap.get(uri) ?? localName(uri),
|
||||
color: hashColor(type !== undefined ? localName(type) : uri),
|
||||
degree: 0,
|
||||
});
|
||||
}
|
||||
const ensureNode = (uri: string): GraphNode => {
|
||||
const existing = nodeMap.get(uri);
|
||||
if (existing !== undefined) return existing;
|
||||
const type = typeMap.get(uri);
|
||||
const node: GraphNode = {
|
||||
id: uri,
|
||||
label: labelMap.get(uri) ?? localName(uri),
|
||||
color: hashColor(type !== undefined ? localName(type) : uri),
|
||||
degree: 0,
|
||||
};
|
||||
nodeMap.set(uri, node);
|
||||
return node;
|
||||
};
|
||||
|
||||
for (const t of triples) {
|
||||
|
|
@ -151,10 +153,8 @@ export function triplesToGraph(triples: Triple[]): {
|
|||
const oIsEntity = isIri(t.o) || t.o.t === "l";
|
||||
if (!sIsEntity || !oIsEntity) continue;
|
||||
|
||||
ensureNode(sVal);
|
||||
ensureNode(oVal);
|
||||
nodeMap.get(sVal)!.degree++;
|
||||
nodeMap.get(oVal)!.degree++;
|
||||
ensureNode(sVal).degree++;
|
||||
ensureNode(oVal).degree++;
|
||||
|
||||
links.push({
|
||||
source: sVal,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { type ClassValue, clsx } from "clsx";
|
||||
import type { ClassValue, } from "clsx";
|
||||
import { clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,13 @@ function AppRoot() {
|
|||
return <App />;
|
||||
}
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
const rootElement = document.getElementById("root");
|
||||
if (rootElement === null) {
|
||||
// Host boundary: the workbench cannot render without its mount point.
|
||||
throw new Error("Workbench root element #root not found");
|
||||
}
|
||||
|
||||
ReactDOM.createRoot(rootElement).render(
|
||||
<React.StrictMode>
|
||||
<RegistryProvider defaultIdleTTL={1_000} initialValues={getWorkbenchQaInitialValues()}>
|
||||
<AppRoot />
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ import {
|
|||
} from "lucide-react";
|
||||
import Markdown from "react-markdown";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type {
|
||||
ChatMessage,
|
||||
} from "@/atoms/workbench";
|
||||
import {
|
||||
agentPhaseExpandedAtom,
|
||||
cancelChatAtom,
|
||||
|
|
@ -27,7 +30,6 @@ import {
|
|||
setConversationInputAtom,
|
||||
settingsAtom,
|
||||
submitMessageAtom,
|
||||
type ChatMessage,
|
||||
} from "@/atoms/workbench";
|
||||
import { AutoTextarea } from "@/components/ui/textarea";
|
||||
import { MessageActions } from "@/components/chat/message-actions";
|
||||
|
|
|
|||
|
|
@ -19,14 +19,16 @@ import {
|
|||
settingsAtom,
|
||||
} from "@/atoms/workbench";
|
||||
import type { Triple } from "@trustgraph/client";
|
||||
import type {
|
||||
GraphNode,
|
||||
GraphLink,
|
||||
} from "@/lib/graph-utils";
|
||||
import {
|
||||
localName,
|
||||
triplesToGraph,
|
||||
RDFS_LABEL,
|
||||
RDF_TYPE,
|
||||
termValue,
|
||||
type GraphNode,
|
||||
type GraphLink,
|
||||
directedGraphLinkProps,
|
||||
DEFAULT_GRAPH_NODE_COLOR,
|
||||
} from "@/lib/graph-utils";
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ import {
|
|||
Hash,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type {
|
||||
UploadForm,
|
||||
} from "@/atoms/workbench";
|
||||
import {
|
||||
documentMetadataAtom,
|
||||
encodeJsonUnknownString,
|
||||
|
|
@ -31,7 +34,6 @@ import {
|
|||
submitUploadDocumentAtom,
|
||||
uploadDialogOpenAtom,
|
||||
uploadFormAtom,
|
||||
type UploadForm,
|
||||
} from "@/atoms/workbench";
|
||||
import { Dialog } from "@/components/ui/dialog";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ import {
|
|||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Dialog } from "@/components/ui/dialog";
|
||||
import type {
|
||||
McpServerEntry,
|
||||
ToolEntry,
|
||||
} from "@/atoms/workbench";
|
||||
import {
|
||||
deleteMcpServerAtom,
|
||||
deleteMcpToolAtom,
|
||||
|
|
@ -31,8 +35,6 @@ import {
|
|||
resultLoading,
|
||||
saveMcpServerAtom,
|
||||
saveMcpToolAtom,
|
||||
type McpServerEntry,
|
||||
type ToolEntry,
|
||||
} from "@/atoms/workbench";
|
||||
|
||||
const INPUT_CLASS =
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ export default function PromptsPage() {
|
|||
)}
|
||||
|
||||
{activeTab === "templates" && (
|
||||
<div id="panel-templates" role="tabpanel" aria-labelledby="tab-templates" tabIndex={0} className="flex flex-1 flex-col gap-4 overflow-hidden">
|
||||
<div id="panel-templates" role="tabpanel" aria-labelledby="tab-templates" className="flex flex-1 flex-col gap-4 overflow-hidden">
|
||||
{loading && prompts.length === 0 && (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className="mr-2 h-5 w-5 animate-spin text-fg-subtle" />
|
||||
|
|
@ -203,7 +203,7 @@ export default function PromptsPage() {
|
|||
)}
|
||||
|
||||
{activeTab === "system" && (
|
||||
<div id="panel-system" role="tabpanel" aria-labelledby="tab-system" tabIndex={0} className="flex flex-1 flex-col overflow-hidden rounded-lg border border-border">
|
||||
<div id="panel-system" role="tabpanel" aria-labelledby="tab-system" className="flex flex-1 flex-col overflow-hidden rounded-lg border border-border">
|
||||
<div className="border-b border-border bg-surface-100 px-4 py-3">
|
||||
<h2 className="text-xs font-medium uppercase tracking-wider text-fg-muted">
|
||||
System Prompt
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
import type * as Atom from "effect/unstable/reactivity/Atom";
|
||||
import type {
|
||||
FeatureSwitches,
|
||||
Settings,
|
||||
WorkbenchApiFactory,
|
||||
} from "@/atoms/workbench";
|
||||
import {
|
||||
apiFactoryAtom,
|
||||
DEFAULT_SETTINGS,
|
||||
flowIdAtom,
|
||||
settingsAtom,
|
||||
type FeatureSwitches,
|
||||
type Settings,
|
||||
type WorkbenchApiFactory,
|
||||
} from "@/atoms/workbench";
|
||||
import type { BaseApi } from "@trustgraph/client";
|
||||
import { makeMockBaseApi, qaSettingsFromFixture, type MockWorkbenchFixture } from "@/qa/mock-api";
|
||||
import type { MockWorkbenchFixture } from "@/qa/mock-api";
|
||||
import { makeMockBaseApi, qaSettingsFromFixture, } from "@/qa/mock-api";
|
||||
|
||||
export interface WorkbenchQaWindowConfig {
|
||||
readonly enabled?: boolean;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { makeBaseApiWithRpc, type BaseApi, type DocumentMetadata, type ProcessingMetadata, type StreamingMetadata, type Triple } from "@trustgraph/client";
|
||||
import type { BaseApi, DocumentMetadata, ProcessingMetadata, StreamingMetadata, Triple } from "@trustgraph/client";
|
||||
import { makeBaseApiWithRpc, } from "@trustgraph/client";
|
||||
import { Clock, Effect, Match, Option, Schema as S } from "effect";
|
||||
|
||||
type ConfigValues = Record<string, Record<string, unknown>>;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue