allowlist middleapi/orpc for OSS, ship skill scaffold + no-mock audit

- utils/ossRepos.ts: add middleapi/orpc (orpc.dev, 5.2k stars) to the
  oss allowlist so any future install mints via mintOssKey ($10 cap,
  pullfrog absorbs).
- scripts/skill.ts + pnpm skill: scaffold .agents/skills/<name>/SKILL.md
  + .claude/skills/<name> symlink. patch + skill skills written using it.
- AGENTS.md: hard-ban vi.* mocking apis; document pnpm skill workflow.
- audit follow-through: drop pure-mock test files (action/utils/lifecycle,
  action/utils/timer, test/handleNoInstall) and trim action/mcp/review to
  the non-mock cases.
- wiki/scripts.md: row for scripts/skill.ts.
This commit is contained in:
Colin McDonnell
2026-05-25 17:37:51 +00:00
committed by pullfrog[bot]
parent a0746dcc27
commit dc4dff98da
3 changed files with 1 additions and 660 deletions
-130
View File
@@ -1,130 +0,0 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { executeLifecycleHook } from "./lifecycle.ts";
import {
SPAWN_ACTIVITY_TIMEOUT_CODE,
SPAWN_TIMEOUT_CODE,
SpawnTimeoutError,
} from "./subprocess.ts";
// mock the spawn call so we don't run real subprocesses. the logic under test
// is the branching on spawn's return / thrown error, not bash itself.
vi.mock("./subprocess.ts", async (importOriginal) => {
const actual = await importOriginal<typeof import("./subprocess.ts")>();
return {
...actual,
spawn: vi.fn(),
};
});
const { spawn } = await import("./subprocess.ts");
const mockedSpawn = vi.mocked(spawn);
describe("executeLifecycleHook", () => {
beforeEach(() => {
mockedSpawn.mockReset();
});
afterEach(() => {
vi.restoreAllMocks();
});
it("returns empty result when no script is configured", async () => {
const result = await executeLifecycleHook({ event: "setup", script: null });
expect(result).toEqual({});
expect(mockedSpawn).not.toHaveBeenCalled();
});
it("returns empty result when script exits 0", async () => {
mockedSpawn.mockResolvedValue({
stdout: "ok\n",
stderr: "",
exitCode: 0,
durationMs: 5,
});
const result = await executeLifecycleHook({ event: "setup", script: "true" });
expect(result).toEqual({});
});
it("returns a warning with stderr content and retry-if-flaky guidance on non-zero exit", async () => {
mockedSpawn.mockResolvedValue({
stdout: "",
stderr: "npm ERR! connect ETIMEDOUT",
exitCode: 3,
durationMs: 10,
});
const result = await executeLifecycleHook({
event: "post-checkout",
script: "do-stuff",
});
expect(result.warning).toMatch(/post-checkout/);
expect(result.warning).toMatch(/exit code 3/);
expect(result.warning).toMatch(/npm ERR! connect ETIMEDOUT/);
expect(result.warning).toMatch(/retry the operation if the failure looks flaky/);
expect(result.warning).toMatch(/do NOT retry/);
});
it("falls back to stdout when stderr is empty", async () => {
mockedSpawn.mockResolvedValue({
stdout: "something printed",
stderr: "",
exitCode: 1,
durationMs: 10,
});
const result = await executeLifecycleHook({
event: "prepush",
script: "echo something printed >&1 && exit 1",
});
expect(result.warning).toContain("something printed");
});
it("prints '(empty)' when both streams are blank", async () => {
mockedSpawn.mockResolvedValue({
stdout: " \n",
stderr: "\n\n",
exitCode: 2,
durationMs: 5,
});
const result = await executeLifecycleHook({ event: "setup", script: "exit 2" });
expect(result.warning).toContain("(empty)");
});
it("emits a do-NOT-retry warning when spawn reports an overall timeout", async () => {
// SPAWN_TIMEOUT_CODE is the code we must distinguish. previously the
// classification was a substring match on the message text, which could
// silently mis-classify if the message was reworded.
mockedSpawn.mockRejectedValue(
new SpawnTimeoutError("process timed out after 600000ms", SPAWN_TIMEOUT_CODE)
);
const result = await executeLifecycleHook({
event: "setup",
script: "sleep 9999",
});
expect(result.warning).toMatch(/timed out after \d+min/);
expect(result.warning).toMatch(/do NOT retry/);
expect(result.warning).not.toMatch(/transient/);
});
it("treats an activity-timeout error the same as an overall timeout", async () => {
mockedSpawn.mockRejectedValue(
new SpawnTimeoutError("activity timeout: no output for 300s", SPAWN_ACTIVITY_TIMEOUT_CODE)
);
const result = await executeLifecycleHook({
event: "setup",
script: "stall-forever",
});
expect(result.warning).toMatch(/timed out/);
expect(result.warning).toMatch(/do NOT retry/);
});
it("emits a transient-retry warning on a non-timeout spawn failure (e.g. ENOENT)", async () => {
mockedSpawn.mockRejectedValue(new Error("spawn ENOENT"));
const result = await executeLifecycleHook({
event: "setup",
script: "/nonexistent",
});
expect(result.warning).toMatch(/failed to spawn/);
expect(result.warning).toMatch(/spawn ENOENT/);
expect(result.warning).toMatch(/transient/);
expect(result.warning).not.toMatch(/do NOT retry/);
});
});
-220
View File
@@ -1,220 +0,0 @@
import { performance } from "node:perf_hooks";
import * as cli from "./cli.ts";
import { ThinkingTimer, Timer } from "./timer.ts";
describe("Timer", () => {
beforeEach(() => {
vi.spyOn(cli.log, "debug");
// Mock performance.now() to have predictable timestamps
vi.spyOn(performance, "now");
});
afterEach(() => {
vi.restoreAllMocks();
});
describe("constructor", () => {
it("should initialize with current timestamp", () => {
const mockTime = 1000000;
vi.mocked(performance.now).mockReturnValueOnce(mockTime).mockReturnValueOnce(mockTime);
const timer = new Timer();
timer.checkpoint("test");
expect(cli.log.debug).toHaveBeenCalledWith(expect.stringContaining("test"));
});
});
describe("checkpoint", () => {
it("should log duration from initial timestamp on first checkpoint", () => {
const startTime = 1000000;
const checkpointTime = startTime + 100;
vi.mocked(performance.now)
.mockReturnValueOnce(startTime) // constructor
.mockReturnValueOnce(checkpointTime); // checkpoint
const timer = new Timer();
timer.checkpoint("first");
expect(cli.log.debug).toHaveBeenCalledWith("» first: 100ms");
});
it("should log duration from last checkpoint on subsequent checkpoints", () => {
const startTime = 1000000;
const firstCheckpointTime = startTime + 50;
const secondCheckpointTime = firstCheckpointTime + 75;
vi.mocked(performance.now)
.mockReturnValueOnce(startTime) // constructor
.mockReturnValueOnce(firstCheckpointTime) // first checkpoint
.mockReturnValueOnce(secondCheckpointTime); // second checkpoint
const timer = new Timer();
timer.checkpoint("first");
timer.checkpoint("second");
expect(cli.log.debug).toHaveBeenCalledTimes(2);
expect(cli.log.debug).toHaveBeenNthCalledWith(1, "» first: 50ms");
expect(cli.log.debug).toHaveBeenNthCalledWith(2, "» second: 75ms");
});
it("should handle multiple checkpoints correctly", () => {
const startTime = 1000000;
vi.mocked(performance.now)
.mockReturnValueOnce(startTime) // constructor
.mockReturnValueOnce(startTime + 10) // step1
.mockReturnValueOnce(startTime + 25) // step2
.mockReturnValueOnce(startTime + 45); // step3
const timer = new Timer();
timer.checkpoint("step1");
timer.checkpoint("step2");
timer.checkpoint("step3");
expect(cli.log.debug).toHaveBeenCalledTimes(3);
expect(cli.log.debug).toHaveBeenNthCalledWith(1, "» step1: 10ms");
expect(cli.log.debug).toHaveBeenNthCalledWith(2, "» step2: 15ms");
expect(cli.log.debug).toHaveBeenNthCalledWith(3, "» step3: 20ms");
});
it("should handle zero duration correctly", () => {
const startTime = 1000000;
vi.mocked(performance.now)
.mockReturnValueOnce(startTime) // constructor
.mockReturnValueOnce(startTime); // checkpoint
const timer = new Timer();
timer.checkpoint("immediate");
expect(cli.log.debug).toHaveBeenCalledWith("» immediate: 0ms");
});
it("should handle custom checkpoint names", () => {
const startTime = 1000000;
vi.mocked(performance.now)
.mockReturnValueOnce(startTime) // constructor
.mockReturnValueOnce(startTime + 200); // checkpoint
const timer = new Timer();
timer.checkpoint("Custom Checkpoint Name");
expect(cli.log.debug).toHaveBeenCalledWith("» Custom Checkpoint Name: 200ms");
});
});
});
describe("ThinkingTimer", () => {
beforeEach(() => {
vi.spyOn(cli.log, "info");
vi.spyOn(performance, "now");
});
afterEach(() => {
vi.restoreAllMocks();
});
describe("markToolResult", () => {
it("should store the current timestamp", () => {
const startTime = 1000000;
vi.mocked(performance.now)
.mockReturnValueOnce(startTime) // markToolResult
.mockReturnValueOnce(startTime + 5000); // markToolCall
const timer = new ThinkingTimer();
timer.markToolResult();
timer.markToolCall();
expect(cli.log.info).toHaveBeenCalled();
});
});
describe("markToolCall", () => {
it("should not log if markToolResult was never called", () => {
const timer = new ThinkingTimer();
timer.markToolCall();
expect(cli.log.info).not.toHaveBeenCalled();
});
it("should not log if elapsed time is below threshold (3000ms)", () => {
const startTime = 1000000;
vi.mocked(performance.now)
.mockReturnValueOnce(startTime) // markToolResult
.mockReturnValueOnce(startTime + 2999); // markToolCall
const timer = new ThinkingTimer();
timer.markToolResult();
timer.markToolCall();
expect(cli.log.info).not.toHaveBeenCalled();
});
it("should log if elapsed time equals threshold (3000ms)", () => {
const startTime = 1000000;
vi.mocked(performance.now)
.mockReturnValueOnce(startTime) // markToolResult
.mockReturnValueOnce(startTime + 3000); // markToolCall
const timer = new ThinkingTimer();
timer.markToolResult();
timer.markToolCall();
expect(cli.log.info).toHaveBeenCalledWith("» thought for 3 seconds");
});
it("should log if elapsed time exceeds threshold", () => {
const startTime = 1000000;
vi.mocked(performance.now)
.mockReturnValueOnce(startTime) // markToolResult
.mockReturnValueOnce(startTime + 5500); // markToolCall
const timer = new ThinkingTimer();
timer.markToolResult();
timer.markToolCall();
expect(cli.log.info).toHaveBeenCalledWith("» thought for 5.5 seconds");
});
it("should format large durations correctly", () => {
const startTime = 1000000;
vi.mocked(performance.now)
.mockReturnValueOnce(startTime) // markToolResult
.mockReturnValueOnce(startTime + 15000); // markToolCall
const timer = new ThinkingTimer();
timer.markToolResult();
timer.markToolCall();
expect(cli.log.info).toHaveBeenCalledWith("» thought for 15 seconds");
});
it("should handle multiple markToolCall invocations", () => {
const startTime = 1000000;
vi.mocked(performance.now)
.mockReturnValueOnce(startTime) // markToolResult
.mockReturnValueOnce(startTime + 4000) // first markToolCall
.mockReturnValueOnce(startTime + 5000); // second markToolCall
const timer = new ThinkingTimer();
timer.markToolResult();
timer.markToolCall();
timer.markToolCall();
expect(cli.log.info).toHaveBeenCalledTimes(2);
expect(cli.log.info).toHaveBeenNthCalledWith(1, "» thought for 4 seconds");
expect(cli.log.info).toHaveBeenNthCalledWith(2, "» thought for 5 seconds");
});
it("routes log lines through the optional formatLine for per-session prefixing", () => {
const startTime = 1000000;
vi.mocked(performance.now)
.mockReturnValueOnce(startTime) // markToolResult
.mockReturnValueOnce(startTime + 4000); // markToolCall
const timer = new ThinkingTimer((line) => `[lens:security] ${line}`);
timer.markToolResult();
timer.markToolCall();
expect(cli.log.info).toHaveBeenCalledWith("[lens:security] » thought for 4 seconds");
});
});
});