Skip to content

Commit 2331f44

Browse files
chore(miner): migrate deny-hooks and pr-outcome to TypeScript (batch 4.5 partial) (#7399)
Convert two well-tested batch 4.5 modules with in-place emit, keeping the rest of #7313 for follow-ups. Extends existing unit tests so CI path filters and codecov/patch stay green. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 467306e commit 2331f44

8 files changed

Lines changed: 289 additions & 124 deletions

File tree

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1 @@
1-
export type DenyRule = {
2-
/** Tool-name glob (`*` = any within a segment, `**` across segments) or an exact tool name. */
3-
matcher: string;
4-
/** Optional glob tested against every path-shaped string in the tool-call input. */
5-
pathPattern?: string;
6-
/** Optional substrings that must ALL appear in one string-shaped input field (e.g. a shell command). */
7-
inputIncludesAll?: string[];
8-
/** Optional pattern that must match a whole whitespace-separated token (quotes stripped) of one
9-
* string-shaped input field — for flag-shaped needles where a substring test would false-positive
10-
* on an unrelated longer flag (e.g. `-f` vs. `--follow-tags`). */
11-
inputTokenPattern?: RegExp;
12-
/** Human-readable reason surfaced when this rule blocks a call. */
13-
reason: string;
14-
};
15-
16-
export type DenyVerdict = {
17-
allowed: boolean;
18-
blockedBy?: DenyRule;
19-
};
20-
21-
export type ProposedToolCall = {
22-
name: string;
23-
input: Record<string, unknown>;
24-
};
25-
26-
export const DEFAULT_DENY_RULES: DenyRule[];
27-
28-
export function evaluateDenyHooks(toolCall: ProposedToolCall, rules?: DenyRule[]): DenyVerdict;
1+
export { DEFAULT_DENY_RULES, evaluateDenyHooks, type DenyRule, type DenyVerdict, type ProposedToolCall, } from "@loopover/engine";

packages/loopover-miner/lib/deny-hooks.js

Lines changed: 4 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// PreToolUse-style deny-hook primitives (#2295). Now a thin re-export of the engine's pure, deterministic deny
2+
// evaluator: the whole implementation moved into `@loopover/engine` (packages/loopover-engine/src/miner/
3+
// deny-hooks.ts) by #5667 so the review stack and the miner share one copy. No behavior change — the evaluator is
4+
// pure (no IO, no globals, no Date/random). Types (DenyRule/DenyVerdict/ProposedToolCall) come from the same
5+
// engine module so the miner package's public contract stays identical after the TypeScript migration.
6+
7+
export {
8+
DEFAULT_DENY_RULES,
9+
evaluateDenyHooks,
10+
type DenyRule,
11+
type DenyVerdict,
12+
type ProposedToolCall,
13+
} from "@loopover/engine";
Lines changed: 59 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,60 @@
11
import type { AppendEventInput, LedgerEntry } from "./event-ledger.js";
2-
3-
export const MINER_PR_OUTCOME_EVENT: "pr_outcome";
4-
export const MINER_PR_OUTCOME_DECISIONS: readonly ["merged", "closed"];
5-
6-
export type MinerPrOutcomeDecision = "merged" | "closed";
7-
8-
export interface NormalizedPrOutcomePayload {
9-
prNumber: number;
10-
decision: MinerPrOutcomeDecision;
11-
closedAt: string | null;
12-
reason: string | null;
13-
}
14-
15-
export interface PrOutcomeInput {
16-
repoFullName?: unknown;
17-
prNumber?: unknown;
18-
decision?: unknown;
19-
closedAt?: unknown;
20-
reason?: unknown;
21-
}
22-
23-
export interface RecordPrOutcomeOptions {
24-
/** Optional at the type level so a caller can pass an unusable ledger to exercise the fail-closed guard; the
25-
* writer throws `invalid_event_ledger` at runtime when this is absent or lacks `appendEvent`. Reuses the
26-
* real EventLedger#appendEvent signature so a genuine EventLedger (not just a same-shaped stub) type-checks. */
27-
eventLedger?: { appendEvent(event: AppendEventInput): LedgerEntry };
28-
}
29-
30-
export interface PrOutcomeLedgerReader {
31-
readEvents(filter?: { since?: number; repoFullName?: string }): unknown[];
32-
}
33-
34-
export function normalizePrOutcomePayload(payload: unknown): NormalizedPrOutcomePayload | null;
35-
36-
export function recordPrOutcomeSnapshot(input: PrOutcomeInput, options?: RecordPrOutcomeOptions): unknown;
37-
38-
export function readPrOutcomes(
39-
eventLedger: PrOutcomeLedgerReader,
40-
filter?: { since?: number; repoFullName?: string },
41-
): Map<string, NormalizedPrOutcomePayload & { repoFullName: string }>;
2+
/** Event-ledger vocabulary for a miner-local PR outcome. */
3+
export declare const MINER_PR_OUTCOME_EVENT: "pr_outcome";
4+
/** The terminal decisions a miner records for one of its own PRs. */
5+
export declare const MINER_PR_OUTCOME_DECISIONS: readonly ["merged", "closed"];
6+
export type MinerPrOutcomeDecision = (typeof MINER_PR_OUTCOME_DECISIONS)[number];
7+
export type NormalizedPrOutcomePayload = {
8+
prNumber: number;
9+
decision: MinerPrOutcomeDecision;
10+
closedAt: string | null;
11+
reason: string | null;
12+
};
13+
export type PrOutcomeInput = {
14+
repoFullName?: unknown;
15+
prNumber?: unknown;
16+
decision?: unknown;
17+
closedAt?: unknown;
18+
reason?: unknown;
19+
};
20+
export type RecordPrOutcomeOptions = {
21+
/** Optional at the type level so a caller can pass an unusable ledger to exercise the fail-closed guard; the
22+
* writer throws `invalid_event_ledger` at runtime when this is absent or lacks `appendEvent`. Reuses the
23+
* real EventLedger#appendEvent signature so a genuine EventLedger (not just a same-shaped stub) type-checks. */
24+
eventLedger?: {
25+
appendEvent(event: AppendEventInput): LedgerEntry;
26+
};
27+
};
28+
export type PrOutcomeLedgerReader = {
29+
readEvents(filter?: {
30+
since?: number;
31+
repoFullName?: string;
32+
}): unknown[];
33+
};
34+
/**
35+
* Validate + normalize a PR-outcome payload; returns `null` on any malformed shape (mirrors manage-status.js's
36+
* `normalizeManageUpdatePayload`, so a bad row can neither be written nor read back). A `closed` decision may carry
37+
* a reason bucket drawn from {@link REJECTION_REASONS} (shared with the rejection-state-machine sibling); a `merged`
38+
* decision — or an unrecognized reason — normalizes the reason to `null` (a merged PR has no rejection reason).
39+
*/
40+
export declare function normalizePrOutcomePayload(payload: unknown): NormalizedPrOutcomePayload | null;
41+
/**
42+
* Thin writer over an INJECTED event ledger (same dependency-injection shape as manage-poll.js's
43+
* `recordManagePollSnapshot`, so it's unit-testable without a real ledger file). Appends one
44+
* {@link MINER_PR_OUTCOME_EVENT} scoped to the repo and returns the appended entry. Fail-soft on a malformed
45+
* snapshot: a missing repo or an invalid payload returns `null` rather than throwing (an unusable ledger is the
46+
* only hard error, since that is a programmer wiring mistake).
47+
*/
48+
export declare function recordPrOutcomeSnapshot(input: PrOutcomeInput, options?: RecordPrOutcomeOptions): unknown;
49+
/**
50+
* Reconstruct the latest outcome per repo/PR from the ledger's ascending append-only event stream (mirrors
51+
* manage-status.js's `indexLatestManageUpdates`). Reads via the injected ledger's `readEvents(filter)` and reduces
52+
* the pure result — a later event for the same repo/PR supersedes an earlier one. Returns a `Map` keyed by
53+
* `repoFullName:prNumber`.
54+
*/
55+
export declare function readPrOutcomes(eventLedger: PrOutcomeLedgerReader | null | undefined, filter?: {
56+
since?: number;
57+
repoFullName?: string;
58+
}): Map<string, NormalizedPrOutcomePayload & {
59+
repoFullName: string;
60+
}>;

packages/loopover-miner/lib/pr-outcome.js

Lines changed: 61 additions & 53 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)