pi-warden 0.40.1 → 0.40.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +1 -1
- package/dist/action-guard.d.ts +4 -4
- package/dist/extension.js +8 -5
- package/dist/guard.d.ts +6 -4
- package/dist/guard.js +3 -2
- package/dist/index.d.ts +1 -1
- package/dist/rules-file.d.ts +23 -8
- package/dist/rules-file.js +61 -31
- package/dist/rules.d.ts +13 -1
- package/dist/rules.js +12 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,13 @@ How to keep this current: add the entry in the same pull request as the change,
|
|
|
8
8
|
|
|
9
9
|
<!-- Empty. Next release starts here. -->
|
|
10
10
|
|
|
11
|
+
## 0.40.2
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- The first-run notice no longer names a fallback document when `rules.files` is what resolved. It was decided by whether `pi-warden.md` existed, so a project whose rules come from `rules.files` was told `AGENTS.md` was judging it while its own configured files were the ones in force — and the notice's remedy, creating a `pi-warden.md`, is the one thing that shadows those files. The notice now asks the rule store which tier answered and fires only when a fallback document really is in force, listing `rules.files` among the tiers it looked at.
|
|
16
|
+
- The escalation request carries the rules the config resolves, not a document of the resolver's own choosing. It walked `pi-warden.md` and then `AGENTS.md` / `CLAUDE.md` / `README.md` while the rules guard was judging with `rules.files`, so a project with configured rules sent `AGENTS.md` prose as its rules, or no rules at all when it had no such file. It now takes every file in `rules.files`, in order, before the fallback names, and honors `rules.fallback: false`.
|
|
17
|
+
|
|
11
18
|
## 0.40.1
|
|
12
19
|
|
|
13
20
|
### Fixed
|
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ paths: src/**/*.ts
|
|
|
68
68
|
Every exported function declares its return type.
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
Jev judges every write and quotes violations back. Rules can be things no linter checks: "a TODO must name a ticket", "comments must not restate the code". Without `pi-warden.md`, the first of `
|
|
71
|
+
Jev judges every write and quotes violations back. Rules can be things no linter checks: "a TODO must name a ticket", "comments must not restate the code". Without `pi-warden.md`, the first of `AGENTS.md`/`CLAUDE.md`/`README.md` is judged instead. [Examples and config details.](docs/configuration.md)
|
|
72
72
|
|
|
73
73
|
## Does it actually help?
|
|
74
74
|
|
package/dist/action-guard.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ActionGuardConfig,
|
|
2
|
-
import type { PreviousAction, TaskMessage, Verdict } from "./guard.js";
|
|
1
|
+
import type { ActionGuardConfig, SecurityConfig, SlopGuardConfig } from "./config.js";
|
|
2
|
+
import type { EvaluateOptions, PreviousAction, TaskMessage, Verdict } from "./guard.js";
|
|
3
3
|
import type { Judge } from "pi-typesafe";
|
|
4
4
|
/** One tool call as the agent proposed it. `id` is Pi's tool call id, stable across hooks and retries. */
|
|
5
5
|
export interface ToolCallRef {
|
|
@@ -25,8 +25,8 @@ export interface InspectOptions {
|
|
|
25
25
|
signal?: AbortSignal | undefined;
|
|
26
26
|
slop?: SlopGuardConfig | undefined;
|
|
27
27
|
security?: SecurityConfig | undefined;
|
|
28
|
-
/** The rules guard's switch
|
|
29
|
-
rules?:
|
|
28
|
+
/** The rules guard's switch, forwarded to `evaluateAction`: off keeps the rules content out of the request. */
|
|
29
|
+
rules?: EvaluateOptions["rules"];
|
|
30
30
|
/** Calls allowed in the previous turn; the regret question about them rides this call's request, never a sibling's. */
|
|
31
31
|
previousActions?: readonly PreviousAction[] | undefined;
|
|
32
32
|
}
|
package/dist/extension.js
CHANGED
|
@@ -20,7 +20,7 @@ import { evaluateProse, proseNudge, ProseTrend, RESTATE_MIN_SENTENCES, RESTATE_S
|
|
|
20
20
|
import { compressOutput, duplicateNote, evaluateOutput, mergeOutput, outputKey, saveOutput, securityNotice, CompressionLearner } from "./output.js";
|
|
21
21
|
import { classifyRecall, detectSearchTool, recallInstruction } from "./recall.js";
|
|
22
22
|
import { redact } from "./redact.js";
|
|
23
|
-
import { formatRules, pathNoteSteer, RulesGuard, rulesSteer } from "./rules.js";
|
|
23
|
+
import { formatRules, pathNoteSteer, RulesGuard, rulesSteer, RULES_FILE, FALLBACK_FILES } from "./rules.js";
|
|
24
24
|
import { checkPiWardenMissing } from "./rules-file.js";
|
|
25
25
|
import { writeStarterRules, buildInitPrompt } from "./init.js";
|
|
26
26
|
import { buildAuditPrompt, findProjects, snapshotReport, reportOutcome } from "./audit.js";
|
|
@@ -1060,14 +1060,17 @@ export default function wardenExtension(pi) {
|
|
|
1060
1060
|
if (!config.action.enabled || !config.action.tools.includes(event.toolName))
|
|
1061
1061
|
return;
|
|
1062
1062
|
stats.inspected++;
|
|
1063
|
-
// First-run warning:
|
|
1063
|
+
// First-run warning: no project rules file resolved, one time per session.
|
|
1064
1064
|
if (!warnedMissingRules && config.rules.enabled) {
|
|
1065
|
-
const { missing, fallbackSource } = checkPiWardenMissing(ctx.cwd);
|
|
1065
|
+
const { missing, fallbackSource } = checkPiWardenMissing(ctx.cwd, config.rules);
|
|
1066
1066
|
if (missing && ctx.hasUI) {
|
|
1067
1067
|
warnedMissingRules = true;
|
|
1068
|
+
// The tiers still in play, in resolution order: naming README/CLAUDE/AGENTS to someone who
|
|
1069
|
+
// turned the fallback off would send them to a document pi-warden will not read.
|
|
1070
|
+
const tiers = [RULES_FILE, "rules.files", ...(config.rules.fallback ? FALLBACK_FILES : [])].join(", ");
|
|
1068
1071
|
const msg = fallbackSource
|
|
1069
|
-
? `No pi-warden.md
|
|
1070
|
-
: `No rules file detected (
|
|
1072
|
+
? `No pi-warden.md or rules.files entry here. Using ${fallbackSource} as active fallback rules. Run /warden init to create project-specific rules.`
|
|
1073
|
+
: `No rules file detected (${tiers}). Run /warden init to create project-specific rules.`;
|
|
1071
1074
|
ctx.ui.notify(msg, "warning");
|
|
1072
1075
|
}
|
|
1073
1076
|
}
|
package/dist/guard.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IntegrationErrorCode, Judge, Questions } from "pi-typesafe";
|
|
2
2
|
import type { ActionGuardConfig, ArmingRule, CommandRule, PathRule, RulesConfig, SecurityConfig, SlopGuardConfig } from "./config.js";
|
|
3
|
+
import type { RulesSourceConfig } from "./rules.js";
|
|
3
4
|
export type Level = "allow" | "warn" | "confirm" | "deny";
|
|
4
5
|
export type Severity = "destructive" | "risky" | "sensitive" | "deny";
|
|
5
6
|
export type ViolationSource = "pattern" | "rules-guard" | "security-guard" | "slop-guard";
|
|
@@ -171,11 +172,12 @@ export interface EvaluateOptions {
|
|
|
171
172
|
slop?: SlopGuardConfig | undefined;
|
|
172
173
|
security?: SecurityConfig | undefined;
|
|
173
174
|
/**
|
|
174
|
-
* The rules guard's switch. The active
|
|
175
|
-
* that content on this machine.
|
|
176
|
-
*
|
|
175
|
+
* The rules guard's switch. The rules the active config resolves ride every judged action request, so
|
|
176
|
+
* `enabled: false` is what keeps that content on this machine. `files` and `fallback` say which documents
|
|
177
|
+
* those are; omitting them sends what the config would resolve unfiltered, so a library caller that passes
|
|
178
|
+
* no rules config keeps today's behaviour.
|
|
177
179
|
*/
|
|
178
|
-
rules?: Pick<RulesConfig, "enabled"> | undefined;
|
|
180
|
+
rules?: Pick<RulesConfig, "enabled"> & Partial<RulesSourceConfig> | undefined;
|
|
179
181
|
/** This exact call was held earlier and the user has replied since: ask whether the reply approves it. */
|
|
180
182
|
retryAfterHold?: boolean | undefined;
|
|
181
183
|
/** Calls allowed in the previous turn: ask whether the user's latest message regrets one of them (rides this request). */
|
package/dist/guard.js
CHANGED
|
@@ -877,8 +877,9 @@ export async function evaluateAction(action, options) {
|
|
|
877
877
|
if (!judge)
|
|
878
878
|
return withPlan({ level, source: "pattern", summary, patterns, reasons });
|
|
879
879
|
// Resolve the rules file once per call for the Jev request state. With the rules guard off, no rules content leaves
|
|
880
|
-
// the machine at all: no question names the field, so an absent one costs nothing.
|
|
881
|
-
|
|
880
|
+
// the machine at all: no question names the field, so an absent one costs nothing. The config decides which files
|
|
881
|
+
// count, so the content sent here is the content the guard judges with.
|
|
882
|
+
const resolved = options.rules?.enabled === false ? null : resolveRulesFile(action.cwd, options.rules);
|
|
882
883
|
const floorHits = builtInHits.length ? builtInHits.join("; ") : "none";
|
|
883
884
|
const request = buildRequest(summary, action.task, { slop: options.slop?.enabled ?? false, approval: options.retryAfterHold ?? false, security: options.security?.enabled ?? false, context: action.context, previousActions: options.previousActions, plan, questions: options.questions, rules: resolved?.content, rulesSource: resolved?.source, violations: remainingViolations, floorHits });
|
|
884
885
|
const result = await ask(judge, request, { timeoutMs: config.timeoutMs, ...(options.signal ? { signal: options.signal } : {}) });
|
package/dist/index.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ export type { WidgetConfig, WidgetPlacement, WidgetBarMode, Tokens } from "./wid
|
|
|
37
37
|
export { Trace, actionDetails, stuckDetails, doneDetails, proseDetails, runawayDetails, rulesDetails } from "./trace.js";
|
|
38
38
|
export type { TraceEntry, GuardName } from "./trace.js";
|
|
39
39
|
export { parseRules, condense, isRuleShaped, globToRegExp, matchGlob, projectPath, RuleStore, rulesFor, describeRuleSet, describeTarget, buildRulesRequest, ruleQuestion, evaluateRules, skipReason, rulesSteer, formatRules, pathNotes, pathNoteSteer, RulesGuard, RULES_FILE, FALLBACK_FILES, MAX_RULES, AGGREGATE_QUESTION, LOCATOR_QUESTION } from "./rules.js";
|
|
40
|
-
export type { Rule, RuleSet, RuleOutcome, RuleScore, RuleFinding, RulesVerdict, RulesOptions, RulesTarget, EditView, PathNote, RulesCallRef } from "./rules.js";
|
|
40
|
+
export type { Rule, RuleSet, RuleOutcome, RuleScore, RuleFinding, RulesVerdict, RulesOptions, RulesTarget, EditView, PathNote, RulesCallRef, RulesTier, RulesSourceConfig } from "./rules.js";
|
|
41
41
|
export { resolveRulesFile, extractRules, checkPiWardenMissing } from "./rules-file.js";
|
|
42
42
|
export type { ResolvedRulesFile } from "./rules-file.js";
|
|
43
43
|
export { writeStarterRules, generateStarterRules, detectProjectType, buildProjectContext, buildInitPrompt } from "./init.js";
|
package/dist/rules-file.d.ts
CHANGED
|
@@ -1,18 +1,33 @@
|
|
|
1
|
+
import type { RulesConfig } from "./config.js";
|
|
2
|
+
import type { RulesSourceConfig } from "./rules.js";
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
+
* The rules content for escalation and context. The order is the rules guard's: pi-warden.md alone, else the files in
|
|
5
|
+
* `rules.files`, in order, else the fallback names while `rules.fallback` is on. An empty document counts where the
|
|
6
|
+
* guard counts it: a configured file is a source whatever it holds, a fallback document has to say something.
|
|
4
7
|
*/
|
|
5
8
|
export interface ResolvedRulesFile {
|
|
6
|
-
path: string;
|
|
7
9
|
content: string;
|
|
10
|
+
/** The file name, or the configured names joined by commas, as the config spells them. */
|
|
8
11
|
source: string;
|
|
9
12
|
}
|
|
10
13
|
/** Token-aware truncation: extract heading blocks, cap at maxTokens (1 token ≈ 4 chars). */
|
|
11
14
|
export declare function extractRules(content: string, maxTokens?: number): string;
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the rules content the escalation request carries. Returns null when the config reaches no document.
|
|
17
|
+
*
|
|
18
|
+
* `RuleStore` judges with the documents this returns, in this order, so the request names the rules in force. What
|
|
19
|
+
* differs is the cap: the request carries one budget for the whole field (`MAX_CHARS`) where the guard spends
|
|
20
|
+
* `rules.maxChars` per resolution.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveRulesFile(cwd: string, config?: Partial<RulesSourceConfig>): ResolvedRulesFile | null;
|
|
23
|
+
/**
|
|
24
|
+
* Whether the first-run "no project rules" notice should fire, and which document is being judged.
|
|
25
|
+
*
|
|
26
|
+
* RuleStore.loadTiered owns the resolution order, so ask it which tier answered instead of walking
|
|
27
|
+
* the filesystem here. Inferring one warns while the configured files are the ones in force, names a
|
|
28
|
+
* fallback document the config turned off, or names a `rules.files` entry after the file it points at.
|
|
29
|
+
*/
|
|
30
|
+
export declare function checkPiWardenMissing(cwd: string, config: Pick<RulesConfig, "files" | "fallback" | "maxChars">): {
|
|
16
31
|
missing: boolean;
|
|
17
|
-
fallbackSource?: string;
|
|
32
|
+
fallbackSource?: string | undefined;
|
|
18
33
|
};
|
package/dist/rules-file.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
3
|
import { redact } from "./redact.js";
|
|
4
|
-
import { RULES_FILE, FALLBACK_FILES } from "./rules.js";
|
|
5
|
-
const RULES_CANDIDATES = [
|
|
6
|
-
{ path: RULES_FILE, source: RULES_FILE },
|
|
7
|
-
...FALLBACK_FILES.map(f => ({ path: f, source: f })),
|
|
8
|
-
];
|
|
4
|
+
import { RuleStore, RULES_FILE, FALLBACK_FILES } from "./rules.js";
|
|
9
5
|
const MAX_CHARS = 16_000; // ~4000 tokens
|
|
6
|
+
/** A rules file's text, or undefined when it is missing, unreadable, or a directory. */
|
|
7
|
+
function readRules(path) {
|
|
8
|
+
if (!existsSync(path))
|
|
9
|
+
return undefined;
|
|
10
|
+
try {
|
|
11
|
+
return readFileSync(path, "utf8");
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// Unreadable or non-file entries (e.g., directories matching a candidate name) are skipped.
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
10
18
|
/** Token-aware truncation: extract heading blocks, cap at maxTokens (1 token ≈ 4 chars). */
|
|
11
19
|
export function extractRules(content, maxTokens = 4000) {
|
|
12
20
|
const lines = content.split("\n");
|
|
@@ -41,32 +49,54 @@ export function extractRules(content, maxTokens = 4000) {
|
|
|
41
49
|
}
|
|
42
50
|
return result.join("\n\n");
|
|
43
51
|
}
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
/** Content is redacted before it leaves this machine, and capped once it is past the budget. */
|
|
53
|
+
function redactAndCap(source, raw) {
|
|
54
|
+
return { content: raw.length > MAX_CHARS ? redact(extractRules(raw)) : redact(raw), source };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the rules content the escalation request carries. Returns null when the config reaches no document.
|
|
58
|
+
*
|
|
59
|
+
* `RuleStore` judges with the documents this returns, in this order, so the request names the rules in force. What
|
|
60
|
+
* differs is the cap: the request carries one budget for the whole field (`MAX_CHARS`) where the guard spends
|
|
61
|
+
* `rules.maxChars` per resolution.
|
|
62
|
+
*/
|
|
63
|
+
export function resolveRulesFile(cwd, config) {
|
|
64
|
+
const root = resolve(cwd, RULES_FILE);
|
|
65
|
+
const rootContent = readRules(root);
|
|
66
|
+
if (rootContent !== undefined)
|
|
67
|
+
return redactAndCap(RULES_FILE, rootContent);
|
|
68
|
+
const configured = [];
|
|
69
|
+
for (const entry of config?.files ?? []) {
|
|
70
|
+
const content = readRules(resolve(cwd, entry));
|
|
71
|
+
if (content !== undefined)
|
|
72
|
+
configured.push({ source: entry, content });
|
|
73
|
+
}
|
|
74
|
+
if (configured.length) {
|
|
75
|
+
return redactAndCap(configured.map(file => file.source).join(", "), configured.map(file => file.content).join("\n\n"));
|
|
76
|
+
}
|
|
77
|
+
if (config?.fallback === false)
|
|
78
|
+
return null;
|
|
79
|
+
for (const file of FALLBACK_FILES) {
|
|
80
|
+
const content = readRules(resolve(cwd, file));
|
|
81
|
+
// A blank fallback document is not a source: the store walks on to the next name, so this does too.
|
|
82
|
+
if (content?.trim())
|
|
83
|
+
return redactAndCap(file, content);
|
|
60
84
|
}
|
|
61
85
|
return null;
|
|
62
86
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
87
|
+
const missingStore = new RuleStore();
|
|
88
|
+
/**
|
|
89
|
+
* Whether the first-run "no project rules" notice should fire, and which document is being judged.
|
|
90
|
+
*
|
|
91
|
+
* RuleStore.loadTiered owns the resolution order, so ask it which tier answered instead of walking
|
|
92
|
+
* the filesystem here. Inferring one warns while the configured files are the ones in force, names a
|
|
93
|
+
* fallback document the config turned off, or names a `rules.files` entry after the file it points at.
|
|
94
|
+
*/
|
|
95
|
+
export function checkPiWardenMissing(cwd, config) {
|
|
96
|
+
const { set, tier } = missingStore.loadTiered(cwd, config);
|
|
97
|
+
if (tier === "none")
|
|
98
|
+
return { missing: true };
|
|
99
|
+
// A project that has a rules source of its own is not missing one, empty file or not: /warden init
|
|
100
|
+
// would name the file the project already has, and a pi-warden.md would shadow a bound `rules.files`.
|
|
101
|
+
return tier === "fallback" ? { missing: true, fallbackSource: set?.sources[0] } : { missing: false };
|
|
72
102
|
}
|
package/dist/rules.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { RulesConfig } from "./config.js";
|
|
|
5
5
|
* agent is steered, never held, with the violated rule. Path scoping and sensitive-path notes are code only.
|
|
6
6
|
*
|
|
7
7
|
* Sources, in order: `pi-warden.md` at the project root; else the files in `rules.files`; else, with `rules.fallback`, the
|
|
8
|
-
* first of
|
|
8
|
+
* first of AGENTS.md, CLAUDE.md, README.md as one aggregate rule set. Files are re-read when their mtime or size changes.
|
|
9
9
|
*/
|
|
10
10
|
export interface Rule {
|
|
11
11
|
id: string;
|
|
@@ -27,6 +27,13 @@ export interface RuleSet {
|
|
|
27
27
|
proseOnly?: boolean;
|
|
28
28
|
}
|
|
29
29
|
export declare const RULES_FILE = "pi-warden.md";
|
|
30
|
+
/** Which tier of the resolution order answered; `none` means no source resolved. */
|
|
31
|
+
export type RulesTier = "root" | "configured" | "fallback" | "none";
|
|
32
|
+
/**
|
|
33
|
+
* The parts of `rules` that decide which document is in force. A caller that cannot supply them gets
|
|
34
|
+
* the default fallback behaviour, the same as a project config that omits the keys.
|
|
35
|
+
*/
|
|
36
|
+
export type RulesSourceConfig = Pick<RulesConfig, "files" | "fallback">;
|
|
30
37
|
export declare const FALLBACK_FILES: string[];
|
|
31
38
|
/** TypeSafe answers at most 32 questions per request; one is kept for the edit locator. */
|
|
32
39
|
export declare const MAX_RULES = 31;
|
|
@@ -54,6 +61,11 @@ export declare function projectPath(target: string, cwd: string): string | undef
|
|
|
54
61
|
export declare class RuleStore {
|
|
55
62
|
private readonly cache;
|
|
56
63
|
private read;
|
|
64
|
+
/** Where a rule set came from; the order is the resolution order. */
|
|
65
|
+
loadTiered(cwd: string, config: Pick<RulesConfig, "files" | "fallback" | "maxChars">): {
|
|
66
|
+
set: RuleSet | undefined;
|
|
67
|
+
tier: RulesTier;
|
|
68
|
+
};
|
|
57
69
|
/** The active rule set for a project, or undefined when no source exists. Never throws. */
|
|
58
70
|
load(cwd: string, config: Pick<RulesConfig, "files" | "fallback" | "maxChars">): RuleSet | undefined;
|
|
59
71
|
}
|
package/dist/rules.js
CHANGED
|
@@ -218,26 +218,30 @@ export class RuleStore {
|
|
|
218
218
|
return undefined;
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
|
-
/**
|
|
222
|
-
|
|
221
|
+
/** Where a rule set came from; the order is the resolution order. */
|
|
222
|
+
loadTiered(cwd, config) {
|
|
223
223
|
const root = this.read(resolve(cwd, RULES_FILE));
|
|
224
224
|
if (root !== undefined)
|
|
225
|
-
return ruleSet([RULES_FILE], [root], config.maxChars);
|
|
225
|
+
return { set: ruleSet([RULES_FILE], [root], config.maxChars), tier: "root" };
|
|
226
226
|
const configured = config.files.map(file => ({ file, text: this.read(resolve(cwd, file)) })).filter((entry) => entry.text !== undefined);
|
|
227
227
|
if (configured.length)
|
|
228
|
-
return ruleSet(configured.map(entry => entry.file), configured.map(entry => entry.text), config.maxChars);
|
|
228
|
+
return { set: ruleSet(configured.map(entry => entry.file), configured.map(entry => entry.text), config.maxChars), tier: "configured" };
|
|
229
229
|
if (!config.fallback)
|
|
230
|
-
return undefined;
|
|
230
|
+
return { set: undefined, tier: "none" };
|
|
231
231
|
for (const file of FALLBACK_FILES) {
|
|
232
232
|
const text = this.read(resolve(cwd, file));
|
|
233
233
|
if (text !== undefined && text.trim()) {
|
|
234
234
|
const redacted = redact(text);
|
|
235
235
|
if (isRuleShaped(text))
|
|
236
|
-
return { sources: [file], rules: [], aggregate: condense(redacted, config.maxChars), dropped: 0 };
|
|
237
|
-
return { sources: [file], rules: [], dropped: 0, proseOnly: true };
|
|
236
|
+
return { set: { sources: [file], rules: [], aggregate: condense(redacted, config.maxChars), dropped: 0 }, tier: "fallback" };
|
|
237
|
+
return { set: { sources: [file], rules: [], dropped: 0, proseOnly: true }, tier: "fallback" };
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
|
-
return undefined;
|
|
240
|
+
return { set: undefined, tier: "none" };
|
|
241
|
+
}
|
|
242
|
+
/** The active rule set for a project, or undefined when no source exists. Never throws. */
|
|
243
|
+
load(cwd, config) {
|
|
244
|
+
return this.loadTiered(cwd, config).set;
|
|
241
245
|
}
|
|
242
246
|
}
|
|
243
247
|
function ruleSet(sources, texts, maxChars) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-warden",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.2",
|
|
4
4
|
"description": "Makes the Pi agent follow your project's rules. Jev judges every write against your pi-warden.md and quotes the broken rule back to the agent, names slop, breaks stuck loops, calls out unverified done claims, compresses large tool output, and holds the rare destructive command. Built on pi-typesafe.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|