import type { IntegrationErrorCode, Judge } from "pi-typesafe"; import type { RulesConfig } from "./config.js"; /** * Project rules: Markdown headings become rules, Jev judges each write or edit against every rule on its own request, and the * agent is steered, never held, with the violated rule. Path scoping and sensitive-path notes are code only. * * Sources, in order: `pi-warden.md` at the project root; else the files in `rules.files`; else, with `rules.fallback`, the * first of README.md, CLAUDE.md, AGENTS.md as one aggregate rule set. Files are re-read when their mtime or size changes. */ export interface Rule { id: string; name: string; /** Rule text under the heading, fences included, `paths:` line removed. */ body: string; /** Globs the rule applies to; empty means every file. */ paths: string[]; } export interface RuleSet { /** Project-relative file names the rules came from. */ sources: string[]; rules: Rule[]; /** Fallback documents have no rule headings: one question judges the content against this whole text. */ aggregate?: string; /** Rules past the request cap, dropped in file order. */ dropped: number; } export declare const RULES_FILE = "pi-warden.md"; export declare const FALLBACK_FILES: string[]; /** TypeSafe answers at most 32 questions per request; one is kept for the edit locator. */ export declare const MAX_RULES = 31; /** * A fallback document cut to `limit` characters while keeping its shape: every heading stays and each section keeps its * head, so a rule stated in the last section of a very long AGENTS.md is still seen. Code inside fences is treated as text. */ export declare function condense(text: string, limit: number): string; /** * Headings at the highest level present outside code fences delimit rules (`#` in a jev-rules style file, `##` under a * document title). Text before the first such heading is not a rule. A `paths:` line at the top of a body scopes the rule. */ export declare function parseRules(markdown: string): Rule[]; export declare function globToRegExp(pattern: string): RegExp; /** The first pattern that matches the path, or undefined. */ export declare function matchGlob(path: string, patterns: readonly string[]): string | undefined; /** Project-relative path with forward slashes, or undefined when the target lies outside the project. */ export declare function projectPath(target: string, cwd: string): string | undefined; export declare class RuleStore { private readonly cache; private read; /** The active rule set for a project, or undefined when no source exists. Never throws. */ load(cwd: string, config: Pick): RuleSet | undefined; } /** Rules that apply to a path: unscoped rules plus those whose `paths` match. */ export declare function rulesFor(set: RuleSet, path: string): Rule[]; export declare function describeRuleSet(set: RuleSet | undefined): string; export interface EditView { id: string; /** Current file text around the replaced text, when the file exists and the text was found. */ before?: string; newText: string; } export interface RulesTarget { tool: "write" | "edit"; /** Project-relative path. */ path: string; content?: string; edits?: EditView[]; /** Edits beyond MAX_EDITS, not shown. */ moreEdits?: number; } /** The redacted content of a write or edit as Jev sees it, or undefined when the call carries nothing to judge. */ export declare function describeTarget(tool: string, input: Record, cwd: string): RulesTarget | undefined; export type RuleOutcome = "compliant" | "violation" | "not_applicable" | "insufficient_context"; export declare const AGGREGATE_QUESTION = "rules"; export declare const LOCATOR_QUESTION = "which_edit"; export declare function ruleQuestion(rule: Rule): import("pi-typesafe").ChoiceQuestion>; export declare function buildRulesRequest(target: RulesTarget, set: RuleSet): { state: { rules?: string; moreEdits?: number; edits?: { newText: string; before?: string; id: string; }[]; content?: string; path: string; }; questions: Record>; applicable: Rule[]; }; export interface RuleScore { id: string; name: string; outcome: RuleOutcome; violation: number; } export interface RuleFinding extends RuleScore { body: string; } export interface RulesVerdict { source: "skipped" | "typesafe" | "error"; path: string; tool: "write" | "edit"; sources: string[]; /** Rules asked about, after path scoping; 1 for an aggregate document. */ asked: number; aggregate: boolean; scores?: RuleScore[]; /** Violations at or above the threshold, strongest first. */ findings: RuleFinding[]; /** The edit Jev points at when several edits were judged and something was flagged. */ editId?: string; editPreview?: string; model?: string; elapsedMs?: number; skippedReason?: string; error?: string; errorCode?: IntegrationErrorCode; } export interface RulesOptions { cwd: string; config: RulesConfig; set: RuleSet | undefined; judge?: Judge | undefined; timeoutMs: number; signal?: AbortSignal | undefined; } /** Why a write or edit is not judged against the rules: nothing to judge, no rules, or a path the config keeps out. */ export declare function skipReason(target: RulesTarget | undefined, set: RuleSet | undefined, config: RulesConfig): string | undefined; export declare function evaluateRules(tool: string, input: Record, options: RulesOptions): Promise; /** Names each violated rule with a short quote of its text; the third hit of one rule in a session makes it a standing rule. */ export declare function rulesSteer(verdict: RulesVerdict, counts: ReadonlyMap): string; export declare function formatRules(verdict: RulesVerdict, template?: string): string; export interface PathNote { glob: string; note: string; } export declare function pathNotes(path: string | undefined, notes: Readonly>): PathNote[]; export declare function pathNoteSteer(path: string, hits: readonly PathNote[]): string; export interface RulesCallRef { id: string; tool: string; input: Record; } /** * The Rules guard for one session: loads and caches the rule sources, judges each write or edit on its own request (fired * together with the action guard's), prejudges sibling writes so their requests overlap, and counts hits per rule so a repeat * becomes a standing rule. */ export declare class RulesGuard { readonly store: RuleStore; private readonly prejudged; private readonly counts; private readonly noted; inspect(call: RulesCallRef, siblings: readonly RulesCallRef[], options: Omit): Promise; /** Records the findings and returns the per-rule session counts the steer text uses. */ count(verdict: RulesVerdict): ReadonlyMap; /** Sensitive-path notes not yet given for this path in this session. */ notesFor(path: string | undefined, notes: Readonly>): PathNote[]; describe(cwd: string, config: Pick): string; turnEnd(): void; reset(): void; }