chore: add log-tag lint + fix biome errors + pre-push hook

- scripts/lint-log-tags.sh: static check for invalid Crockford Base32 log tags (I/L/O/U)
- fix two invalid log tags in ws-client.ts (6CJX2RLP→6CJX2R8P, T9W2KL5H→T9W2K35H)
- fix biome errors: unused import, exhaustive deps, cognitive complexity suppression
- add pre-push git hook running bun run check
- integrate lint-log-tags into bun run check pipeline

Refs #244
This commit is contained in:
2026-05-13 14:59:20 +00:00
parent 82e40f0c21
commit 76830c5e22
6 changed files with 41 additions and 22 deletions
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
# pre-push hook: typecheck + biome + lint-log-tags
set -euo pipefail
echo "🔍 pre-push: running checks..."
bun run check
echo "✅ pre-push: all checks passed"
+1 -1
View File
@@ -6,7 +6,7 @@
], ],
"scripts": { "scripts": {
"build": "bunx tsc --build", "build": "bunx tsc --build",
"check": "bunx tsc --build && biome check .", "check": "bunx tsc --build && biome check . && bash scripts/lint-log-tags.sh",
"typecheck": "bunx tsc --build", "typecheck": "bunx tsc --build",
"format": "biome format --write .", "format": "biome format --write .",
"test": "bun run --filter '*' test", "test": "bun run --filter '*' test",
@@ -100,7 +100,7 @@ export function startGatewayWsClient(params: GatewayWsClientParams): () => void
clearReconnectTimer(); clearReconnectTimer();
const delayMs = Math.min(INITIAL_BACKOFF_MS * 2 ** attempt, MAX_BACKOFF_MS); const delayMs = Math.min(INITIAL_BACKOFF_MS * 2 ** attempt, MAX_BACKOFF_MS);
attempt++; attempt++;
params.log("6CJX2RLP", `gateway WebSocket reconnect in ${delayMs}ms (attempt ${attempt})`); params.log("6CJX2R8P", `gateway WebSocket reconnect in ${delayMs}ms (attempt ${attempt})`);
reconnectTimer = setTimeout(connect, delayMs); reconnectTimer = setTimeout(connect, delayMs);
}; };
@@ -143,7 +143,7 @@ export function startGatewayWsClient(params: GatewayWsClientParams): () => void
ws.addEventListener("message", (ev) => { ws.addEventListener("message", (ev) => {
const data = ev.data; const data = ev.data;
if (typeof data !== "string") { if (typeof data !== "string") {
params.log("T9W2KL5H", "gateway WebSocket non-text frame ignored"); params.log("T9W2K35H", "gateway WebSocket non-text frame ignored");
return; return;
} }
void handleGatewayMessage(ws, data, params).catch((e: unknown) => { void handleGatewayMessage(ws, data, params).catch((e: unknown) => {
@@ -1,9 +1,4 @@
import { import { BaseEdge, EdgeLabelRenderer, type EdgeProps, getSmoothStepPath } from "@xyflow/react";
BaseEdge,
EdgeLabelRenderer,
type EdgeProps,
getSmoothStepPath,
} from "@xyflow/react";
import type { ConditionEdgeData } from "./types.ts"; import type { ConditionEdgeData } from "./types.ts";
// Must match the FEEDBACK_OFFSET_X in use-layout.ts // Must match the FEEDBACK_OFFSET_X in use-layout.ts
@@ -15,12 +10,7 @@ const FEEDBACK_RADIUS = 16;
* Build an SVG path for a feedback (back) edge that routes to the right of the nodes. * Build an SVG path for a feedback (back) edge that routes to the right of the nodes.
* The path goes: source right → arc → vertical up → arc → target right * The path goes: source right → arc → vertical up → arc → target right
*/ */
function feedbackPath( function feedbackPath(sourceX: number, sourceY: number, targetX: number, targetY: number): string {
sourceX: number,
sourceY: number,
targetX: number,
targetY: number,
): string {
const rightX = Math.max(sourceX, targetX) + FEEDBACK_OFFSET_X; const rightX = Math.max(sourceX, targetX) + FEEDBACK_OFFSET_X;
const r = FEEDBACK_RADIUS; const r = FEEDBACK_RADIUS;
@@ -42,6 +32,7 @@ function feedbackPath(
return segments.join(" "); return segments.join(" ");
} }
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: edge routing logic is inherently branchy
export function ConditionEdge(props: EdgeProps) { export function ConditionEdge(props: EdgeProps) {
const { const {
id, id,
@@ -1,7 +1,7 @@
import type { Edge, Node } from "@xyflow/react"; import type { Edge, Node } from "@xyflow/react";
import { useMemo } from "react"; import { useMemo } from "react";
import type { WorkflowGraphEdge } from "../../api.ts"; import type { WorkflowGraphEdge } from "../../api.ts";
import type { ConditionEdgeData, NodeState, RoleNodeData, TerminalNodeData } from "./types.ts"; import type { NodeState, RoleNodeData, TerminalNodeData } from "./types.ts";
const START_ID = "__start__"; const START_ID = "__start__";
const END_ID = "__end__"; const END_ID = "__end__";
@@ -41,6 +41,7 @@ function edgeKey(e: WorkflowGraphEdge): string {
* Forward edges go from lower rank to higher rank; feedback edges go backwards. * Forward edges go from lower rank to higher rank; feedback edges go backwards.
* Self-loops are neither forward nor feedback — they're handled separately. * Self-loops are neither forward nor feedback — they're handled separately.
*/ */
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: topological sort is inherently branchy
function extractSpine(edges: readonly WorkflowGraphEdge[]): string[] { function extractSpine(edges: readonly WorkflowGraphEdge[]): string[] {
// Collect all node IDs // Collect all node IDs
const ids = new Set<string>(); const ids = new Set<string>();
@@ -213,8 +214,8 @@ function computeLayout(input: LayoutInput): LayoutResult {
isFallback, isFallback,
isFeedback, isFeedback,
isSelfLoop, isSelfLoop,
labelX, labelX,
labelY, labelY,
}, },
}; };
}); });
@@ -223,8 +224,5 @@ function computeLayout(input: LayoutInput): LayoutResult {
} }
export function useLayout(input: LayoutInput): LayoutResult { export function useLayout(input: LayoutInput): LayoutResult {
return useMemo( return useMemo(() => computeLayout(input), [input]);
() => computeLayout(input),
[input.edges, input.roles, input.nodeStates],
);
} }
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Validate Crockford Base32 log tags in .log("TAG", ...) calls.
# Crockford Base32 excludes: I, L, O, U
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
BAD=0
while IFS= read -r match; do
file="${match%%:*}"
rest="${match#*:}"
line="${rest%%:*}"
tag=$(echo "$rest" | grep -oP '\.log\(\s*"\K[A-Za-z0-9]+')
if echo "$tag" | grep -qiE '[ILOU]'; then
echo "${file}:${line} tag \"${tag}\" contains invalid Crockford Base32 char (I/L/O/U)"
BAD=1
fi
done < <(grep -rn '\.log("[A-Za-z0-9]\{8\}"' "$ROOT/packages/" --include='*.ts' \
| grep -v node_modules | grep -v '/dist/')
if [ "$BAD" -eq 0 ]; then
echo " ✅ All log tags are valid Crockford Base32"
fi
exit $BAD