@lore-co/cli 0.1.15 → 0.1.16

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/dist/guard.js ADDED
@@ -0,0 +1,170 @@
1
+ import { execFile as execFileCallback } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { GuardApproveResponseSchema, WorkspaceGuardPolicySchema, } from "@lore-co/core";
4
+ import { loreApiRequest } from "./api.js";
5
+ import { repositoryScopeFromGitRoot } from "./repository.js";
6
+ const execFile = promisify(execFileCallback);
7
+ const GUARD_HELP = `lore guard
8
+ Control Context Guard: proactive Lore context at action boundaries.
9
+
10
+ Modes:
11
+ off Lore only responds when asked.
12
+ assist Lore automatically gives agents relevant context before
13
+ important actions. Recommended.
14
+ enforce Lore can require confirmation for actions that conflict with
15
+ required (governed) rules.
16
+
17
+ Usage:
18
+ lore guard status [--json]
19
+ lore guard off [--repo [name]]
20
+ lore guard assist [--repo [name]]
21
+ lore guard enforce [--repo [name]]
22
+ lore guard approve <check-id>
23
+ lore guard clear-override [--repo [name]]
24
+
25
+ Options:
26
+ --repo [name] Apply to one repository instead of the workspace default.
27
+ Omit the value to use the current repository.
28
+ --json Print machine-readable output
29
+
30
+ Notes:
31
+ Workspace-level changes need a token attributed to an owner or admin.
32
+ Rules only gate actions in enforce mode when an owner or admin marks
33
+ them "required" in the dashboard.
34
+
35
+ Examples:
36
+ lore guard status
37
+ lore guard assist
38
+ lore guard enforce --repo
39
+ lore guard off --repo payments-api
40
+ lore guard approve 6f1d3f9a-4b7e-4c1e-9a2b-1c3d5e7f9a0b
41
+ `;
42
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
43
+ async function detectRepoScope() {
44
+ try {
45
+ const { stdout } = await execFile("git", ["rev-parse", "--show-toplevel"], { timeout: 5_000 });
46
+ const root = stdout.trim();
47
+ if (root === "") {
48
+ return undefined;
49
+ }
50
+ return await repositoryScopeFromGitRoot(root);
51
+ }
52
+ catch {
53
+ return undefined;
54
+ }
55
+ }
56
+ /**
57
+ * Resolves the --repo option: absent → undefined, bare flag → detected from
58
+ * the current git repository, valued → that value.
59
+ */
60
+ async function resolveRepoOption(args) {
61
+ const index = args.indexOf("--repo");
62
+ if (index < 0) {
63
+ return undefined;
64
+ }
65
+ const next = args[index + 1];
66
+ if (next !== undefined && !next.startsWith("--")) {
67
+ return next;
68
+ }
69
+ const detected = await detectRepoScope();
70
+ if (detected === undefined) {
71
+ throw new Error("Not inside a git repository. Pass the repository name: --repo <name>");
72
+ }
73
+ return detected;
74
+ }
75
+ function isGuardMode(value) {
76
+ return value === "off" || value === "assist" || value === "enforce";
77
+ }
78
+ function describeMode(mode) {
79
+ switch (mode) {
80
+ case "off":
81
+ return "off — Lore only responds when asked";
82
+ case "assist":
83
+ return "assist — relevant context is surfaced before important actions";
84
+ case "enforce":
85
+ return "enforce — required rules can ask for confirmation";
86
+ }
87
+ }
88
+ function writeStatus(policy, currentRepo) {
89
+ const override = currentRepo === undefined
90
+ ? undefined
91
+ : policy.overrides.find((entry) => entry.repo === currentRepo);
92
+ const effective = override?.mode ?? policy.mode;
93
+ const lines = [
94
+ "Context Guard",
95
+ `Mode: ${describeMode(effective)}`,
96
+ `Scope: ${override === undefined ? "workspace default" : `repo override (${override.repo})`}`,
97
+ `Workspace default: ${policy.mode}`,
98
+ ];
99
+ if (policy.overrides.length > 0) {
100
+ lines.push("Repo overrides:");
101
+ for (const entry of policy.overrides) {
102
+ lines.push(` ${entry.repo}: ${entry.mode}`);
103
+ }
104
+ }
105
+ else {
106
+ lines.push("Repo overrides: none");
107
+ }
108
+ process.stdout.write(`${lines.join("\n")}\n`);
109
+ }
110
+ export async function runGuardCommand(args, config) {
111
+ if (args.length === 0 ||
112
+ args.includes("--help") ||
113
+ args.includes("-h")) {
114
+ process.stdout.write(GUARD_HELP);
115
+ return;
116
+ }
117
+ if (config === null) {
118
+ throw new Error("Lore is not connected. Run: lore connect --token <workspace-token>");
119
+ }
120
+ const subcommand = args[0];
121
+ const rest = args.slice(1);
122
+ if (subcommand === "status") {
123
+ const policy = await loreApiRequest(config, "GET", "/v1/guard/policy", WorkspaceGuardPolicySchema);
124
+ if (args.includes("--json")) {
125
+ process.stdout.write(`${JSON.stringify(policy, null, 2)}\n`);
126
+ return;
127
+ }
128
+ writeStatus(policy, await detectRepoScope());
129
+ return;
130
+ }
131
+ if (subcommand === "mode" || isGuardMode(subcommand)) {
132
+ const mode = subcommand === "mode" ? rest[0] : subcommand;
133
+ if (!isGuardMode(mode)) {
134
+ throw new Error("Usage: lore guard <off|assist|enforce> [--repo [name]]");
135
+ }
136
+ const repo = await resolveRepoOption(subcommand === "mode" ? rest.slice(1) : rest);
137
+ const policy = await loreApiRequest(config, "PATCH", "/v1/guard/policy", WorkspaceGuardPolicySchema, repo === undefined ? { mode } : { setOverride: { repo, mode } });
138
+ process.stdout.write(repo === undefined
139
+ ? `Context Guard workspace default is now ${mode}.\n`
140
+ : `Context Guard for ${repo} is now ${mode}.\n`);
141
+ if (mode === "enforce") {
142
+ process.stdout.write("Enforce gates only rules marked \"required\" in the dashboard.\n");
143
+ }
144
+ writeStatus(policy, repo ?? (await detectRepoScope()));
145
+ return;
146
+ }
147
+ if (subcommand === "clear-override") {
148
+ const repo = await resolveRepoOption(rest.includes("--repo") ? rest : [...rest, "--repo"]);
149
+ if (repo === undefined) {
150
+ throw new Error("Usage: lore guard clear-override [--repo [name]]");
151
+ }
152
+ const policy = await loreApiRequest(config, "PATCH", "/v1/guard/policy", WorkspaceGuardPolicySchema, { removeOverride: { repo } });
153
+ process.stdout.write(`Removed the Context Guard override for ${repo}.\n`);
154
+ writeStatus(policy, await detectRepoScope());
155
+ return;
156
+ }
157
+ if (subcommand === "approve") {
158
+ const checkId = rest[0];
159
+ if (checkId === undefined || !UUID_PATTERN.test(checkId)) {
160
+ throw new Error("Usage: lore guard approve <check-id>");
161
+ }
162
+ const result = await loreApiRequest(config, "POST", "/v1/guard/approvals", GuardApproveResponseSchema, { checkId });
163
+ process.stdout.write(`Approved ${result.approved} rule(s) for this session${result.expiresAt === null
164
+ ? ""
165
+ : ` until ${result.expiresAt.slice(0, 16).replace("T", " ")}`}. Retry the action in your agent.\n`);
166
+ return;
167
+ }
168
+ throw new Error(`Unknown guard subcommand: ${subcommand}\nTry: lore guard --help`);
169
+ }
170
+ //# sourceMappingURL=guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.js","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"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"}
package/dist/runtime.d.ts CHANGED
@@ -62,6 +62,9 @@ interface HookInput {
62
62
  response?: unknown;
63
63
  message?: unknown;
64
64
  text?: unknown;
65
+ tool_name?: unknown;
66
+ tool_input?: unknown;
67
+ command?: unknown;
65
68
  }
66
69
  export interface HookRuntimeOptions {
67
70
  home?: string;
@@ -74,9 +77,15 @@ export interface HookResult {
74
77
  systemMessage?: string;
75
78
  outcome?: "accept";
76
79
  additional_context?: string;
80
+ /** Cursor beforeShellExecution verdict ("ask" is unenforced upstream). */
81
+ permission?: "deny";
82
+ userMessage?: string;
83
+ agentMessage?: string;
77
84
  hookSpecificOutput?: {
78
- hookEventName: "UserPromptSubmit";
79
- additionalContext: string;
85
+ hookEventName: "UserPromptSubmit" | "PreToolUse";
86
+ additionalContext?: string;
87
+ permissionDecision?: "ask";
88
+ permissionDecisionReason?: string;
80
89
  };
81
90
  }
82
91
  export interface LineageMetadata {
@@ -93,6 +102,17 @@ export interface LineageMetadata {
93
102
  export declare function readLineageMetadata(home?: string, environment?: NodeJS.ProcessEnv): Promise<LineageMetadata>;
94
103
  export declare function redactSecrets(text: string): string;
95
104
  export declare function createTurnRequest(input: HookInput, agent: CommandHookAgentName, pending: PendingAssistantMessage, now: Date, lineage?: LineageMetadata): Promise<TurnRequest | null>;
105
+ interface GuardTrigger {
106
+ action: "edit" | "commit" | "deploy" | "command";
107
+ files?: string[];
108
+ command?: string;
109
+ }
110
+ /**
111
+ * Decides whether a tool call is a meaningful action boundary. File edits
112
+ * always qualify; shell commands qualify only when they look high-impact
113
+ * (commit, push, deploy, publish, destructive) to keep the guard quiet.
114
+ */
115
+ export declare function guardTrigger(toolName: string, toolInput: Record<string, unknown>): GuardTrigger | null;
96
116
  export declare function handleHookEvent(value: unknown, agent: CommandHookAgentName, options?: HookRuntimeOptions): Promise<HookResult | undefined>;
97
117
  export declare function runHook(args?: readonly string[]): Promise<void>;
98
118
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAiCA,eAAO,MAAM,wBAAwB,qDAK3B,CAAC;AACX,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC7E,iFAAiF;AACjF,MAAM,MAAM,SAAS,GAAG,oBAAoB,CAAC;AAgB7C,UAAU,uBAAuB;IAC/B,KAAK,EAAE,oBAAoB,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,UAAU,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,oBAAoB,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,WAAW,EAAE;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE;QACN,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,aAAa,EAAE;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE;QACT,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AA2BD,UAAU,SAAS;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACjC;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE;QACnB,aAAa,EAAE,kBAAkB,GAAG,YAAY,CAAC;QACjD,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,kBAAkB,CAAC,EAAE,KAAK,CAAC;QAC3B,wBAAwB,CAAC,EAAE,MAAM,CAAC;KACnC,CAAC;CACH;AA2DD,MAAM,WAAW,eAAe;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,CAAC,EAAE,MAAM,EACb,WAAW,GAAE,MAAM,CAAC,UAAwB,GAC3C,OAAO,CAAC,eAAe,CAAC,CAyB1B;AAmJD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgFlD;AA+PD,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,oBAAoB,EAC3B,OAAO,EAAE,uBAAuB,EAChC,GAAG,EAAE,IAAI,EACT,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CA+D7B;AAufD,UAAU,YAAY;IACpB,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACjD,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,YAAY,GAAG,IAAI,CAwBrB;AAmVD,wBAAsB,eAAe,CACnC,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,oBAAoB,EAC3B,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAyJjC;AAoBD,wBAAsB,OAAO,CAC3B,IAAI,GAAE,SAAS,MAAM,EAA0B,GAC9C,OAAO,CAAC,IAAI,CAAC,CAaf"}
package/dist/runtime.js CHANGED
@@ -9,7 +9,7 @@ import { promisify } from "node:util";
9
9
  import { boundedUtf8Text, repositoryScopeFromGitRoot, } from "./repository.js";
10
10
  const RUNTIME_VERSION = typeof __LORE_VERSION__ === "string" && __LORE_VERSION__ !== ""
11
11
  ? __LORE_VERSION__
12
- : "0.1.15";
12
+ : "0.1.16";
13
13
  const IS_STANDALONE_RUNTIME = typeof __LORE_STANDALONE__ === "boolean" && __LORE_STANDALONE__;
14
14
  export const COMMAND_HOOK_AGENT_NAMES = [
15
15
  "codex",
@@ -153,10 +153,18 @@ function normalizeHookInput(input, agent, environment) {
153
153
  ? "AssistantResponse"
154
154
  : rawEventName === "sessionEnd"
155
155
  ? "SessionEnd"
156
- : rawEventName;
156
+ : rawEventName === "beforeShellExecution"
157
+ ? "PreToolUse"
158
+ : rawEventName;
157
159
  return {
158
160
  input: {
159
161
  ...input,
162
+ ...(eventName === "PreToolUse"
163
+ ? {
164
+ tool_name: "Bash",
165
+ tool_input: { command: input.command },
166
+ }
167
+ : {}),
160
168
  ...(sessionId === undefined ? {} : { session_id: sessionId }),
161
169
  ...(cwd === undefined ? {} : { cwd }),
162
170
  ...(stringField(input.prompt_id) !== undefined
@@ -863,6 +871,307 @@ function receiptMessage(config, agent, delivery) {
863
871
  }
864
872
  return boundedContext(parts.join(" · "));
865
873
  }
874
+ // ---------------------------------------------------------------------------
875
+ // Context Guard: proactive checks at tool boundaries (PreToolUse and Cursor
876
+ // beforeShellExecution). Fail-open by design — a slow or unreachable hub must
877
+ // never stall an agent's tool call.
878
+ // ---------------------------------------------------------------------------
879
+ /** How long a locally cached "guard is off" verdict suppresses network calls. */
880
+ const GUARD_MODE_TTL_MS = 5 * 60_000;
881
+ /** How long an already-surfaced action key stays quiet in this session. */
882
+ const GUARD_KEY_COOLDOWN_MS = 15 * 60_000;
883
+ const GUARD_MAX_CACHED_KEYS = 200;
884
+ const GUARD_TIMEOUT_MS = 4_000;
885
+ const GUARD_EDIT_TOOLS = new Set([
886
+ "Edit",
887
+ "Write",
888
+ "MultiEdit",
889
+ "NotebookEdit",
890
+ "ApplyPatch",
891
+ "apply_patch",
892
+ "edit",
893
+ "write",
894
+ "patch",
895
+ "str_replace_editor",
896
+ ]);
897
+ const GUARD_SHELL_TOOLS = new Set(["Bash", "bash", "Shell", "shell"]);
898
+ const GUARD_COMMIT_COMMAND = /\bgit\s+(?:commit|push)\b/iu;
899
+ const GUARD_DEPLOY_COMMAND = /\b(?:terraform\s+(?:apply|destroy)|pulumi\s+up|kubectl\s+(?:apply|delete|rollout)|helm\s+(?:install|upgrade|uninstall)|docker\s+push|fly(?:ctl)?\s+deploy|vercel(?:\s+deploy|\s+--prod)|railway\s+up|serverless\s+deploy|sls\s+deploy|cdk\s+deploy|eb\s+deploy|gcloud\s+(?:app|run|functions)\s+deploy|az\s+webapp\s+(?:up|deploy)|(?:npm|pnpm|yarn)\s+publish|prisma\s+migrate\s+deploy|drizzle-kit\s+(?:push|migrate))\b/iu;
900
+ const GUARD_DESTRUCTIVE_COMMAND = /\b(?:rm\s+(?:-[a-z]*\s+)*-[a-z]*r[a-z]*f|drop\s+(?:table|database)|truncate\s+table)\b/iu;
901
+ /**
902
+ * Decides whether a tool call is a meaningful action boundary. File edits
903
+ * always qualify; shell commands qualify only when they look high-impact
904
+ * (commit, push, deploy, publish, destructive) to keep the guard quiet.
905
+ */
906
+ export function guardTrigger(toolName, toolInput) {
907
+ if (GUARD_EDIT_TOOLS.has(toolName)) {
908
+ const files = [toolInput.file_path, toolInput.notebook_path, toolInput.path]
909
+ .map((value) => stringField(value))
910
+ .filter((value) => value !== undefined);
911
+ return { action: "edit", ...(files.length === 0 ? {} : { files }) };
912
+ }
913
+ if (GUARD_SHELL_TOOLS.has(toolName)) {
914
+ const command = stringField(toolInput.command);
915
+ if (command === undefined) {
916
+ return null;
917
+ }
918
+ if (GUARD_COMMIT_COMMAND.test(command)) {
919
+ return { action: "commit", command };
920
+ }
921
+ if (GUARD_DEPLOY_COMMAND.test(command)) {
922
+ return { action: "deploy", command };
923
+ }
924
+ if (GUARD_DESTRUCTIVE_COMMAND.test(command)) {
925
+ return { action: "command", command };
926
+ }
927
+ return null;
928
+ }
929
+ return null;
930
+ }
931
+ function guardStatePath(agent, sessionId, home) {
932
+ return resolve(loreDirectory(home), "state", "guard", `${sha256(`${agent}\0${sessionId}`)}.json`);
933
+ }
934
+ async function readGuardState(agent, sessionId, home) {
935
+ try {
936
+ const parsed = JSON.parse(await readFile(guardStatePath(agent, sessionId, home), "utf8"));
937
+ if (isObject(parsed)) {
938
+ return {
939
+ ...(typeof parsed.mode === "string" ? { mode: parsed.mode } : {}),
940
+ ...(typeof parsed.modeCheckedAt === "string"
941
+ ? { modeCheckedAt: parsed.modeCheckedAt }
942
+ : {}),
943
+ keys: isObject(parsed.keys)
944
+ ? Object.fromEntries(Object.entries(parsed.keys).filter((entry) => typeof entry[1] === "string"))
945
+ : {},
946
+ };
947
+ }
948
+ }
949
+ catch {
950
+ // First guarded action for this session.
951
+ }
952
+ return { keys: {} };
953
+ }
954
+ async function writeGuardState(agent, sessionId, state, home) {
955
+ const entries = Object.entries(state.keys)
956
+ .sort((a, b) => (a[1] < b[1] ? 1 : -1))
957
+ .slice(0, GUARD_MAX_CACHED_KEYS);
958
+ try {
959
+ await atomicWriteJson(guardStatePath(agent, sessionId, home), {
960
+ ...state,
961
+ keys: Object.fromEntries(entries),
962
+ });
963
+ }
964
+ catch {
965
+ // Cache misses only cost an extra network round trip.
966
+ }
967
+ }
968
+ function guardCheckUrl(apiUrl) {
969
+ return `${apiUrl.replace(/\/+$/u, "")}/v1/guard/check`;
970
+ }
971
+ function guardResultFromResponse(value) {
972
+ if (!isObject(value) || typeof value.mode !== "string") {
973
+ return null;
974
+ }
975
+ const items = Array.isArray(value.items)
976
+ ? value.items.flatMap((item) => {
977
+ if (!isObject(item) || typeof item.content !== "string") {
978
+ return [];
979
+ }
980
+ const scope = isObject(item.scope) ? item.scope : {};
981
+ const source = isObject(item.source) ? item.source : {};
982
+ return [
983
+ {
984
+ content: redactSecrets(item.content),
985
+ enforcement: typeof item.enforcement === "string"
986
+ ? item.enforcement
987
+ : "advisory",
988
+ ...(typeof scope.repo === "string" ? { repo: scope.repo } : {}),
989
+ ...(typeof scope.organization === "string"
990
+ ? { organization: scope.organization }
991
+ : {}),
992
+ ...(typeof source.agent === "string"
993
+ ? { sourceAgent: source.agent }
994
+ : {}),
995
+ },
996
+ ];
997
+ })
998
+ : [];
999
+ const conflictSummaries = Array.isArray(value.conflicts)
1000
+ ? value.conflicts.flatMap((conflict) => isObject(conflict) && typeof conflict.summary === "string"
1001
+ ? [redactSecrets(conflict.summary)]
1002
+ : [])
1003
+ : [];
1004
+ const confirm = isObject(value.confirm) ? value.confirm : {};
1005
+ return {
1006
+ ...(typeof value.checkId === "string" ? { checkId: value.checkId } : {}),
1007
+ mode: value.mode,
1008
+ requiresConfirmation: value.requiresConfirmation === true,
1009
+ ...(typeof confirm.command === "string"
1010
+ ? { confirmCommand: confirm.command }
1011
+ : {}),
1012
+ items,
1013
+ conflictSummaries,
1014
+ };
1015
+ }
1016
+ function guardItemSource(item) {
1017
+ const scope = item.repo !== undefined
1018
+ ? `${item.repo} repo`
1019
+ : item.organization !== undefined
1020
+ ? "organization rule"
1021
+ : "workspace knowledge";
1022
+ return item.sourceAgent === undefined
1023
+ ? scope
1024
+ : `${scope} · ${item.sourceAgent} session`;
1025
+ }
1026
+ function guardAssistContext(result) {
1027
+ const lines = ["Relevant Lore context:"];
1028
+ for (const item of result.items) {
1029
+ lines.push(`- ${item.content}`);
1030
+ }
1031
+ lines.push("", "Sources:");
1032
+ for (const item of result.items) {
1033
+ lines.push(`- ${guardItemSource(item)}`);
1034
+ }
1035
+ if (result.conflictSummaries.length > 0) {
1036
+ lines.push("", "Conflicting Lore context (do not silently pick a winner):");
1037
+ for (const summary of result.conflictSummaries) {
1038
+ lines.push(`- ${summary}`);
1039
+ }
1040
+ }
1041
+ return boundedContext(lines.join("\n"));
1042
+ }
1043
+ function guardConfirmationReason(result) {
1044
+ const required = result.items.filter((item) => item.enforcement === "required");
1045
+ const shown = required.length > 0 ? required : result.items;
1046
+ const lines = [
1047
+ "Lore Guard: this action conflicts with a required rule.",
1048
+ ...shown.map((item) => `- ${item.content} (${guardItemSource(item)})`),
1049
+ ];
1050
+ lines.push(result.confirmCommand === undefined
1051
+ ? "Ask the user to confirm before proceeding."
1052
+ : `Approve here to proceed, or the user can run: ${result.confirmCommand}`);
1053
+ return boundedContext(lines.join("\n"));
1054
+ }
1055
+ async function postGuardCheck(config, request, fetchImplementation) {
1056
+ const response = await fetchImplementation(guardCheckUrl(config.apiUrl), {
1057
+ method: "POST",
1058
+ headers: {
1059
+ authorization: `Bearer ${config.token}`,
1060
+ "content-type": "application/json",
1061
+ "user-agent": `lore-cli/${RUNTIME_VERSION}`,
1062
+ },
1063
+ body: JSON.stringify(request),
1064
+ signal: AbortSignal.timeout(Math.min(config.timeoutMs ?? GUARD_TIMEOUT_MS, GUARD_TIMEOUT_MS)),
1065
+ });
1066
+ if (!response.ok) {
1067
+ throw new Error(`Lore guard check failed with HTTP ${response.status}`);
1068
+ }
1069
+ return guardResultFromResponse(JSON.parse(await response.text()));
1070
+ }
1071
+ async function handleGuardEvent(input, agent, config, sessionId, options, now) {
1072
+ if (agent === "polytoken") {
1073
+ // Polytoken has no tool boundary; its guard runs at prompt injection.
1074
+ return undefined;
1075
+ }
1076
+ const toolName = stringField(input.tool_name);
1077
+ if (toolName === undefined) {
1078
+ return undefined;
1079
+ }
1080
+ const trigger = guardTrigger(toolName, isObject(input.tool_input) ? input.tool_input : {});
1081
+ if (trigger === null) {
1082
+ return undefined;
1083
+ }
1084
+ const state = await readGuardState(agent, sessionId, options.home);
1085
+ if (state.mode === "off" &&
1086
+ state.modeCheckedAt !== undefined &&
1087
+ now.getTime() - Date.parse(state.modeCheckedAt) < GUARD_MODE_TTL_MS) {
1088
+ return undefined;
1089
+ }
1090
+ const cwd = stringField(input.cwd);
1091
+ const scope = await repositoryScope(cwd);
1092
+ const root = cwd === undefined ? null : await repositoryRoot(cwd);
1093
+ const files = (trigger.files ?? [])
1094
+ .map((file) => {
1095
+ const absolute = resolve(cwd ?? ".", file);
1096
+ if (root === null) {
1097
+ return normalizedRepositoryPath(file);
1098
+ }
1099
+ const relativePath = relative(root, absolute);
1100
+ return relativePath.startsWith("..")
1101
+ ? normalizedRepositoryPath(file)
1102
+ : normalizedRepositoryPath(relativePath);
1103
+ })
1104
+ .filter((file) => file !== "")
1105
+ .slice(0, 100);
1106
+ const key = sha256([
1107
+ "guard",
1108
+ trigger.action,
1109
+ ...files,
1110
+ trigger.command === undefined ? "" : redactSecrets(trigger.command),
1111
+ ].join("\0"));
1112
+ const cachedAt = state.keys[key];
1113
+ if (cachedAt !== undefined &&
1114
+ now.getTime() - Date.parse(cachedAt) < GUARD_KEY_COOLDOWN_MS) {
1115
+ return undefined;
1116
+ }
1117
+ const result = await postGuardCheck(config, {
1118
+ connector: "lore-cli",
1119
+ agent,
1120
+ sessionId,
1121
+ action: trigger.action,
1122
+ tool: toolName,
1123
+ ...(scope?.repo === undefined ? {} : { repo: scope.repo }),
1124
+ ...(scope?.path === undefined ? {} : { path: scope.path }),
1125
+ ...(files.length === 0 ? {} : { files }),
1126
+ ...(trigger.command === undefined
1127
+ ? {}
1128
+ : { command: redactSecrets(trigger.command).slice(0, 10_000) }),
1129
+ }, options.fetch ?? globalThis.fetch);
1130
+ if (result === null) {
1131
+ return undefined;
1132
+ }
1133
+ state.mode = result.mode;
1134
+ state.modeCheckedAt = now.toISOString();
1135
+ if (!result.requiresConfirmation) {
1136
+ state.keys[key] = now.toISOString();
1137
+ }
1138
+ await writeGuardState(agent, sessionId, state, options.home);
1139
+ if (result.mode === "off") {
1140
+ return undefined;
1141
+ }
1142
+ if (result.requiresConfirmation) {
1143
+ const reason = guardConfirmationReason(result);
1144
+ if (agent === "cursor") {
1145
+ // Cursor's "ask" verdict is unenforced upstream; deny is the only
1146
+ // reliable gate. The message carries the approve-and-retry path.
1147
+ return {
1148
+ permission: "deny",
1149
+ userMessage: reason,
1150
+ agentMessage: reason,
1151
+ };
1152
+ }
1153
+ return {
1154
+ hookSpecificOutput: {
1155
+ hookEventName: "PreToolUse",
1156
+ permissionDecision: "ask",
1157
+ permissionDecisionReason: reason,
1158
+ },
1159
+ };
1160
+ }
1161
+ if (result.items.length === 0 && result.conflictSummaries.length === 0) {
1162
+ return undefined;
1163
+ }
1164
+ if (agent === "cursor") {
1165
+ // Cursor cannot inject agent context at the shell boundary on allow.
1166
+ return undefined;
1167
+ }
1168
+ return {
1169
+ hookSpecificOutput: {
1170
+ hookEventName: "PreToolUse",
1171
+ additionalContext: guardAssistContext(result),
1172
+ },
1173
+ };
1174
+ }
866
1175
  export async function handleHookEvent(value, agent, options = {}) {
867
1176
  if (!isObject(value)) {
868
1177
  return undefined;
@@ -889,6 +1198,15 @@ export async function handleHookEvent(value, agent, options = {}) {
889
1198
  // invocation can still capture a correction.
890
1199
  return undefined;
891
1200
  }
1201
+ if (eventName === "PreToolUse") {
1202
+ try {
1203
+ return await handleGuardEvent(input, agent, config, sessionId, options, now);
1204
+ }
1205
+ catch {
1206
+ // Guard checks fail open: never stall or break a tool call.
1207
+ return undefined;
1208
+ }
1209
+ }
892
1210
  if (eventName !== "UserPromptSubmit") {
893
1211
  return undefined;
894
1212
  }