pi-warden 0.28.4 → 0.35.0

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +158 -1
  2. package/README.md +5 -1
  3. package/dist/audit.d.ts +22 -0
  4. package/dist/audit.js +110 -0
  5. package/dist/config.d.ts +14 -0
  6. package/dist/config.js +12 -1
  7. package/dist/conscience-config.d.ts +21 -0
  8. package/dist/conscience-config.js +82 -0
  9. package/dist/conscience-loader.d.ts +26 -0
  10. package/dist/conscience-loader.js +82 -0
  11. package/dist/conscience-policy.d.ts +24 -0
  12. package/dist/conscience-policy.js +28 -0
  13. package/dist/conscience-privacy.d.ts +4 -0
  14. package/dist/conscience-privacy.js +11 -0
  15. package/dist/discover.d.ts +14 -0
  16. package/dist/discover.js +85 -0
  17. package/dist/extension.d.ts +1 -1
  18. package/dist/extension.js +261 -38
  19. package/dist/extension.js.bak +213 -35
  20. package/dist/guard.d.ts +127 -1
  21. package/dist/guard.js +447 -31
  22. package/dist/holds.d.ts +2 -0
  23. package/dist/holds.js +7 -1
  24. package/dist/index.d.ts +9 -3
  25. package/dist/index.js +5 -2
  26. package/dist/init.d.ts +36 -0
  27. package/dist/init.js +199 -0
  28. package/dist/laya-download.d.ts +40 -0
  29. package/dist/laya-download.js +288 -0
  30. package/dist/laya-judge.d.ts +62 -0
  31. package/dist/laya-judge.js +495 -0
  32. package/dist/learning.d.ts +30 -0
  33. package/dist/learning.js +53 -10
  34. package/dist/redact.js +9 -4
  35. package/dist/rules-file.d.ts +18 -0
  36. package/dist/rules-file.js +72 -0
  37. package/dist/rules.d.ts +8 -0
  38. package/dist/rules.js +54 -3
  39. package/dist/shape.js +10 -2
  40. package/dist/stuck.d.ts +9 -0
  41. package/dist/stuck.js +101 -0
  42. package/dist/tool-recommendation.d.ts +15 -0
  43. package/dist/tool-recommendation.js +31 -0
  44. package/dist/widget.d.ts +3 -1
  45. package/dist/widget.js +16 -13
  46. package/package.json +2 -2
@@ -0,0 +1,28 @@
1
+ import { createHash } from "node:crypto";
2
+ export const SCORE_LEVELS = [
3
+ "No useful contribution to the current request, or conflicts with the supplied constraints.",
4
+ "Related to the subject, but already covered, premature, or unable to resolve the current need.",
5
+ "Addresses a concrete unmet need at the current step without displacing the active workflow.",
6
+ "Supplies a missing prerequisite or directly applicable documented method needed for the current step.",
7
+ ];
8
+ export const SCORE_INSTRUCTIONS = "Assess next-step usefulness of the named candidate against task, recent context, observations, supplied capabilities and constraints. A supplied workflow must not be replaced; another skill must support a new unmet need. Conversation methods may be useful before asking the user a question. An appropriate ordinary tool does not exclude a complementary skill.";
9
+ export const DISPOSITION = {
10
+ type: "choice",
11
+ instructions: "What is the current task disposition? An initial thinking request is not awaiting_user merely because clarification will be needed: its conversation method is an available next step. Awaiting_user requires an actual unanswered question from the assistant.",
12
+ criteria: {
13
+ advance: "A useful next step can be taken now.",
14
+ awaiting_user: "The assistant has already asked for information and must wait for the user.",
15
+ no_gap: "The request is satisfied with no useful missing step.",
16
+ unclear: "There is insufficient evidence to identify a useful next step.",
17
+ },
18
+ };
19
+ export const CONSCIENCE_MODEL = "jev-1.13";
20
+ export const digest = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
21
+ export const QUESTION_POLICY_HASH = digest({ levels: SCORE_LEVELS, instructions: SCORE_INSTRUCTIONS, disposition: DISPOSITION, stateVersion: 1 });
22
+ // No shipping threshold exists until the frozen holdout and follow-through gates pass.
23
+ export const measuredPolicy = undefined;
24
+ export function validPolicy(policy, model) {
25
+ return !!policy && policy.questionHash === QUESTION_POLICY_HASH && policy.model === model && !!policy.evidence
26
+ && Number.isFinite(policy.recommendation) && policy.recommendation >= 0.5 && policy.recommendation <= 0.95
27
+ && Number.isFinite(policy.loading) && policy.loading >= policy.recommendation && policy.loading <= 0.95;
28
+ }
@@ -0,0 +1,4 @@
1
+ /** URLs are not local paths; protect them before scanning free text for filesystem spans. */
2
+ export declare function safeConscienceText(text: string, disclosed?: string): string;
3
+ export declare const safeCapabilityName: (name: string) => boolean;
4
+ export declare const excludedCapability: (name: string, patterns: readonly string[]) => boolean;
@@ -0,0 +1,11 @@
1
+ import { redact } from "./redact.js";
2
+ /** URLs are not local paths; protect them before scanning free text for filesystem spans. */
3
+ export function safeConscienceText(text, disclosed = "") {
4
+ const urls = [];
5
+ const redacted = redact(text);
6
+ const masked = redacted.replace(/https?:\/\/[^\s<>"'`]+/gi, value => `\u0001${urls.push(value) - 1}\u0002`);
7
+ const clean = masked.replace(/(?:file:\/\/[^\s<>"'`]+|[A-Za-z]:[\\/][^\s<>"'`]+|\\\\[^\s<>"'`]+|~\/[^\s<>"'`]+|(?<![\w/:])\/(?:[^\s<>"'`]+))/g, path => disclosed.includes(path) ? path : "[local path]");
8
+ return clean.replace(/\u0001(\d+)\u0002/g, (_, index) => urls[Number(index)] ?? "[URL]");
9
+ }
10
+ export const safeCapabilityName = (name) => /^[A-Za-z0-9_.:\-]{1,128}$/.test(name) && redact(name) === name;
11
+ export const excludedCapability = (name, patterns) => patterns.some(pattern => new RegExp(`^${pattern.split("*").map(part => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*")}$`).test(name));
@@ -0,0 +1,14 @@
1
+ /** Directories to skip during source-file discovery. */
2
+ export declare const SKIP_DIRS: Set<string>;
3
+ export declare const SOURCE_RE: RegExp;
4
+ /**
5
+ * Recursively walk `dir` and collect source-file paths (relative to `dir`).
6
+ * Skips directories in SKIP_DIRS and stops after `max` files.
7
+ */
8
+ export declare function discoverSourceFiles(dir: string, max?: number): string[];
9
+ /**
10
+ * Find project directories inside a workspace. A project is any directory
11
+ * containing a known manifest file. The workspace root itself counts if it
12
+ * has one.
13
+ */
14
+ export declare function findProjects(cwd: string): string[];
@@ -0,0 +1,85 @@
1
+ import { existsSync, readdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { redact } from "./redact.js";
4
+ /** Directories to skip during source-file discovery. */
5
+ export const SKIP_DIRS = new Set([
6
+ ".git", "node_modules", "dist", "build", ".next", ".output",
7
+ "coverage", ".cache", "__pycache__", ".pi-warden", "vendor",
8
+ ".turbo", ".vercel", ".netlify", ".svelte-kit",
9
+ ]);
10
+ export const SOURCE_RE = /\.(ts|tsx|js|jsx|rs|py|go|vue|svelte|rb|php|java|kt|swift|cs|cpp|c|h)$/;
11
+ /**
12
+ * Recursively walk `dir` and collect source-file paths (relative to `dir`).
13
+ * Skips directories in SKIP_DIRS and stops after `max` files.
14
+ */
15
+ export function discoverSourceFiles(dir, max = 200) {
16
+ const files = [];
17
+ function walk(current) {
18
+ if (files.length >= max)
19
+ return;
20
+ let entries;
21
+ try {
22
+ entries = readdirSync(current, { withFileTypes: true });
23
+ }
24
+ catch (err) {
25
+ console.warn(`audit: could not list ${redact(current)}: ${err instanceof Error ? err.message : err}`);
26
+ return;
27
+ }
28
+ for (const entry of entries) {
29
+ if (files.length >= max)
30
+ return;
31
+ if (entry.name.startsWith(".") && entry.name !== ".")
32
+ continue;
33
+ const fullPath = join(current, entry.name);
34
+ if (entry.isDirectory()) {
35
+ if (SKIP_DIRS.has(entry.name))
36
+ continue;
37
+ walk(fullPath);
38
+ }
39
+ else if (entry.isFile() && SOURCE_RE.test(entry.name)) {
40
+ files.push(fullPath.slice(dir.length + 1)); // relative path
41
+ }
42
+ }
43
+ }
44
+ walk(dir);
45
+ return files;
46
+ }
47
+ /**
48
+ * Find project directories inside a workspace. A project is any directory
49
+ * containing a known manifest file. The workspace root itself counts if it
50
+ * has one.
51
+ */
52
+ export function findProjects(cwd) {
53
+ const projects = [];
54
+ const manifests = ["package.json", "Cargo.toml", "pyproject.toml", "go.mod"];
55
+ for (const m of manifests) {
56
+ if (existsSync(join(cwd, m))) {
57
+ projects.push(cwd);
58
+ break;
59
+ }
60
+ }
61
+ let entries;
62
+ try {
63
+ entries = readdirSync(cwd);
64
+ }
65
+ catch (err) {
66
+ console.warn(`audit: could not read workspace ${redact(cwd)}: ${err instanceof Error ? err.message : err}`);
67
+ return projects;
68
+ }
69
+ for (const entry of entries) {
70
+ if (entry === "node_modules" || entry.startsWith("."))
71
+ continue;
72
+ const sub = join(cwd, entry);
73
+ try {
74
+ if (!existsSync(join(sub, "package.json")) && !existsSync(join(sub, "Cargo.toml")) && !existsSync(join(sub, "pyproject.toml")) && !existsSync(join(sub, "go.mod")))
75
+ continue;
76
+ }
77
+ catch (err) {
78
+ console.warn(`audit: could not check ${redact(sub)}: ${err instanceof Error ? err.message : err}`);
79
+ continue;
80
+ }
81
+ if (!projects.includes(sub))
82
+ projects.push(sub);
83
+ }
84
+ return projects;
85
+ }
@@ -2,7 +2,7 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
2
2
  import type { ToolCallRef } from "./action-guard.js";
3
3
  import type { SlopSymptom, Verdict } from "./guard.js";
4
4
  import type { ShapeResult } from "./shape.js";
5
- export declare const disclosure = "With TypeSafe judgments enabled, pi-warden sends to api.typesafe.ai: your latest request and up to eight redacted prior user/assistant text messages for task context, plus a redacted, truncated summary of each guarded bash, write, or edit call before it runs, with the agent's own words from the message that makes the call (its stated plan); for a write or edit in a project with a rules file (pi-warden.md, the configured files, or README/CLAUDE/AGENTS as fallback), a larger redacted sample of the written content with the current file around each edit and the rule text; the last few tool calls and output tails when the agent keeps failing; the agent's final message when it reports completion without running checks; redacted tool-output samples for security and context saving (retention and output format); a redacted sample of an async subagent report that names a failure, a stop, or a question, with your latest request, when warden decides whether that report should wake the agent; and, on the first guarded call after your reply, the redacted summaries of the calls allowed in the previous turn, so Jev can say whether your reply regrets one of them. Compression and duplicate notes store an exact, owner-only copy in a temporary file on this machine; the hold feedback log stores tool names, pattern ids, scores, and outcomes (never commands) in an owner-only file under Pi's agent directory; an owner-only SQLite database under Pi's agent directory stores redacted hold context (plan, summary, reason) for learning and retention (configurable, default 365 days). Requests may incur charges. Secret redaction is best-effort. Results are model judgments, not proof or authorization; offline pattern checks stay active either way.";
5
+ export declare const disclosure = "With TypeSafe judgments enabled, pi-warden sends to api.typesafe.ai: your latest request and up to eight redacted prior user/assistant text messages for task context, plus a redacted, truncated summary of each guarded bash, write, or edit call before it runs, with the agent's own words from the message that makes the call (its stated plan); the resolved active rules file content (pi-warden.md, the configured files, or README/CLAUDE/AGENTS as fallback, token-aware truncated at ~4000 tokens) sent with every action request, including calls where the rules guard is disabled; for a write or edit in a project with a rules file (pi-warden.md, the configured files, or README/CLAUDE/AGENTS as fallback), a larger redacted sample of the written content with the current file around each edit and the rule text; the last few tool calls and output tails when the agent keeps failing; the agent's final message when it reports completion without running checks; redacted tool-output samples for security and context saving (retention and output format); a redacted sample of an async subagent report that names a failure, a stop, or a question, with your latest request, when warden decides whether that report should wake the agent; and, on the first guarded call after your reply, the redacted summaries of the calls allowed in the previous turn, so Jev can say whether your reply regrets one of them. Compression and duplicate notes store an exact, owner-only copy in a temporary file on this machine; the hold feedback log stores tool names, pattern ids, scores, and outcomes (never commands) in an owner-only file under Pi's agent directory; an owner-only SQLite database under Pi's agent directory stores redacted hold context (plan, summary, redacted command preview, outcomes) for held and judged-allowed calls, for learning and retention (configurable, default 365 days). Requests may incur charges. Secret redaction is best-effort. Results are model judgments, not proof or authorization; offline pattern checks stay active either way.";
6
6
  /** Which guard spent the user's attention. The status line reports one count per guard. */
7
7
  export type SteerGuard = "action" | "rules" | "security" | "stuck" | "done" | "prose" | "runaway" | "subagent";
8
8
  /**