@lore-co/cli 0.2.26 → 0.2.28

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 (223) hide show
  1. package/README.md +67 -20
  2. package/dist/agents.d.ts +9 -0
  3. package/dist/agents.d.ts.map +1 -1
  4. package/dist/agents.js +19 -6
  5. package/dist/agents.js.map +1 -1
  6. package/dist/api.d.ts +2 -1
  7. package/dist/api.d.ts.map +1 -1
  8. package/dist/api.js +15 -2
  9. package/dist/api.js.map +1 -1
  10. package/dist/approach-guidance.d.ts +8 -1
  11. package/dist/approach-guidance.d.ts.map +1 -1
  12. package/dist/approach-guidance.js +13 -3
  13. package/dist/approach-guidance.js.map +1 -1
  14. package/dist/approach.d.ts.map +1 -1
  15. package/dist/approach.js +2 -2
  16. package/dist/approach.js.map +1 -1
  17. package/dist/ask.d.ts.map +1 -1
  18. package/dist/ask.js +2 -2
  19. package/dist/ask.js.map +1 -1
  20. package/dist/capture-repair.d.ts +11 -0
  21. package/dist/capture-repair.d.ts.map +1 -0
  22. package/dist/capture-repair.js +94 -0
  23. package/dist/capture-repair.js.map +1 -0
  24. package/dist/capture-scope.d.ts.map +1 -1
  25. package/dist/capture-scope.js +2 -1
  26. package/dist/capture-scope.js.map +1 -1
  27. package/dist/chat.d.ts.map +1 -1
  28. package/dist/chat.js +2 -2
  29. package/dist/chat.js.map +1 -1
  30. package/dist/cli.d.ts +114 -6
  31. package/dist/cli.d.ts.map +1 -1
  32. package/dist/cli.js +628 -127
  33. package/dist/cli.js.map +1 -1
  34. package/dist/codex-hook-approval.d.ts.map +1 -1
  35. package/dist/codex-hook-approval.js +9 -2
  36. package/dist/codex-hook-approval.js.map +1 -1
  37. package/dist/command-help.d.ts +3 -2
  38. package/dist/command-help.d.ts.map +1 -1
  39. package/dist/command-help.js +11 -5
  40. package/dist/command-help.js.map +1 -1
  41. package/dist/config-command.js +2 -2
  42. package/dist/config-command.js.map +1 -1
  43. package/dist/connect-project.d.ts.map +1 -1
  44. package/dist/connect-project.js +2 -1
  45. package/dist/connect-project.js.map +1 -1
  46. package/dist/console-keys.d.ts +11 -0
  47. package/dist/console-keys.d.ts.map +1 -0
  48. package/dist/console-keys.js +24 -0
  49. package/dist/console-keys.js.map +1 -0
  50. package/dist/cursor-mcp.d.ts.map +1 -1
  51. package/dist/cursor-mcp.js +4 -0
  52. package/dist/cursor-mcp.js.map +1 -1
  53. package/dist/demo-process.js +3 -3
  54. package/dist/demo-process.js.map +1 -1
  55. package/dist/devin-capture.d.ts.map +1 -1
  56. package/dist/devin-capture.js +2 -2
  57. package/dist/devin-capture.js.map +1 -1
  58. package/dist/devin-cli.d.ts.map +1 -1
  59. package/dist/devin-cli.js +23 -13
  60. package/dist/devin-cli.js.map +1 -1
  61. package/dist/devin-client.d.ts +3 -0
  62. package/dist/devin-client.d.ts.map +1 -1
  63. package/dist/devin-client.js +5 -0
  64. package/dist/devin-client.js.map +1 -1
  65. package/dist/devin.d.ts.map +1 -1
  66. package/dist/devin.js +29 -9
  67. package/dist/devin.js.map +1 -1
  68. package/dist/drafts.d.ts +8 -0
  69. package/dist/drafts.d.ts.map +1 -0
  70. package/dist/drafts.js +173 -0
  71. package/dist/drafts.js.map +1 -0
  72. package/dist/generated-assets.d.ts +9 -9
  73. package/dist/generated-assets.d.ts.map +1 -1
  74. package/dist/generated-assets.js +9 -9
  75. package/dist/generated-assets.js.map +1 -1
  76. package/dist/github.d.ts.map +1 -1
  77. package/dist/github.js +46 -14
  78. package/dist/github.js.map +1 -1
  79. package/dist/guard.d.ts +1 -1
  80. package/dist/guard.d.ts.map +1 -1
  81. package/dist/guard.js +42 -14
  82. package/dist/guard.js.map +1 -1
  83. package/dist/handoff.js +2 -2
  84. package/dist/handoff.js.map +1 -1
  85. package/dist/host.js +4 -4
  86. package/dist/host.js.map +1 -1
  87. package/dist/image-capture.d.ts.map +1 -1
  88. package/dist/image-capture.js +5 -4
  89. package/dist/image-capture.js.map +1 -1
  90. package/dist/learnings.d.ts.map +1 -1
  91. package/dist/learnings.js +3 -3
  92. package/dist/learnings.js.map +1 -1
  93. package/dist/live.d.ts +7 -0
  94. package/dist/live.d.ts.map +1 -1
  95. package/dist/live.js +11 -6
  96. package/dist/live.js.map +1 -1
  97. package/dist/lore-agent.d.ts +6 -1
  98. package/dist/lore-agent.d.ts.map +1 -1
  99. package/dist/lore-agent.js +29 -7
  100. package/dist/lore-agent.js.map +1 -1
  101. package/dist/mcp-config.d.ts +7 -0
  102. package/dist/mcp-config.d.ts.map +1 -1
  103. package/dist/mcp-config.js +19 -3
  104. package/dist/mcp-config.js.map +1 -1
  105. package/dist/npm-installation.js +2 -2
  106. package/dist/npm-installation.js.map +1 -1
  107. package/dist/owned-files.d.ts +14 -4
  108. package/dist/owned-files.d.ts.map +1 -1
  109. package/dist/owned-files.js +24 -9
  110. package/dist/owned-files.js.map +1 -1
  111. package/dist/platform.d.ts +26 -3
  112. package/dist/platform.d.ts.map +1 -1
  113. package/dist/platform.js +87 -20
  114. package/dist/platform.js.map +1 -1
  115. package/dist/pty.d.ts +2 -1
  116. package/dist/pty.d.ts.map +1 -1
  117. package/dist/pty.js +20 -7
  118. package/dist/pty.js.map +1 -1
  119. package/dist/recovery-cloud-receipt.d.ts.map +1 -1
  120. package/dist/recovery-cloud-receipt.js +2 -1
  121. package/dist/recovery-cloud-receipt.js.map +1 -1
  122. package/dist/recovery-cloud-workspace.d.ts.map +1 -1
  123. package/dist/recovery-cloud-workspace.js +2 -1
  124. package/dist/recovery-cloud-workspace.js.map +1 -1
  125. package/dist/recovery-cloud.d.ts.map +1 -1
  126. package/dist/recovery-cloud.js +8 -9
  127. package/dist/recovery-cloud.js.map +1 -1
  128. package/dist/recovery-copilot.d.ts.map +1 -1
  129. package/dist/recovery-copilot.js +3 -2
  130. package/dist/recovery-copilot.js.map +1 -1
  131. package/dist/recovery-desktop.d.ts.map +1 -1
  132. package/dist/recovery-desktop.js +3 -2
  133. package/dist/recovery-desktop.js.map +1 -1
  134. package/dist/recovery-hooks.js +2 -2
  135. package/dist/recovery-hooks.js.map +1 -1
  136. package/dist/recovery-hosts.d.ts.map +1 -1
  137. package/dist/recovery-hosts.js +53 -6
  138. package/dist/recovery-hosts.js.map +1 -1
  139. package/dist/recovery-polytoken.d.ts +2 -2
  140. package/dist/recovery-polytoken.d.ts.map +1 -1
  141. package/dist/recovery-polytoken.js +13 -8
  142. package/dist/recovery-polytoken.js.map +1 -1
  143. package/dist/recovery-remote.js +3 -3
  144. package/dist/recovery-remote.js.map +1 -1
  145. package/dist/recovery-state.d.ts +22 -1
  146. package/dist/recovery-state.d.ts.map +1 -1
  147. package/dist/recovery-state.js +167 -31
  148. package/dist/recovery-state.js.map +1 -1
  149. package/dist/recovery-transfer.d.ts.map +1 -1
  150. package/dist/recovery-transfer.js +8 -4
  151. package/dist/recovery-transfer.js.map +1 -1
  152. package/dist/recovery.d.ts +5 -2
  153. package/dist/recovery.d.ts.map +1 -1
  154. package/dist/recovery.js +37 -14
  155. package/dist/recovery.js.map +1 -1
  156. package/dist/reliability-store.d.ts +13 -0
  157. package/dist/reliability-store.d.ts.map +1 -1
  158. package/dist/reliability-store.js +110 -8
  159. package/dist/reliability-store.js.map +1 -1
  160. package/dist/remember.d.ts.map +1 -1
  161. package/dist/remember.js +32 -3
  162. package/dist/remember.js.map +1 -1
  163. package/dist/retained-context.d.ts.map +1 -1
  164. package/dist/retained-context.js +3 -2
  165. package/dist/retained-context.js.map +1 -1
  166. package/dist/retrieval-task.d.ts +30 -0
  167. package/dist/retrieval-task.d.ts.map +1 -1
  168. package/dist/retrieval-task.js +98 -2
  169. package/dist/retrieval-task.js.map +1 -1
  170. package/dist/review.d.ts +1 -1
  171. package/dist/review.d.ts.map +1 -1
  172. package/dist/review.js +12 -7
  173. package/dist/review.js.map +1 -1
  174. package/dist/runtime.d.ts +4 -1
  175. package/dist/runtime.d.ts.map +1 -1
  176. package/dist/runtime.js +147 -97
  177. package/dist/runtime.js.map +1 -1
  178. package/dist/self-host.d.ts +1 -1
  179. package/dist/self-host.d.ts.map +1 -1
  180. package/dist/self-host.js +105 -23
  181. package/dist/self-host.js.map +1 -1
  182. package/dist/share.d.ts.map +1 -1
  183. package/dist/share.js +2 -1
  184. package/dist/share.js.map +1 -1
  185. package/dist/signed-snapshot.d.ts +7 -0
  186. package/dist/signed-snapshot.d.ts.map +1 -1
  187. package/dist/signed-snapshot.js +27 -0
  188. package/dist/signed-snapshot.js.map +1 -1
  189. package/dist/skill-command.d.ts +5 -2
  190. package/dist/skill-command.d.ts.map +1 -1
  191. package/dist/skill-command.js +73 -10
  192. package/dist/skill-command.js.map +1 -1
  193. package/dist/skill-guidance.js +1 -1
  194. package/dist/skill-guidance.js.map +1 -1
  195. package/dist/skills.d.ts.map +1 -1
  196. package/dist/skills.js +9 -7
  197. package/dist/skills.js.map +1 -1
  198. package/dist/status-health.d.ts +26 -0
  199. package/dist/status-health.d.ts.map +1 -0
  200. package/dist/status-health.js +49 -0
  201. package/dist/status-health.js.map +1 -0
  202. package/dist/telemetry.d.ts.map +1 -1
  203. package/dist/telemetry.js +2 -2
  204. package/dist/telemetry.js.map +1 -1
  205. package/dist/terminal-output.d.ts +76 -0
  206. package/dist/terminal-output.d.ts.map +1 -0
  207. package/dist/terminal-output.js +151 -0
  208. package/dist/terminal-output.js.map +1 -0
  209. package/dist/uninstall.d.ts.map +1 -1
  210. package/dist/uninstall.js +3 -1
  211. package/dist/uninstall.js.map +1 -1
  212. package/dist/update.d.ts +3 -0
  213. package/dist/update.d.ts.map +1 -1
  214. package/dist/update.js +40 -14
  215. package/dist/update.js.map +1 -1
  216. package/dist/windows-permissions.d.ts +6 -0
  217. package/dist/windows-permissions.d.ts.map +1 -1
  218. package/dist/windows-permissions.js +22 -2
  219. package/dist/windows-permissions.js.map +1 -1
  220. package/dist/windows-pty.d.ts.map +1 -1
  221. package/dist/windows-pty.js +4 -3
  222. package/dist/windows-pty.js.map +1 -1
  223. package/package.json +4 -4
package/dist/runtime.js CHANGED
@@ -2,10 +2,13 @@ import { redactSecrets } from "./runtime-redaction.js";
2
2
  export { redactSecrets } from "./runtime-redaction.js";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import { CAPTURE_CONFLICT_MESSAGES } from "@lore-co/core/capture-conflict";
5
- import { approachGuidance } from "./approach-guidance.js";
5
+ import { guardItemLine } from "@lore-co/core/learning-line";
6
+ import { fitSkillContext } from "@lore-co/core/skill-context";
7
+ import { systemCommand } from "@lore-co/core/system-command";
8
+ import { approachGuidance, approachWorkflowRequested } from "./approach-guidance.js";
6
9
  import { skillGuidance } from "./skill-guidance.js";
7
10
  import { retainedContextTexts, retainedReceiptIds, rememberDeliveredContext } from "./retained-context.js";
8
- import { clearRetrievalTask, resolveRetrievalTask } from "./retrieval-task.js";
11
+ import { clearRetrievalTask, forgetUncapturedPromptText, resolveRetrievalTask, unsavedRetrievalTask } from "./retrieval-task.js";
9
12
  import { access, chmod, mkdir, readFile, stat, } from "node:fs/promises";
10
13
  import { constants as fsConstants, realpathSync } from "node:fs";
11
14
  import { execFile as execFileCallback } from "node:child_process";
@@ -16,12 +19,12 @@ import { promisify } from "node:util";
16
19
  import { boundedUtf8Text, defaultGitPushBranches, repositoryRemoteScope, repositoryScopeFromGitRoot, } from "./repository.js";
17
20
  import { ReliabilityStore, ReliabilityStoreError, atomicWriteJson as durableAtomicWriteJson, classifyRetryFailure, createIntegrationInvocationAttempt, durableUnlink, recordLocalGuardMetric, recordLocalRetrievalMetric, } from "./reliability-store.js";
18
21
  import { writeInvocationAttemptBounded, writeInvocationCompletionBounded, } from "./invocation-health-writer.js";
19
- import { SignedSnapshotError, verifySignedSnapshot, } from "./signed-snapshot.js";
22
+ import { SignedSnapshotError, referencesSourceConversations, verifySignedSnapshot, } from "./signed-snapshot.js";
20
23
  import { CONTEXT_CACHE_MAX_STALE_MS, isContextTransportFailure, selectCachedContext, } from "./context-fallback.js";
21
24
  import { RUNTIME_VERSION } from "./runtime-version.js";
22
- import { checkLoreAccess, checkDirectory, checkWorkspaceRepository, readLocalConfig, resolveLocalConnection, bindLocalSession } from "./directory-policy.js";
25
+ import { checkLoreAccess, checkDirectory, checkWorkspaceRepository, readLocalConfig, resolveLocalConnection, bindLocalSession, loreDisabled, RECAP_REMINDER } from "./directory-policy.js";
23
26
  import { capturePendingImages, enqueueEventImages, flushImageAttachment, hostMessageImages, rememberImageCapture } from "./image-capture.js";
24
- import { guardRepositoryOverride, guardRuleConflict, guardToolTrigger, supportedGuardRule, guardEmergencyCommand, emergencyOverrideApplies } from "@lore-co/core/guard-evaluation";
27
+ import { guardRepositoryOverride, guardRuleConflict, guardRuleContent, guardToolTrigger, isGuardRule, supportedGuardRule, guardEmergencyCommand, emergencyOverrideApplies } from "@lore-co/core/guard-evaluation";
25
28
  const IS_STANDALONE_RUNTIME = typeof __LORE_STANDALONE__ === "boolean" && __LORE_STANDALONE__;
26
29
  export const COMMAND_HOOK_AGENT_NAMES = [
27
30
  "codex",
@@ -368,6 +371,7 @@ async function readRuntimeConfig(home, cwd) {
368
371
  agents,
369
372
  ...(timeoutMs === undefined ? {} : { timeoutMs }),
370
373
  capture: saved?.capture !== "off",
374
+ ...(saved?.recapReminder === "off" ? { recapReminder: false } : {}),
371
375
  };
372
376
  }
373
377
  catch {
@@ -479,7 +483,7 @@ async function repositoryScope(cwd, repository = repositoryRemoteScope) {
479
483
  }
480
484
  async function gitOutput(root, args) {
481
485
  try {
482
- const result = await execFile("git", ["-C", root, ...args], {
486
+ const result = await execFile(systemCommand("git", { exclude: [root] }), ["-C", root, ...args], {
483
487
  encoding: "utf8",
484
488
  timeout: 500,
485
489
  maxBuffer: MAX_GIT_DIFF_BYTES * 2,
@@ -612,7 +616,11 @@ function createPromptObservationRequest(input, agent, sessionId, now, gitContext
612
616
  connector: "lore-cli",
613
617
  agent,
614
618
  sessionId,
615
- eventId: deterministicUuid(`lore-prompt\0${agent}\0${sessionId}\0${promptId ?? idempotencyKey}`),
619
+ // Hosts keep one prompt or turn ID for messages queued while the agent is
620
+ // still working, so the ID alone does not identify a prompt. The key also
621
+ // covers the text: a repeated hook keeps its event and a queued message
622
+ // gets its own, instead of conflicting with the first one.
623
+ eventId: deterministicUuid(`lore-prompt\0${agent}\0${sessionId}\0${idempotencyKey}`),
616
624
  prompt: redactSecrets(prompt),
617
625
  ...(promptId === undefined ? {} : { promptId }),
618
626
  timestamp: now.toISOString(),
@@ -804,8 +812,10 @@ function deliveryFromResponse(value) {
804
812
  context: boundedContext(context),
805
813
  ...(typeof value.relatedContext === "string" ? { relatedContext: redactSecrets(value.relatedContext).slice(0, 2_500) }
806
814
  : isObject(value.context) && typeof value.context.relatedContext === "string" ? { relatedContext: redactSecrets(value.context.relatedContext).slice(0, 2_500) } : {}),
807
- ...(typeof value.skillContext === "string" && value.skillContext.length <= 72_000 ? { skillContext: redactSecrets(value.skillContext) }
808
- : isObject(value.context) && typeof value.context.skillContext === "string" && value.context.skillContext.length <= 72_000 ? { skillContext: redactSecrets(value.context.skillContext) } : {}),
815
+ ...(typeof value.skillContext === "string" ? { skillContext: fitSkillContext(redactSecrets(value.skillContext)) }
816
+ : isObject(value.context) && typeof value.context.skillContext === "string" ? { skillContext: fitSkillContext(redactSecrets(value.context.skillContext)) } : {}),
817
+ ...(typeof value.approachContext === "string" ? { approachContext: redactSecrets(value.approachContext).slice(0, 600) }
818
+ : isObject(value.context) && typeof value.context.approachContext === "string" ? { approachContext: redactSecrets(value.context.approachContext).slice(0, 600) } : {}),
809
819
  completeContext: context.length <= MAX_CONTEXT_CHARS,
810
820
  ...(receiptId === undefined ? {} : { receiptId }),
811
821
  learned,
@@ -918,6 +928,7 @@ function deliveryFromSignedContext(payload, expectedRequest) {
918
928
  context: payload.context,
919
929
  relatedContext: payload.relatedContext,
920
930
  skillContext: payload.skillContext,
931
+ approachContext: payload.approachContext,
921
932
  receipt: { id: payload.receiptId },
922
933
  memories: payload.memories,
923
934
  hits: payload.hits,
@@ -1180,6 +1191,7 @@ async function postTurn(config, request, fetchImplementation, store) {
1180
1191
  headers: {
1181
1192
  "x-lore-context": "related-work-v1",
1182
1193
  "x-lore-skills": "skills-v1",
1194
+ "x-lore-approaches": "approaches-v1",
1183
1195
  accept: runtimeAccept(config),
1184
1196
  authorization: `Bearer ${config.token}`,
1185
1197
  "content-type": "application/json",
@@ -1249,11 +1261,13 @@ async function postTurn(config, request, fetchImplementation, store) {
1249
1261
  task: (request.task ?? request.currentUser.content).trim(),
1250
1262
  },
1251
1263
  });
1252
- await store
1253
- .writeContextSnapshot(request.eventId, {
1254
- compactJws: value.contextSnapshot,
1255
- })
1256
- .catch(() => undefined);
1264
+ if (!referencesSourceConversations(payload)) {
1265
+ await store
1266
+ .writeContextSnapshot(request.eventId, {
1267
+ compactJws: value.contextSnapshot,
1268
+ })
1269
+ .catch(() => undefined);
1270
+ }
1257
1271
  return { ...delivery, reliability };
1258
1272
  }
1259
1273
  async function postPromptObservation(config, request, fetchImplementation, _store) {
@@ -1334,6 +1348,7 @@ async function getPromptContext(config, agent, sessionId, sourceEventId, prompt,
1334
1348
  headers: {
1335
1349
  "x-lore-context": "related-work-v1",
1336
1350
  "x-lore-skills": "skills-v1",
1351
+ "x-lore-approaches": "approaches-v1",
1337
1352
  accept: runtimeAccept(config),
1338
1353
  authorization: `Bearer ${config.token}`,
1339
1354
  "content-type": "application/json",
@@ -1388,9 +1403,13 @@ async function getPromptContext(config, agent, sessionId, sourceEventId, prompt,
1388
1403
  };
1389
1404
  }
1390
1405
  const delivery = deliveryFromSignedContext(payload, deliveryRequest);
1391
- await store
1392
- .writeContextSnapshot(sourceEventId, { compactJws: value.snapshot })
1393
- .catch(() => undefined);
1406
+ // The signed snapshot carries the request, prompt included, so capture off
1407
+ // caches nothing. Offline fallback uses context cached while capture was on.
1408
+ if (config.capture && !referencesSourceConversations(payload)) {
1409
+ await store
1410
+ .writeContextSnapshot(sourceEventId, { compactJws: value.snapshot })
1411
+ .catch(() => undefined);
1412
+ }
1394
1413
  return {
1395
1414
  ...delivery,
1396
1415
  reliability,
@@ -1437,8 +1456,9 @@ async function getCachedPromptContext(config, prompt, gitContext, store, now, re
1437
1456
  now,
1438
1457
  }).payload;
1439
1458
  // Signed payloads cannot be redacted in place. Retire legacy snapshots
1440
- // containing raw request secrets and let a later live request replace them.
1441
- if (!hasRedactedContextRequest(payload)) {
1459
+ // containing raw request secrets or source conversations and let a
1460
+ // later live request replace them.
1461
+ if (!hasRedactedContextRequest(payload) || referencesSourceConversations(payload)) {
1442
1462
  await store.deleteContextSnapshot(record.cacheKey, value.compactJws);
1443
1463
  continue;
1444
1464
  }
@@ -1536,8 +1556,12 @@ async function retrievalNotice(delivery, sessionId, store, now) {
1536
1556
  if (reliability.fallback === "cached_context") {
1537
1557
  return `Lore used verified ${reliability.freshness.state} cached context (${formatAge(reliability.freshness.ageMs)} old) because live retrieval was unavailable.`;
1538
1558
  }
1559
+ // Keyword-only retrieval is a normal setup; mention it only when semantic search is down.
1560
+ if (reliability.fallback === "live_lexical" && !reliability.reasons.includes("semantic_unavailable")) {
1561
+ return undefined;
1562
+ }
1539
1563
  const notice = reliability.fallback === "live_lexical"
1540
- ? "Lore used live lexical retrieval because semantic retrieval was unavailable."
1564
+ ? "Lore matched learnings by keyword because semantic search is unavailable right now."
1541
1565
  : reliability.reasons.includes("invalid_response")
1542
1566
  ? "Lore context could not be trusted; continuing without injected context."
1543
1567
  : "Lore context is temporarily unavailable; continuing without injected context.";
@@ -1729,9 +1753,6 @@ function receiptMessage(config, agent, delivery) {
1729
1753
  ? preview(delivered.content)
1730
1754
  : `${preview(delivered.content)} (+${delivery.delivered.length - 1} more)`;
1731
1755
  parts.push(`Lore taught ${label}${delivered.path === undefined ? "" : ` for ${delivered.path}`}: ${suffix}`);
1732
- if (delivered.reasons !== undefined) {
1733
- parts.push(`Why: ${delivered.reasons.join(", ")}`);
1734
- }
1735
1756
  }
1736
1757
  if (parts.length === 0) {
1737
1758
  return undefined;
@@ -1740,7 +1761,7 @@ function receiptMessage(config, agent, delivery) {
1740
1761
  const path = delivery.receiptId === undefined
1741
1762
  ? learned === undefined
1742
1763
  ? undefined
1743
- : `/memories/${encodeURIComponent(learned.id)}`
1764
+ : `/learnings/${encodeURIComponent(learned.id)}`
1744
1765
  : `/receipts/${encodeURIComponent(delivery.receiptId)}`;
1745
1766
  if (path !== undefined) {
1746
1767
  parts.push(`${learned?.status === "proposed" ? "Review proposal" : "Review or forget"}: ${config.dashboardUrl}${path}`);
@@ -1817,23 +1838,15 @@ function guardResultFromResponse(value) {
1817
1838
  if (!isObject(item) || typeof item.content !== "string") {
1818
1839
  return [];
1819
1840
  }
1820
- const scope = isObject(item.scope) ? item.scope : {};
1821
- const source = isObject(item.source) ? item.source : {};
1822
- const explanation = guardExplanationFromValue(item.explanation);
1841
+ const rule = isGuardRule(item.guardRule) ? guardRuleContent(item.guardRule) : undefined;
1823
1842
  return [
1824
1843
  {
1825
1844
  content: redactSecrets(item.content),
1845
+ ...(rule === undefined || rule === item.content ? {} : { rule }),
1826
1846
  enforcement: typeof item.enforcement === "string"
1827
1847
  ? item.enforcement
1828
1848
  : "advisory",
1829
- ...(typeof scope.repo === "string" ? { repo: scope.repo } : {}),
1830
- ...(typeof scope.organization === "string"
1831
- ? { organization: scope.organization }
1832
- : {}),
1833
- ...(typeof source.agent === "string"
1834
- ? { sourceAgent: source.agent }
1835
- : {}),
1836
- ...(explanation === undefined ? {} : { explanation }),
1849
+ scope: guardItemScope(item),
1837
1850
  },
1838
1851
  ];
1839
1852
  })
@@ -1856,48 +1869,23 @@ function guardResultFromResponse(value) {
1856
1869
  conflictSummaries,
1857
1870
  };
1858
1871
  }
1859
- function guardExplanationFromValue(value) {
1860
- if (!isObject(value) ||
1861
- typeof value.summary !== "string" ||
1862
- !isObject(value.provenance) ||
1863
- typeof value.provenance.agent !== "string" ||
1864
- typeof value.status !== "string" ||
1865
- !isObject(value.freshness) ||
1866
- typeof value.freshness.updatedAt !== "string") {
1867
- return undefined;
1868
- }
1869
- const scope = isObject(value.scope) ? value.scope : {};
1870
- const scopeParts = [
1871
- typeof scope.organization === "string"
1872
- ? `org ${scope.organization}`
1873
- : undefined,
1874
- typeof scope.project === "string" ? `project ${scope.project}` : undefined,
1875
- typeof scope.repo === "string" ? `repo ${scope.repo}` : undefined,
1876
- typeof scope.path === "string" ? `path ${scope.path}` : undefined,
1877
- typeof scope.component === "string"
1878
- ? `component ${scope.component}`
1879
- : undefined,
1880
- ].filter((part) => part !== undefined);
1881
- const session = typeof value.provenance.sessionId === "string"
1882
- ? ` session ${value.provenance.sessionId}`
1883
- : "";
1884
- return `Why: ${redactSecrets(value.summary)} Source: ${value.provenance.agent}${session}. Scope: ${scopeParts.join(" · ") || "workspace-wide"}. Status: ${value.status}. Updated: ${value.freshness.updatedAt.slice(0, 10)}.`;
1885
- }
1886
- function guardItemSource(item) {
1887
- const scope = item.repo !== undefined
1888
- ? `${item.repo} repo`
1889
- : item.organization !== undefined
1890
- ? "organization rule"
1891
- : "workspace knowledge";
1892
- return item.sourceAgent === undefined
1893
- ? scope
1894
- : `${scope} · ${item.sourceAgent} session`;
1895
- }
1896
- function guardAssistContext(result) {
1872
+ /** Where the item applies, as the explanation (or the item) reports it. */
1873
+ function guardItemScope(item) {
1874
+ const scope = isObject(item.explanation) && isObject(item.explanation.scope)
1875
+ ? item.explanation.scope
1876
+ : isObject(item.scope)
1877
+ ? item.scope
1878
+ : {};
1879
+ return {
1880
+ ...(typeof scope.repo === "string" ? { repo: scope.repo } : {}),
1881
+ ...(typeof scope.path === "string" ? { path: scope.path } : {}),
1882
+ ...(typeof scope.component === "string" ? { component: scope.component } : {}),
1883
+ };
1884
+ }
1885
+ function guardAssistContext(result, repo) {
1897
1886
  const lines = ["Relevant Lore context:"];
1898
1887
  for (const item of result.items) {
1899
- lines.push(`- ${item.content}`);
1900
- lines.push(` ${item.explanation ?? `Source: ${guardItemSource(item)}.`}`);
1888
+ lines.push(`- ${guardItemLine(item, repo)}`);
1901
1889
  }
1902
1890
  if (result.conflictSummaries.length > 0) {
1903
1891
  lines.push("", "Lore policy notes:");
@@ -1907,18 +1895,15 @@ function guardAssistContext(result) {
1907
1895
  }
1908
1896
  return boundedContext(lines.join("\n"));
1909
1897
  }
1910
- function guardConfirmationReason(result) {
1898
+ function guardConfirmationReason(result, repo) {
1911
1899
  if (result.policyUnavailable)
1912
1900
  return "Lore Guard cannot evaluate an existing required rule. An owner or admin must rewrite it using a supported prohibition or change it to advisory before retrying.";
1913
1901
  const required = result.items.filter((item) => item.enforcement === "required");
1914
1902
  const shown = required.length > 0 ? required : result.items;
1915
1903
  const lines = [
1916
- result.items.length === 0 ? "Lore Guard cannot verify the enforcement policy." : "Lore Guard: this action conflicts with a required rule.",
1904
+ result.items.length === 0 ? "Lore Guard cannot verify the enforcement policy." : "Lore Guard: this action needs approval under a required rule.",
1917
1905
  ...result.conflictSummaries,
1918
- ...shown.flatMap((item) => [
1919
- `- ${item.content}`,
1920
- ` ${item.explanation ?? `Source: ${guardItemSource(item)}.`}`,
1921
- ]),
1906
+ ...shown.map((item) => `- ${guardItemLine(item, repo)}${item.rule === undefined ? "" : ` Rule: ${item.rule}`}`),
1922
1907
  ];
1923
1908
  lines.push(result.offline && result.confirmCommand !== undefined
1924
1909
  ? `Restore Lore or run: ${result.confirmCommand}`
@@ -2009,7 +1994,7 @@ async function offlineGuard(config, store, request, now, previouslyEnforced, all
2009
1994
  }) !== null) : [];
2010
1995
  return {
2011
1996
  offline: true, mode: "enforce", requiresConfirmation: rules.length > 0,
2012
- items: rules.map((rule) => ({ content: String(rule.content), enforcement: "required" })),
1997
+ items: rules.map((rule) => ({ content: String(rule.content), enforcement: "required", scope: guardItemScope(rule) })),
2013
1998
  conflictSummaries: ["Lore is offline; this action was checked against its verified, unexpired saved policy."],
2014
1999
  confirmCommand: guardEmergencyCommand(String(request.sessionId), String(request.repo ?? "")),
2015
2000
  };
@@ -2131,7 +2116,7 @@ async function handleGuardEvent(input, agent, config, sessionId, options, now, c
2131
2116
  // independently of `requiresConfirmation`: the producer reports it via
2132
2117
  // `policyIssue` rather than `confirm`, so it never sets `requiresConfirmation`.
2133
2118
  if (result.requiresConfirmation || result.policyUnavailable) {
2134
- const reason = guardConfirmationReason(result);
2119
+ const reason = guardConfirmationReason(result, scope?.repo);
2135
2120
  if (agent === "polytoken")
2136
2121
  return { outcome: "deny", reason };
2137
2122
  if (agent === "copilot-cli") {
@@ -2166,8 +2151,11 @@ async function handleGuardEvent(input, agent, config, sessionId, options, now, c
2166
2151
  // context was already added by userPromptTransformed.
2167
2152
  return undefined;
2168
2153
  }
2169
- if (agent === "polytoken")
2170
- return { outcome: "allow" };
2154
+ if (agent === "polytoken") {
2155
+ // Like every other host, Polytoken gets no verdict on advisory results:
2156
+ // Lore only ever blocks, it never approves an action.
2157
+ return undefined;
2158
+ }
2171
2159
  if (agent === "cursor") {
2172
2160
  // Cursor cannot inject agent context at the shell boundary on allow.
2173
2161
  return undefined;
@@ -2175,11 +2163,14 @@ async function handleGuardEvent(input, agent, config, sessionId, options, now, c
2175
2163
  return {
2176
2164
  hookSpecificOutput: {
2177
2165
  hookEventName: "PreToolUse",
2178
- additionalContext: guardAssistContext(result),
2166
+ additionalContext: guardAssistContext(result, scope?.repo),
2179
2167
  },
2180
2168
  };
2181
2169
  }
2182
2170
  export async function handleHookEvent(value, agent, options = {}) {
2171
+ // LORE_DISABLE: the same empty answer as an unconnected machine, for every host and event.
2172
+ if (loreDisabled(options.environment ?? process.env))
2173
+ return undefined;
2183
2174
  if (!isObject(value)) {
2184
2175
  throw new InvalidHookInputError();
2185
2176
  }
@@ -2255,6 +2246,8 @@ export async function handleHookEvent(value, agent, options = {}) {
2255
2246
  : "Before answering each user request or using other tools, call lore_context with that sessionId and the full user message as prompt. This retrieves relevant shared knowledge; automatic capture is paused, so nothing is recorded.",
2256
2247
  "Use the returned context when relevant. If retrieval fails, report that limitation; do not invent shared knowledge.",
2257
2248
  ...(capture ? ["After completing each response, call lore_complete with the same sessionId and a concise response summary so the next user correction retains the prior attempt. These Lore calls are available through MCP and require no shell commands."] : ["These Lore calls are available through MCP and require no shell commands."]),
2249
+ // Cursor's session start runs once per conversation, so no marker is needed.
2250
+ ...(recapReminderApplies(agent, capture, config) ? [RECAP_REMINDER] : []),
2258
2251
  ].join("\n") };
2259
2252
  }
2260
2253
  if (eventName === "Stop" || eventName === "AssistantResponse") {
@@ -2323,27 +2316,69 @@ export async function handleHookEvent(value, agent, options = {}) {
2323
2316
  };
2324
2317
  }
2325
2318
  }
2319
+ const promptConfig = { ...config, capture };
2320
+ // Once per store: remove prompt text earlier versions kept with capture off.
2321
+ await runtimeStore(config, options.home)
2322
+ .then(store => forgetUncapturedPromptText(store, capture, now, CURSOR_SESSION_PREFIX))
2323
+ .catch(() => undefined);
2326
2324
  if (agent === "cursor") {
2327
2325
  const store = await runtimeStore(config, options.home);
2328
2326
  const existing = await store.readState(cursorSessionKey(sessionId));
2327
+ const { prompt: _prompt, ...kept } = isObject(existing) ? existing : {};
2329
2328
  const cwd = stringField(input.cwd);
2329
+ // Capture off keeps no prompt: lore_context then searches with the
2330
+ // message the agent passes it.
2330
2331
  if (cwd)
2331
2332
  await store.writeState(cursorSessionKey(sessionId), {
2332
- ...(isObject(existing) ? existing : {}), sessionId, cwd,
2333
- ...(stringField(input.prompt) ? { prompt: redactSecrets(String(input.prompt)) } : {}),
2333
+ ...kept, sessionId, cwd,
2334
+ ...(capture && stringField(input.prompt) ? { prompt: redactSecrets(String(input.prompt)) } : {}),
2334
2335
  ...(stringField(input.prompt_id) ? { promptId: input.prompt_id } : {}), updatedAt: now.toISOString(),
2335
2336
  });
2336
2337
  // Cursor's prompt hook supports submission control only. The MCP call
2337
2338
  // delivers the context; capture here retains native editor event identity.
2338
2339
  // Retrieve is skipped so the notice cooldown is not primed by a discarded
2339
2340
  // result: the MCP `lore_context` call surfaces the reliability notice.
2340
- await handlePromptEvent(input, agent, sessionId, config, options, now, capture, false);
2341
+ await handlePromptEvent(input, agent, sessionId, promptConfig, options, now, capture, false);
2341
2342
  return { continue: true };
2342
2343
  }
2343
- return handlePromptEvent(input, agent, sessionId, config, options, now, capture);
2344
+ return handlePromptEvent(input, agent, sessionId, promptConfig, options, now, capture, true, recapReminderApplies(agent, capture, config));
2345
+ }
2346
+ export { RECAP_REMINDER };
2347
+ /** Hosts where `lore connect` installs the recap skill and this runtime delivers context. */
2348
+ const RECAP_HOSTS = new Set(["claude", "codex", "copilot-cli", "copilot-vscode", "cursor"]);
2349
+ function recapReminderApplies(agent, capture, config) {
2350
+ return !capture && config.recapReminder !== false && RECAP_HOSTS.has(agent);
2351
+ }
2352
+ /** True for the first prompt of a session only. The marker is a hash of the session, never the prompt. */
2353
+ /** True once this session asked to save an approach. The marker is a hash of the session, never the prompt. */
2354
+ async function approachWorkflowStarted(store, agent, sessionId, now, requested) {
2355
+ const key = `approach-workflow-${sha256(`${agent}\0${sessionId}`)}`;
2356
+ try {
2357
+ if (!requested)
2358
+ return await store.readState(key) !== null;
2359
+ await store.updateState(key, (current) => current ?? { at: now.toISOString() }, now, { recoverCorrupt: true });
2360
+ }
2361
+ catch {
2362
+ // An unreadable marker only costs the reminder after compaction.
2363
+ }
2364
+ return requested;
2365
+ }
2366
+ async function claimRecapReminder(store, agent, sessionId, now) {
2367
+ let first = false;
2368
+ try {
2369
+ await store.updateState(`recap-reminder-${sha256(`${agent}\0${sessionId}`)}`, (current) => {
2370
+ first = current === null;
2371
+ return current ?? { at: now.toISOString() };
2372
+ }, now, { recoverCorrupt: true });
2373
+ }
2374
+ catch {
2375
+ return false;
2376
+ }
2377
+ return first;
2344
2378
  }
2379
+ const CURSOR_SESSION_PREFIX = "cursor-session-";
2345
2380
  function cursorSessionKey(sessionId) {
2346
- return `cursor-session-${sha256(sessionId)}`;
2381
+ return `${CURSOR_SESSION_PREFIX}${sha256(sessionId)}`;
2347
2382
  }
2348
2383
  /** MCP requests are bound to a session/workspace observed by the native hook. */
2349
2384
  async function cursorSession(sessionId, options) {
@@ -2371,7 +2406,7 @@ async function cursorSession(sessionId, options) {
2371
2406
  }
2372
2407
  // Same definition as the native hooks: local mode and the dashboard switches.
2373
2408
  const capture = config.capture && access.capturePausedBy === undefined;
2374
- return { config, store, state, cwd: stringField(state.cwd), capture };
2409
+ return { config: { ...config, capture }, store, state, cwd: stringField(state.cwd), capture };
2375
2410
  }
2376
2411
  export async function getCursorContext(sessionId, prompt, options = {}) {
2377
2412
  const { config, state, cwd, capture } = await cursorSession(sessionId, options);
@@ -2390,7 +2425,7 @@ export async function completeCursorResponse(sessionId, response, options = {})
2390
2425
  const { prompt: _prompt, promptId: _promptId, ...rest } = state;
2391
2426
  await store.writeState(cursorSessionKey(sessionId), { ...rest, updatedAt: now.toISOString() });
2392
2427
  }
2393
- async function handlePromptEvent(input, agent, sessionId, config, options, now, capture = true, retrieve = true) {
2428
+ async function handlePromptEvent(input, agent, sessionId, config, options, now, capture = true, retrieve = true, recapReminder = false) {
2394
2429
  const fetchImplementation = options.fetch ?? globalThis.fetch;
2395
2430
  const prompt = stringField(input.prompt);
2396
2431
  if (prompt === undefined) {
@@ -2466,11 +2501,14 @@ async function handlePromptEvent(input, agent, sessionId, config, options, now,
2466
2501
  };
2467
2502
  const lineage = await readLineageMetadata(options.home, options.environment ?? process.env);
2468
2503
  let delivery = emptyDelivery();
2469
- const retrievalTask = (repo) => resolveRetrievalTask(store, {
2504
+ const retrievalSession = (repo) => ({
2470
2505
  agent, sessionId, connectionKey: credentialReliabilityWorkspaceKey(config),
2471
2506
  cwd: stringField(input.cwd), repo,
2472
- }, prompt, now, pending?.cwd === stringField(input.cwd) && pending
2473
- ? { content: pending.assistantMessage, capturedAt: pending.capturedAt } : undefined);
2507
+ });
2508
+ const retrievalTask = (repo) => !config.capture
2509
+ ? unsavedRetrievalTask(store, retrievalSession(repo), prompt)
2510
+ : resolveRetrievalTask(store, retrievalSession(repo), prompt, now, pending?.cwd === stringField(input.cwd) && pending
2511
+ ? { content: pending.assistantMessage, capturedAt: pending.capturedAt } : undefined);
2474
2512
  if (pending !== null) {
2475
2513
  let receivedTurnContext = false;
2476
2514
  let request = await createTurnRequest(input, agent, pending, now, lineage);
@@ -2648,8 +2686,11 @@ async function handlePromptEvent(input, agent, sessionId, config, options, now,
2648
2686
  await rememberDeliveredContext(store, contextSession, delivery).catch(() => undefined);
2649
2687
  const relatedContext = delivery.relatedContext && !retainedContext.some(text => text.includes(delivery.relatedContext))
2650
2688
  ? delivery.relatedContext : undefined;
2689
+ const approachContext = delivery.approachContext && !retainedContext.some(text => text.includes(delivery.approachContext))
2690
+ ? delivery.approachContext : undefined;
2651
2691
  // Skills are fresh on every turn, even when learning context is retained.
2652
- const taskContext = [delivery.skillContext, delivery.context, relatedContext, delivery.reliability?.status === "failed" ? undefined : approachGuidance(prompt, agent, redactSecrets(sessionId), retainedContext), delivery.skillContext === undefined && delivery.reliability?.status !== "failed" ? skillGuidance(retainedContext) : undefined].filter(Boolean).join("\n\n");
2692
+ const taskContext = [delivery.skillContext, delivery.context, relatedContext, approachContext, delivery.reliability?.status === "failed" ? undefined : approachGuidance(prompt, agent, redactSecrets(sessionId), retainedContext, await approachWorkflowStarted(store, agent, sessionId, now, approachWorkflowRequested(prompt))), delivery.skillContext === undefined && delivery.reliability?.status !== "failed" ? skillGuidance(retainedContext) : undefined,
2693
+ recapReminder && await claimRecapReminder(store, agent, sessionId, now) ? RECAP_REMINDER : undefined].filter(Boolean).join("\n\n");
2653
2694
  const systemMessage = agent === "cursor" || agent === "polytoken"
2654
2695
  ? undefined
2655
2696
  : [receipt, reliabilityNotice].filter(Boolean).join(" · ") || undefined;
@@ -2738,6 +2779,15 @@ export async function runHook(args = process.argv.slice(2)) {
2738
2779
  if (agent === null) {
2739
2780
  return;
2740
2781
  }
2782
+ if (loreDisabled(process.env)) {
2783
+ // Nothing is read, sent, or written, not even invocation health. Drain the
2784
+ // payload so the host never sees a broken pipe, then exit 0 with no output.
2785
+ try {
2786
+ for await (const _chunk of process.stdin) { /* discard */ }
2787
+ }
2788
+ catch { /* nothing to report */ }
2789
+ return;
2790
+ }
2741
2791
  let tracker = null;
2742
2792
  let input;
2743
2793
  try {