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": {
"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",
"format": "biome format --write .",
"test": "bun run --filter '*' test",
@@ -100,7 +100,7 @@ export function startGatewayWsClient(params: GatewayWsClientParams): () => void
clearReconnectTimer();
const delayMs = Math.min(INITIAL_BACKOFF_MS * 2 ** attempt, MAX_BACKOFF_MS);
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);
};
@@ -143,7 +143,7 @@ export function startGatewayWsClient(params: GatewayWsClientParams): () => void
ws.addEventListener("message", (ev) => {
const data = ev.data;
if (typeof data !== "string") {
params.log("T9W2KL5H", "gateway WebSocket non-text frame ignored");
params.log("T9W2K35H", "gateway WebSocket non-text frame ignored");
return;
}
void handleGatewayMessage(ws, data, params).catch((e: unknown) => {
@@ -1,9 +1,4 @@
import {
BaseEdge,
EdgeLabelRenderer,
type EdgeProps,
getSmoothStepPath,
} from "@xyflow/react";
import { BaseEdge, EdgeLabelRenderer, type EdgeProps, getSmoothStepPath } from "@xyflow/react";
import type { ConditionEdgeData } from "./types.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.
* The path goes: source right → arc → vertical up → arc → target right
*/
function feedbackPath(
sourceX: number,
sourceY: number,
targetX: number,
targetY: number,
): string {
function feedbackPath(sourceX: number, sourceY: number, targetX: number, targetY: number): string {
const rightX = Math.max(sourceX, targetX) + FEEDBACK_OFFSET_X;
const r = FEEDBACK_RADIUS;
@@ -42,6 +32,7 @@ function feedbackPath(
return segments.join(" ");
}
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: edge routing logic is inherently branchy
export function ConditionEdge(props: EdgeProps) {
const {
id,
@@ -1,7 +1,7 @@
import type { Edge, Node } from "@xyflow/react";
import { useMemo } from "react";
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 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.
* 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[] {
// Collect all node IDs
const ids = new Set<string>();
@@ -213,8 +214,8 @@ function computeLayout(input: LayoutInput): LayoutResult {
isFallback,
isFeedback,
isSelfLoop,
labelX,
labelY,
labelX,
labelY,
},
};
});
@@ -223,8 +224,5 @@ function computeLayout(input: LayoutInput): LayoutResult {
}
export function useLayout(input: LayoutInput): LayoutResult {
return useMemo(
() => computeLayout(input),
[input.edges, input.roles, input.nodeStates],
);
return useMemo(() => computeLayout(input), [input]);
}
+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