🧠 COMMIT INTELLIGENCE
📖 LEARNING LAYER
📚 PROMPT LIBRARY
+ PROVIDER HEALTH
Selected Project
@@ -125,6 +134,7 @@ Commit History & Linkage
Predictive Engineering Lessons
+
@@ -135,10 +145,29 @@ Predictive Engineering Lessons
Autonomous Prompt Templates
+
+
+
+
+
+
+
+
Semantic Provider Configuration
+
+
+
+
+
@@ -148,6 +177,7 @@ Autonomous Prompt Templates
let currentView = 'stream';
const escapeHtml = (v) => String(v ?? '').replace(/&/g, '&').replace(//g, '>');
+ const encodeCandidateId = (v) => encodeURIComponent(String(v)).replace(/'/g, '%27');
const projectName = (p) => p.split(/[\\\/]/).filter(Boolean).pop() || 'ROOT';
function switchView(viewId) {
@@ -156,7 +186,7 @@ Autonomous Prompt Templates
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
event.target.classList.add('active');
- const titles = { 'stream': 'Global Intelligence Stream', 'intelligence': 'Commit Intelligence', 'learning': 'Learning Layer', 'library': 'Prompt Library' };
+ const titles = { 'stream': 'Global Intelligence Stream', 'intelligence': 'Commit Intelligence', 'learning': 'Learning Layer', 'library': 'Prompt Library', 'health': 'Provider Health' };
document.getElementById('view-title').textContent = titles[viewId];
currentView = viewId;
refreshData();
@@ -224,10 +254,11 @@ Autonomous Prompt Templates
${escapeHtml(l.title)}
- ${l.confidence.toUpperCase()} CONFIDENCE
+ ${escapeHtml(l.confidence).toUpperCase()} CONFIDENCE
${escapeHtml(l.summary)}
-
TYPE: ${l.lesson_type.toUpperCase()} • SOURCE: ${l.source}
+
TYPE: ${escapeHtml(l.lesson_type).toUpperCase()} • SOURCE: ${escapeHtml(l.source)} • REVIEW: ${l.approved === 1 ? 'APPROVED' : l.approved === -1 ? 'REJECTED' : 'PENDING'}
+ ${l.approved === 0 ? `
` : ''}
`).join('') || 'No lessons extracted yet. Run derive_lessons!
';
}
@@ -237,13 +268,64 @@ Autonomous Prompt Templates
const data = await res.json();
document.getElementById('template-list').innerHTML = data.map(t => `
-
${escapeHtml(t.name)}
-
${escapeHtml(t.description)}
+
${escapeHtml(t.title)}
+
${escapeHtml(t.usage_notes)}
${escapeHtml(t.template_text)}
-
SUCCESS SCORE: ${t.success_score}%
+
SUCCESS SCORE: ${t.success_score}% • REVIEW: ${t.approved === 1 ? 'APPROVED' : t.deprecated === 1 ? 'REJECTED' : 'PENDING'}
+ ${t.approved === 0 && t.deprecated === 0 ? `
` : ''}
`).join('') || 'Prompt library is currently empty.
';
}
+
+ if (currentView === 'health') {
+ const res = await fetch(`/api/health?project=${encodeURIComponent(currentProject)}`);
+ const health = await res.json();
+ if (!res.ok) throw new Error(health.error || 'Provider health unavailable');
+ const semantic = health.semantic;
+ document.getElementById('runtime-health').innerHTML = `
+ STATUS${escapeHtml(health.runtime.status).toUpperCase()}
+ UPTIME${health.runtime.uptimeSeconds}s
+ NODE${escapeHtml(health.runtime.nodeVersion)}
+ CHECKED${new Date(health.runtime.checkedAt).toLocaleString()}
+ `;
+ document.getElementById('semantic-health').innerHTML = `
+ STATUS${escapeHtml(semantic.status).toUpperCase()}
+ LOCAL PROVIDER${semantic.local.enabled ? 'ENABLED' : 'DISABLED'}
+ ENDPOINT${escapeHtml(semantic.local.endpoint)}
+ MODELS${semantic.local.models.map(escapeHtml).join(', ')}
+ MCP SAMPLING${semantic.mcpSampling.enabled ? 'ENABLED' : 'DISABLED'}
+ COMPLETIONS${semantic.totals.completed}
+ AVG LATENCY${semantic.totals.averageLatencyMs === null ? 'N/A' : semantic.totals.averageLatencyMs + 'ms'}
+ FALLBACK COMPLETIONS${semantic.totals.fallbackCompletions}
+ `;
+ document.getElementById('provider-metrics').innerHTML = semantic.providers.map(p => `
+
+
${escapeHtml(p.provider)}
+
COMPLETIONS${p.completions}
+
AVG LATENCY${p.averageLatencyMs === null ? 'N/A' : p.averageLatencyMs + 'ms'}
+
MODELS${p.models.map(escapeHtml).join(', ')}
+
LAST SUCCESS${new Date(p.lastSuccessAt).toLocaleString()}
+
+ `).join('') || 'No semantic completion telemetry recorded for this project.
';
+ }
+ }
+
+ async function reviewCandidate(kind, encodedId, decision) {
+ const notice = document.getElementById(currentView === 'learning' ? 'review-notice-learning' : 'review-notice-library');
+ notice.textContent = `${decision === 'approve' ? 'Approving' : 'Rejecting'} ${kind}...`;
+ try {
+ const response = await fetch(`/api/review/${kind}/${encodedId}?project=${encodeURIComponent(currentProject)}`, {
+ method: 'POST',
+ headers: { 'content-type': 'application/json' },
+ body: JSON.stringify({ decision })
+ });
+ const result = await response.json();
+ if (!response.ok) throw new Error(result.error || 'Review failed');
+ notice.textContent = `${kind === 'lesson' ? 'Lesson' : 'Template'} ${decision === 'approve' ? 'approved' : 'rejected'}.`;
+ await refreshData();
+ } catch (error) {
+ notice.textContent = error instanceof Error ? error.message : String(error);
+ }
}
// Initial Load
diff --git a/universal-refiner/src/core/dashboard.ts b/universal-refiner/src/core/dashboard.ts
index 2f647f4..1bd3763 100644
--- a/universal-refiner/src/core/dashboard.ts
+++ b/universal-refiner/src/core/dashboard.ts
@@ -8,9 +8,11 @@ import { fileURLToPath } from "url";
import { streamSSE } from "hono/streaming";
import { getDisplayVersion } from "./version.js";
import { RuntimeLogger } from "./logger.js";
+import { ConfigManager } from "./config.js";
import { TimelineProvider } from "../history/timeline.js";
import { EventStore } from "../history/event-store.js";
+import { AutoPilotStatus } from "./autopilot-status.js";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -30,8 +32,47 @@ interface DashboardState {
pattern: string;
}
+interface SemanticEventDetails {
+ taskName?: unknown;
+ provider?: unknown;
+ model?: unknown;
+ latencyMs?: unknown;
+ fallbackFrom?: unknown;
+}
+
+interface SemanticEventRow {
+ timestamp: string;
+ details_json: string;
+}
+
+function safeString(value: unknown, maxLength = 120): string | null {
+ return typeof value === "string" && value.length > 0 ? value.slice(0, maxLength) : null;
+}
+
+function sanitizeEndpoint(rawUrl: string): string {
+ try {
+ const url = new URL(rawUrl);
+ return `${url.protocol}//${url.hostname}${url.port ? `:${url.port}` : ""}`;
+ } catch {
+ return "invalid";
+ }
+}
+
+export function isSameOriginRequest(origin: string | undefined, requestUrl: string): boolean {
+ if (!origin) {
+ return true;
+ }
+
+ try {
+ return new URL(origin).origin === new URL(requestUrl).origin;
+ } catch {
+ return false;
+ }
+}
+
export class CommandCenterDashboard {
private static rootPath: string = ".";
+ private static server: { close: (callback?: (error?: Error) => void) => void } | null = null;
static async setLastRefinement(original: string, refined: string, projectPath: string = ".", gain: number = 0) {
await AgenticBlackboard.setLastRefinement(original, refined, projectPath, gain);
@@ -88,7 +129,102 @@ export class CommandCenterDashboard {
};
}
- static start(port = 3000, defaultPath = ".") {
+ private static buildHealth(selectedPath: string) {
+ const repoId = EventStore.getInstance().ensureRepository(selectedPath).id;
+ const config = ConfigManager.getSemanticConfig(selectedPath);
+ const db = (EventStore.getInstance() as any).db;
+ const rows = db.prepare(`
+ SELECT timestamp, details_json
+ FROM events
+ WHERE repo_id = ? AND event_type = 'semantic_request_completed'
+ ORDER BY timestamp DESC
+ LIMIT 100
+ `).all(repoId) as SemanticEventRow[];
+
+ const events = rows.map(row => {
+ let details: SemanticEventDetails = {};
+ try {
+ details = JSON.parse(row.details_json) as SemanticEventDetails;
+ } catch {
+ // Malformed historical telemetry is ignored rather than exposed.
+ }
+ return {
+ timestamp: row.timestamp,
+ taskName: safeString(details.taskName),
+ provider: safeString(details.provider) || "unknown",
+ model: safeString(details.model) || "unknown",
+ latencyMs: typeof details.latencyMs === "number" && Number.isFinite(details.latencyMs)
+ ? Math.max(0, Math.round(details.latencyMs))
+ : null,
+ fallbackFrom: Array.isArray(details.fallbackFrom)
+ ? details.fallbackFrom.map(item => safeString(item, 80)).filter((item): item is string => item !== null).slice(0, 10)
+ : [],
+ };
+ });
+
+ const providerMetrics = new Map;
+ }>();
+ for (const event of events) {
+ const metric = providerMetrics.get(event.provider) || {
+ completions: 0,
+ latencyTotal: 0,
+ latencyCount: 0,
+ lastSuccessAt: event.timestamp,
+ models: new Set(),
+ };
+ metric.completions += 1;
+ if (event.latencyMs !== null) {
+ metric.latencyTotal += event.latencyMs;
+ metric.latencyCount += 1;
+ }
+ metric.models.add(event.model);
+ providerMetrics.set(event.provider, metric);
+ }
+
+ const totalLatency = events.reduce((sum, event) => sum + (event.latencyMs || 0), 0);
+ const latencyCount = events.filter(event => event.latencyMs !== null).length;
+ const semanticEnabled = config.localEnabled || config.mcpSamplingEnabled;
+
+ return {
+ runtime: {
+ status: "online",
+ uptimeSeconds: Math.floor(process.uptime()),
+ nodeVersion: process.version,
+ checkedAt: new Date().toISOString(),
+ },
+ semantic: {
+ status: !semanticEnabled ? "disabled" : events.length > 0 ? "healthy" : "configured",
+ local: {
+ enabled: config.localEnabled,
+ endpoint: sanitizeEndpoint(config.baseUrl),
+ models: config.models,
+ timeoutMs: config.timeoutMs,
+ allowNonLoopback: config.allowNonLoopback,
+ },
+ mcpSampling: { enabled: config.mcpSamplingEnabled },
+ totals: {
+ completed: events.length,
+ averageLatencyMs: latencyCount > 0 ? Math.round(totalLatency / latencyCount) : null,
+ fallbackCompletions: events.filter(event => event.fallbackFrom.length > 0).length,
+ },
+ lastSuccess: events[0] || null,
+ providers: [...providerMetrics.entries()].map(([provider, metric]) => ({
+ provider,
+ completions: metric.completions,
+ averageLatencyMs: metric.latencyCount > 0 ? Math.round(metric.latencyTotal / metric.latencyCount) : null,
+ lastSuccessAt: metric.lastSuccessAt,
+ models: [...metric.models],
+ })),
+ },
+ };
+ }
+
+ static createApp(defaultPath = ".") {
this.rootPath = defaultPath;
const app = new Hono();
@@ -116,8 +252,9 @@ export class CommandCenterDashboard {
app.get("/api/commits", async (c) => {
try {
- const repoId = path.basename(this.resolveSelectedPath(c.req.query("project")));
- const db = (EventStore.getInstance() as any).db;
+ const store = EventStore.getInstance();
+ const repoId = store.ensureRepository(this.resolveSelectedPath(c.req.query("project"))).id;
+ const db = (store as any).db;
const commits = db.prepare(`
SELECT c.*, e.prompt_id, e.id as execution_id
FROM commits c
@@ -135,8 +272,9 @@ export class CommandCenterDashboard {
app.get("/api/lessons", async (c) => {
try {
- const repoId = path.basename(this.resolveSelectedPath(c.req.query("project")));
- const db = (EventStore.getInstance() as any).db;
+ const store = EventStore.getInstance();
+ const repoId = store.ensureRepository(this.resolveSelectedPath(c.req.query("project"))).id;
+ const db = (store as any).db;
const lessons = db.prepare("SELECT * FROM lessons WHERE repo_id = ? ORDER BY created_at DESC").all(repoId);
return c.json(lessons);
} catch (error) {
@@ -147,8 +285,9 @@ export class CommandCenterDashboard {
app.get("/api/templates", async (c) => {
try {
- const repoId = path.basename(this.resolveSelectedPath(c.req.query("project")));
- const db = (EventStore.getInstance() as any).db;
+ const store = EventStore.getInstance();
+ const repoId = store.ensureRepository(this.resolveSelectedPath(c.req.query("project"))).id;
+ const db = (store as any).db;
const templates = db.prepare("SELECT * FROM prompt_templates WHERE repo_id = ? ORDER BY success_score DESC").all(repoId);
return c.json(templates);
} catch (error) {
@@ -157,6 +296,79 @@ export class CommandCenterDashboard {
}
});
+ app.post("/api/review/:kind/:id", async (c) => {
+ const selectedPath = this.resolveSelectedPath(c.req.query("project"));
+ try {
+ if (!isSameOriginRequest(c.req.header("origin"), c.req.url)) {
+ return c.json({ error: "Cross-origin review requests are not allowed" }, 403);
+ }
+ if (!c.req.header("content-type")?.toLowerCase().startsWith("application/json")) {
+ return c.json({ error: "Review requests must use application/json" }, 415);
+ }
+
+ const kind = c.req.param("kind");
+ if (kind !== "lesson" && kind !== "template") {
+ return c.json({ error: "Unsupported review candidate type" }, 400);
+ }
+
+ let body: { decision?: unknown };
+ try {
+ body = await c.req.json() as { decision?: unknown };
+ } catch {
+ return c.json({ error: "Review request body must be valid JSON" }, 400);
+ }
+ if (body.decision !== "approve" && body.decision !== "reject") {
+ return c.json({ error: "Decision must be approve or reject" }, 400);
+ }
+
+ const id = c.req.param("id");
+ if (!id || id.length > 200) {
+ return c.json({ error: "Review candidate ID is invalid" }, 400);
+ }
+ const store = EventStore.getInstance();
+ const repoId = store.ensureRepository(selectedPath).id;
+ const approved = body.decision === "approve";
+ const changed = kind === "lesson"
+ ? store.reviewLesson(repoId, id, approved)
+ : store.reviewTemplate(repoId, id, approved);
+ if (!changed) {
+ return c.json({ error: `Pending ${kind} not found for selected repository` }, 404);
+ }
+
+ store.recordEvent({
+ id: `evt_dashboard_review_${Date.now()}_${Math.floor(Math.random() * 100000)}`,
+ event_type: `${kind}_reviewed`,
+ repo_id: repoId,
+ summary: `${kind === "lesson" ? "Lesson" : "Template"} ${approved ? "approved" : "rejected"} from dashboard`,
+ details_json: JSON.stringify({ candidateId: id, decision: body.decision }),
+ });
+ this.log(`${kind === "lesson" ? "Lesson" : "Template"} ${approved ? "approved" : "rejected"}: ${id}`, selectedPath);
+ return c.json({ id, kind, decision: body.decision, repoId });
+ } catch (error) {
+ this.logRouteError("api/review", error, selectedPath);
+ return c.json({ error: "Review request failed" }, 500);
+ }
+ });
+
+ app.get("/api/autopilot", (c) => {
+ try {
+ return c.json(AutoPilotStatus.getSnapshot());
+ } catch (error) {
+ this.logRouteError("api/autopilot", error);
+ return c.json({ error: "Auto-pilot status unavailable" }, 500);
+ }
+ });
+
+ app.get("/api/health", async (c) => {
+ const selectedPath = this.resolveSelectedPath(c.req.query("project"));
+ try {
+ return c.json(this.buildHealth(selectedPath));
+ } catch (error) {
+ this.logRouteError("api/health", error, selectedPath);
+ return c.json({ error: "Provider health unavailable" }, 500);
+ }
+ });
+
app.get("/api/events", async (c) => {
try {
return streamSSE(c, async (stream) => {
@@ -224,12 +436,18 @@ export class CommandCenterDashboard {
return c.html(html);
} catch (error) {
this.logRouteError("/", error, selectedPath);
- return c.html(`Dashboard Error
The dashboard failed to render.
${String(error instanceof Error ? error.stack || error.message : error)}`, 500);
+ return c.html(`Dashboard Error
The dashboard failed to render. See sanitized runtime logs.
`, 500);
}
});
+ return app;
+ }
+
+ static start(port = 3000, defaultPath = ".") {
+ const app = this.createApp(defaultPath);
try {
const server = serve({ fetch: app.fetch, port, hostname: resolveDashboardHost() });
+ this.server = server;
server.on("error", (e: any) => {
if (e.code === "EADDRINUSE") {
console.error(`[Command Center] Port ${port} taken.`);
@@ -243,4 +461,15 @@ export class CommandCenterDashboard {
throw error;
}
}
+
+ static async stop(): Promise {
+ const server = this.server;
+ this.server = null;
+ if (!server) {
+ return;
+ }
+ await new Promise((resolve, reject) => {
+ server.close((error?: Error) => error ? reject(error) : resolve());
+ });
+ }
}
diff --git a/universal-refiner/src/core/job-queue.ts b/universal-refiner/src/core/job-queue.ts
new file mode 100644
index 0000000..7a15276
--- /dev/null
+++ b/universal-refiner/src/core/job-queue.ts
@@ -0,0 +1,49 @@
+import { RuntimeLogger } from "./logger.js";
+
+export interface QueueJobOptions {
+ retries?: number;
+ retryDelayMs?: number;
+}
+
+export class SerializedJobQueue {
+ private tail: Promise = Promise.resolve();
+ private pendingKeys = new Set();
+
+ enqueue(key: string, job: () => Promise, options: QueueJobOptions = {}): boolean {
+ if (this.pendingKeys.has(key)) {
+ return false;
+ }
+
+ this.pendingKeys.add(key);
+ this.tail = this.tail
+ .then(() => this.runWithRetry(key, job, options))
+ .catch(error => RuntimeLogger.error(`Queued job failed permanently: ${key}`, error))
+ .finally(() => this.pendingKeys.delete(key));
+ return true;
+ }
+
+ async idle(): Promise {
+ await this.tail;
+ }
+
+ private async runWithRetry(key: string, job: () => Promise, options: QueueJobOptions): Promise {
+ const retries = options.retries ?? 2;
+ const retryDelayMs = options.retryDelayMs ?? 500;
+
+ for (let attempt = 0; attempt <= retries; attempt++) {
+ try {
+ await job();
+ return;
+ } catch (error) {
+ if (attempt === retries) {
+ throw error;
+ }
+ RuntimeLogger.warn(`Queued job retry: ${key}`, {
+ attempt: attempt + 1,
+ error: error instanceof Error ? error.message : String(error),
+ });
+ await new Promise(resolve => setTimeout(resolve, retryDelayMs * (attempt + 1)));
+ }
+ }
+ }
+}
diff --git a/universal-refiner/src/core/logger.ts b/universal-refiner/src/core/logger.ts
index ed20076..8b0a2e6 100644
--- a/universal-refiner/src/core/logger.ts
+++ b/universal-refiner/src/core/logger.ts
@@ -1,6 +1,7 @@
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
+import { redact, redactString } from "./redaction.js";
export type LogLevel = "debug" | "info" | "warn" | "error";
@@ -33,25 +34,19 @@ function serializeMeta(meta?: unknown): string {
return "";
}
- if (meta instanceof Error) {
- return `${meta.name}: ${meta.message}\n${meta.stack || ""}`.trim();
- }
-
- if (typeof meta === "string") {
- return meta;
- }
+ const safeMeta = redact(meta);
try {
- return JSON.stringify(meta);
+ return typeof safeMeta === "string" ? safeMeta : JSON.stringify(safeMeta);
} catch {
- return String(meta);
+ return redactString(String(safeMeta));
}
}
function write(level: LogLevel, message: string, meta?: unknown) {
const timestamp = new Date().toISOString();
const renderedMeta = serializeMeta(meta);
- const line = `[${timestamp}] [${level.toUpperCase()}] ${message}${renderedMeta ? ` | ${renderedMeta}` : ""}`;
+ const line = `[${timestamp}] [${level.toUpperCase()}] ${redactString(message)}${renderedMeta ? ` | ${renderedMeta}` : ""}`;
try {
fs.mkdirSync(getGlobalDir(), { recursive: true });
@@ -87,8 +82,6 @@ export class RuntimeLogger {
}
static error(message: string, meta?: unknown) {
- if (shouldLog("error")) {
- write("error", message, meta);
- }
+ write("error", message, meta);
}
}
diff --git a/universal-refiner/src/core/redaction.ts b/universal-refiner/src/core/redaction.ts
new file mode 100644
index 0000000..b0d776e
--- /dev/null
+++ b/universal-refiner/src/core/redaction.ts
@@ -0,0 +1,113 @@
+export const REDACTED = "[REDACTED]";
+
+const SENSITIVE_KEYS = new Set([
+ "authorization",
+ "cookie",
+ "set_cookie",
+ "password",
+ "passwd",
+ "pwd",
+ "secret",
+ "clientsecret",
+ "client_secret",
+ "token",
+ "access_token",
+ "accesstoken",
+ "refresh_token",
+ "refreshtoken",
+ "api_key",
+ "apikey",
+ "x_api_key",
+ "private_key",
+ "privatekey",
+ "connection_string",
+ "connectionstring",
+ "accountkey",
+ "sig",
+ "aws_access_key_id",
+ "aws_secret_access_key",
+ "azure_client_secret",
+ "openai_api_key",
+ "github_token",
+ "gitlab_token",
+ "database_url",
+ "redis_url",
+]);
+const SENSITIVE_FILE = /^(?:\.env(?:\..*)?|\.npmrc|\.pypirc|credentials?(?:\..*)?|secrets?(?:\..*)?|service-account(?:\..*)?|id_(?:rsa|dsa|ecdsa|ed25519)(?:\..*)?|.*\.(?:key|pem|p12|pfx))$/i;
+const URL_PATTERN = /\b[a-z][a-z0-9+.-]*:\/\/[^\s<>"']+/gi;
+const BEARER_PATTERN = /\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi;
+const ASSIGNMENT_PATTERN = /(\b(?:authorization|password|passwd|pwd|secret|client[_-]?secret|access[_-]?token|refresh[_-]?token|api[_-]?key|x-api-key|private[_-]?key|connection[_-]?string|accountkey|sig|token|aws[_-]?(?:access[_-]?key[_-]?id|secret[_-]?access[_-]?key)|azure[_-]?client[_-]?secret|openai[_-]?api[_-]?key|github[_-]?token|gitlab[_-]?token|database[_-]?url|redis[_-]?url)\b\s*[:=]\s*)(["']?)([^"',;\s}\]]+)\2/gi;
+const PRIVATE_KEY_PATTERN = /-----BEGIN (?:[A-Z0-9 ]+ )?PRIVATE KEY-----/i;
+const CREDENTIAL_LITERAL_PATTERN = /\b(?:authorization|password|passwd|pwd|secret|client[_-]?secret|access[_-]?token|refresh[_-]?token|api[_-]?key|x-api-key|private[_-]?key|connection[_-]?string|accountkey|sig|token|aws[_-]?(?:access[_-]?key[_-]?id|secret[_-]?access[_-]?key)|azure[_-]?client[_-]?secret|openai[_-]?api[_-]?key|github[_-]?token|gitlab[_-]?token|database[_-]?url|redis[_-]?url)\b\s*[:=]\s*(?:(["'])[^"'\r\n]{8,}\1|[A-Za-z0-9._~+/=-]{16,})/i;
+const URL_SECRET_PATTERN = /\b[a-z][a-z0-9+.-]*:\/\/(?:[^/\s:@]+:[^@\s]+@|[^\s?#]+[?&](?:password|secret|client[_-]?secret|access[_-]?token|refresh[_-]?token|api[_-]?key|x-api-key|accountkey|sig|token)=)/i;
+
+function isSensitiveKey(key: string): boolean {
+ return SENSITIVE_KEYS.has(key.replace(/[-\s]/g, "_").toLowerCase());
+}
+
+function redactUrl(value: string): string {
+ try {
+ const url = new URL(value);
+ if (url.username) url.username = REDACTED;
+ if (url.password) url.password = REDACTED;
+ for (const key of [...url.searchParams.keys()]) {
+ if (isSensitiveKey(key)) url.searchParams.set(key, REDACTED);
+ }
+ return url.toString();
+ } catch {
+ return value;
+ }
+}
+
+/** Redacts secrets embedded in free-form text, including URL credentials and query parameters. */
+export function redactString(value: string): string {
+ return value
+ .replace(URL_PATTERN, redactUrl)
+ .replace(BEARER_PATTERN, (_match, scheme: string) => `${scheme} ${REDACTED}`)
+ .replace(ASSIGNMENT_PATTERN, (_match, prefix: string, quote: string) => `${prefix}${quote}${REDACTED}${quote}`);
+}
+
+function redactRecursive(value: unknown, seen: WeakSet