Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
### Fixes
- Make `failproofai config` refuse setup on an unsupported platform (Windows, today) instead of completing it unenforced. The wizard used to skip the daemon requirement and finish anyway, leaving the machine reading as configured while enforcing in-process with no fail-closed guarantee — now it prints why and exits 1 before drawing a single prompt, writing nothing. (#664)

### Chores
- Remove compiler-proven dead code left around the `failproofaid` integration, including the obsolete daemon-hook wrapper and unused `html2canvas` dependency. Enable `noUnusedLocals` to prevent that residue returning, and correct stale daemon documentation and package metadata.

## 1.0.0-beta.12 — 2026-08-07

### Fixes
Expand Down
2 changes: 1 addition & 1 deletion __tests__/actions/update-scheduled-audit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* parity is real: both write through the same `updateConfig`.
*/
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { mkdtempSync, readFileSync, rmSync, writeFileSync, mkdirSync } from "node:fs";
import { mkdtempSync, readFileSync, rmSync, mkdirSync } from "node:fs";
import { tmpdir } from "node:os";
import { resolve } from "node:path";
import { configFile } from "../../src/hooks/fp-home";
Expand Down
2 changes: 1 addition & 1 deletion __tests__/audit/cache.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @vitest-environment node
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir, homedir } from "node:os";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { createHash } from "node:crypto";
import {
Expand Down
2 changes: 1 addition & 1 deletion __tests__/components/button.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect, vi } from "vitest";
import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import { createRef } from "react";
import { Button } from "@/components/ui/button";
Expand Down
2 changes: 1 addition & 1 deletion __tests__/components/date-picker-input.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import DatePickerInput from "@/app/components/date-picker-input";

Expand Down
1 change: 0 additions & 1 deletion __tests__/e2e/hooks/codex-integration.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
assertAllow,
assertPreToolUseDeny,
assertPostToolUseDeny,
assertStopInstruct,
assertPermissionRequestDeny,
} from "../helpers/hook-runner";
import { CodexPayloads } from "../helpers/payloads";
Expand Down
1 change: 0 additions & 1 deletion __tests__/hooks/cloud-enrollment-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { resolve } from "node:path";
import { runConnectCommand, runDisconnectCommand, connectionStatusLines } from "../../src/hooks/cloud-enrollment-cli";
import { cloudCredentialPath, readCloudCredentials, writeCloudCredentials } from "../../src/hooks/cloud-enrollment";
import { readIngestCredential } from "../../src/hooks/collector-config";
import { readHooksConfig } from "../../src/hooks/hooks-config";
import { readConfig } from "../../src/hooks/fp-config";

let dir: string;
Expand Down
2 changes: 1 addition & 1 deletion __tests__/hooks/collector-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// `policies-config.json` is 0664 inside a 0775 `~/.failproofai` on a normal
// machine, which is exactly why the key lives in its own file at 0600.
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { mkdtempSync, rmSync, statSync, readFileSync, writeFileSync, chmodSync, mkdirSync } from "node:fs";
import { mkdtempSync, rmSync, statSync, writeFileSync, chmodSync, mkdirSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";

Expand Down
3 changes: 1 addition & 2 deletions __tests__/hooks/configure-wizard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,13 @@ import {
maybeFirstRunConfigure,
hasSeenLauncher,
markLauncherSeen,
classifyDaemonInstallFailure,
} from "../../src/hooks/configure-wizard";
import { resolvePreset, resolveEverything } from "../../src/hooks/policy-presets";
import { INTEGRATION_TYPES, type IntegrationType } from "../../src/hooks/types";
import { getIntegration } from "../../src/hooks/integrations";
import { runPostSetupAudit } from "../../src/audit/cli";
import { trackHookEvent } from "../../src/hooks/hook-telemetry";
import { globalPolicyConfigFile, configFile as fpConfigFile, launcherMarker } from "../../src/hooks/fp-home";
import { configFile as fpConfigFile, launcherMarker } from "../../src/hooks/fp-home";
import { readConfig as readFpConfig } from "../../src/hooks/fp-config";

const mkTtyStdin = (): TTYIn => ({ isTTY: true }) as unknown as TTYIn;
Expand Down
37 changes: 17 additions & 20 deletions __tests__/hooks/daemon-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { writeConfig, DEFAULT_CONFIG } from "../../src/hooks/fp-config";
import type { DaemonHookRequest, DaemonHookResponse } from "../../src/hooks/daemon-client";

vi.mock("../../src/hooks/hook-logger", () => ({
hookLogInfo: vi.fn(),
Expand Down Expand Up @@ -74,6 +75,12 @@ describe("hooks/daemon-client", () => {
await new Promise<void>((resolvePromise) => server!.listen(socketPath, resolvePromise));
}

async function daemonResult(req: DaemonHookRequest): Promise<DaemonHookResponse | null> {
const { attemptDaemonHook } = await import("../../src/hooks/daemon-client");
const attempt = await attemptDaemonHook(req);
return attempt.ok ? attempt.response : null;
}

it("returns the parsed result on a real hookResult response", async () => {
await startServer(async (socket) => {
const req = await readFrame(socket);
Expand All @@ -92,8 +99,7 @@ describe("hooks/daemon-client", () => {
);
});

const { tryDaemonHook } = await import("../../src/hooks/daemon-client");
const result = await tryDaemonHook({
const result = await daemonResult({
hookEvent: "PreToolUse",
cli: "claude",
stdin: "{}",
Expand All @@ -116,8 +122,7 @@ describe("hooks/daemon-client", () => {
);
});

const { tryDaemonHook } = await import("../../src/hooks/daemon-client");
const result = await tryDaemonHook({ hookEvent: "PreToolUse", cli: "claude", stdin: "{}" });
const result = await daemonResult({ hookEvent: "PreToolUse", cli: "claude", stdin: "{}" });
expect(result).toEqual({ exitCode: 2, stdout: "", stderr: "blocked: sudo is not allowed" });
});

Expand All @@ -127,8 +132,7 @@ describe("hooks/daemon-client", () => {
socket.end(encodeFrame({ type: "error", protocolVersion: 1, message: "daemon unreachable" }));
});

const { tryDaemonHook } = await import("../../src/hooks/daemon-client");
const result = await tryDaemonHook({ hookEvent: "Stop", cli: "codex", stdin: "{}" });
const result = await daemonResult({ hookEvent: "Stop", cli: "codex", stdin: "{}" });
expect(result).toBeNull();
});

Expand All @@ -140,8 +144,7 @@ describe("hooks/daemon-client", () => {
);
});

const { tryDaemonHook } = await import("../../src/hooks/daemon-client");
const result = await tryDaemonHook({ hookEvent: "PreToolUse", cli: "claude", stdin: "{}" });
const result = await daemonResult({ hookEvent: "PreToolUse", cli: "claude", stdin: "{}" });
expect(result).toBeNull();
});

Expand Down Expand Up @@ -204,16 +207,14 @@ describe("hooks/daemon-client", () => {
socket.end(encodeFrame({ type: "hookResult", protocolVersion: 1, stdout: "", stderr: "" }));
});

const { tryDaemonHook } = await import("../../src/hooks/daemon-client");
const result = await tryDaemonHook({ hookEvent: "PreToolUse", cli: "claude", stdin: "{}" });
const result = await daemonResult({ hookEvent: "PreToolUse", cli: "claude", stdin: "{}" });
expect(result).toBeNull();
});

it("returns null immediately when no socket file exists at all", async () => {
// No server started — socketPath was never bound.
const { tryDaemonHook } = await import("../../src/hooks/daemon-client");
const start = Date.now();
const result = await tryDaemonHook({ hookEvent: "PreToolUse", cli: "claude", stdin: "{}" });
const result = await daemonResult({ hookEvent: "PreToolUse", cli: "claude", stdin: "{}" });
const elapsedMs = Date.now() - start;
expect(result).toBeNull();
// ENOENT/ECONNREFUSED on a nonexistent socket is a kernel-level rejection,
Expand All @@ -237,9 +238,8 @@ describe("hooks/daemon-client", () => {
}, 600);
});

const { tryDaemonHook } = await import("../../src/hooks/daemon-client");
const start = Date.now();
const result = await tryDaemonHook({ hookEvent: "PreToolUse", cli: "claude", stdin: "{}" });
const result = await daemonResult({ hookEvent: "PreToolUse", cli: "claude", stdin: "{}" });
expect(result).toEqual({ exitCode: 0, stdout: "ok", stderr: "" });
expect(Date.now() - start).toBeGreaterThanOrEqual(500);
});
Expand All @@ -252,9 +252,8 @@ describe("hooks/daemon-client", () => {
// Deliberately never write a response.
});

const { tryDaemonHook } = await import("../../src/hooks/daemon-client");
let settled = false;
const pending = tryDaemonHook({ hookEvent: "PreToolUse", cli: "claude", stdin: "{}" }).then((r) => {
const pending = daemonResult({ hookEvent: "PreToolUse", cli: "claude", stdin: "{}" }).then((r) => {
settled = true;
return r;
});
Expand All @@ -277,18 +276,16 @@ describe("hooks/daemon-client", () => {
socket.end(Buffer.concat([header, body]));
});

const { tryDaemonHook } = await import("../../src/hooks/daemon-client");
const result = await tryDaemonHook({ hookEvent: "PreToolUse", cli: "claude", stdin: "{}" });
const result = await daemonResult({ hookEvent: "PreToolUse", cli: "claude", stdin: "{}" });
expect(result).toBeNull();
});

it("skips the attempt entirely on win32, never touching the socket", async () => {
const originalPlatform = process.platform;
Object.defineProperty(process, "platform", { value: "win32" });
try {
const { tryDaemonHook } = await import("../../src/hooks/daemon-client");
const start = Date.now();
const result = await tryDaemonHook({ hookEvent: "PreToolUse", cli: "claude", stdin: "{}" });
const result = await daemonResult({ hookEvent: "PreToolUse", cli: "claude", stdin: "{}" });
const elapsedMs = Date.now() - start;
expect(result).toBeNull();
expect(elapsedMs).toBeLessThan(20);
Expand Down
2 changes: 1 addition & 1 deletion __tests__/hooks/fp-home.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { mkdtempSync, rmSync, mkdirSync, writeFileSync, existsSync, readFileSync } from "node:fs";
import { mkdtempSync, rmSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { resolve } from "node:path";
import * as H from "../../src/hooks/fp-home";
Expand Down
4 changes: 1 addition & 3 deletions __tests__/hooks/new-telemetry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
* fire at the trigger site. Keep one focused case per event.
*/
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { readFileSync, existsSync } from "node:fs";
import { execSync } from "node:child_process";
import { resolve } from "node:path";
import { homedir } from "node:os";

vi.mock("node:fs", () => ({
readFileSync: vi.fn(),
Expand Down
2 changes: 1 addition & 1 deletion __tests__/lib/codex-sessions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { tmpdir, homedir } from "node:os";
import { tmpdir } from "node:os";

const line = (obj: Record<string, unknown>): string => JSON.stringify(obj);

Expand Down
2 changes: 1 addition & 1 deletion __tests__/lib/pi-sessions.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @vitest-environment node
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, unlinkSync } from "node:fs";
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import type { AssistantEntry, ContentBlock, ToolUseBlock } from "@/lib/log-entries";
Expand Down
1 change: 0 additions & 1 deletion __tests__/lib/telemetry-id.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ vi.mock("node:crypto", async () => {

const mockedFs = vi.mocked(fs);
const mockedOs = vi.mocked(os);
const mockedCrypto = vi.mocked(crypto);
const mockedExecSync = vi.mocked(execSync);

describe("lib/telemetry-id", () => {
Expand Down
1 change: 0 additions & 1 deletion __tests__/lib/telemetry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import {
isTelemetryEnabled,
initTelemetry,
trackEvent,
flushTelemetry,
shutdownTelemetry,
} from "@/lib/telemetry";

Expand Down
2 changes: 1 addition & 1 deletion __tests__/scripts/translate-docs/cache.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @vitest-environment node
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { describe, it, expect } from "vitest";
import {
contentHash,
getCacheKey,
Expand Down
1 change: 0 additions & 1 deletion app/actions/get-hooks-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { customPolicyId, conventionPolicyId, discoverPolicyFiles } from "@/src/h
import { findProjectConfigDir } from "@/src/hooks/hooks-config";
import { readFile } from "node:fs/promises";
import { existsSync } from "node:fs";
import { homedir } from "node:os";
import { basename, resolve } from "node:path";
import { customPoliciesDir } from "@/src/hooks/fp-home";

Expand Down
4 changes: 1 addition & 3 deletions app/audit/_components/audit-dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
*
* Empty / running states fall back to EmptyState and RunProgress.
*/
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { getAuditResultAction } from "@/app/actions/get-audit-result";
import type { AuditResult, RunAuditOptions } from "@/src/audit/types";
import { classifyAgent } from "@/src/audit/archetypes";
Expand Down Expand Up @@ -227,8 +227,6 @@ export function AuditDashboard({ initial, projectFromUrl, totalCatalogSize }: Pr
const result = cache.status === "cached" ? cache.result : null;
if (!result) return null;
const cachedAt = cache.status === "cached" ? cache.cachedAt : null;
const params = cache.status === "cached" ? cache.params : undefined;

/* ---- scanned but zero sessions --------------------------------- */
if (result.transcripts.scanned === 0) {
return (
Expand Down
2 changes: 1 addition & 1 deletion app/audit/_components/audit-progress-strip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* Reuses the existing `RerunError.kind` discrimination to render a red
* error strip with kind-specific copy when a run dies.
*/
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import type { RerunError } from "./rerun-button";

export type RerunStatus =
Expand Down
2 changes: 1 addition & 1 deletion app/audit/_components/come-back-better-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* link sits under the reminder card so the affordance survives without
* dominating the layout.
*/
import React, { useCallback, useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { usePostHog } from "@/contexts/PostHogContext";
import { isAbortError } from "@/lib/fetch-with-timeout";
import { AuthDialog, type AuthedUser } from "./auth-dialog";
Expand Down
1 change: 0 additions & 1 deletion app/audit/_components/empty-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
* sharp `.btn-press` action button. Sized so it occupies the same vertical
* space as the loaded dashboard does on its hero — no more cramped popover.
*/
import React from "react";
import { triggerRun } from "./rerun-button";
import { usePostHog } from "@/contexts/PostHogContext";

Expand Down
2 changes: 1 addition & 1 deletion app/audit/_components/how-to-improve-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* A single "install all" button at the section header copies the
* combined install command for every prescribed policy.
*/
import React, { useMemo, useState } from "react";
import { useMemo, useState } from "react";
import type { AuditResult } from "@/src/audit/types";
import { type Grade, tierName } from "@/src/audit/scoring";
import { usePostHog } from "@/contexts/PostHogContext";
Expand Down
1 change: 0 additions & 1 deletion app/audit/_components/quirks-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
* detector. No per-finding card chrome, no 4-quad body, no corner
* crosshairs — evidence and fix live in section 04 (How to improve).
*/
import React from "react";
import type { FindingCard } from "@/src/audit/findings";

interface Props {
Expand Down
2 changes: 0 additions & 2 deletions app/audit/_components/report-footer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"use client";

import React from "react";

interface Props {
cachedAt: string | null;
fixed?: boolean;
Expand Down
2 changes: 1 addition & 1 deletion app/audit/_components/run-progress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* pink "▮▮" / dim "○" markers, and a marquee progress bar at the bottom
* filling pink-on-dark as the run advances.
*/
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";

const STAGES = [
{ label: "discovering transcripts", detail: "walking ~/.claude, ~/.codex, ~/.cursor, …" },
Expand Down
1 change: 0 additions & 1 deletion app/audit/_components/strengths-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* ✓ glyph · headline + sub · right-aligned metric. No card chrome,
* no hover backgrounds, no checkmark backdrop.
*/
import React from "react";
import type { Strength } from "@/src/audit/strengths";

interface Props {
Expand Down
2 changes: 1 addition & 1 deletion app/components/pause-notices.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* where every policy ran and allowed, and without saying so the log asserts a
* clean window over exactly the window that was not enforced.
*/
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { ShieldAlert, TriangleAlert } from "lucide-react";
import type { ActivePause } from "@/src/hooks/session-pause";

Expand Down
2 changes: 1 addition & 1 deletion app/components/raw-log-viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/
"use client";

import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { useWindowVirtualizer } from "@tanstack/react-virtual";
import { ChevronDown, Wrench } from "lucide-react";
import type { LogEntry, ToolUseBlock } from "@/lib/log-entries";
Expand Down
2 changes: 1 addition & 1 deletion app/components/toast.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useSyncExternalStore, useCallback, useEffect, useState } from "react";
import { useSyncExternalStore, useEffect, useState } from "react";

interface Toast {
id: number;
Expand Down
Loading