@lore-co/cli 0.2.20 → 0.2.21

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 (67) hide show
  1. package/README.md +18 -2
  2. package/dist/approach-guidance.d.ts.map +1 -1
  3. package/dist/approach-guidance.js +1 -0
  4. package/dist/approach-guidance.js.map +1 -1
  5. package/dist/approach.d.ts +1 -1
  6. package/dist/approach.d.ts.map +1 -1
  7. package/dist/approach.js +11 -3
  8. package/dist/approach.js.map +1 -1
  9. package/dist/ask.d.ts +9 -1
  10. package/dist/ask.d.ts.map +1 -1
  11. package/dist/ask.js +32 -3
  12. package/dist/ask.js.map +1 -1
  13. package/dist/cli.d.ts +36 -2
  14. package/dist/cli.d.ts.map +1 -1
  15. package/dist/cli.js +225 -48
  16. package/dist/cli.js.map +1 -1
  17. package/dist/command-help.d.ts +3 -1
  18. package/dist/command-help.d.ts.map +1 -1
  19. package/dist/command-help.js +13 -1
  20. package/dist/command-help.js.map +1 -1
  21. package/dist/devin-cli.js +2 -2
  22. package/dist/devin-cli.js.map +1 -1
  23. package/dist/embeddings.d.ts +12 -0
  24. package/dist/embeddings.d.ts.map +1 -0
  25. package/dist/embeddings.js +105 -0
  26. package/dist/embeddings.js.map +1 -0
  27. package/dist/generated-assets.d.ts +5 -4
  28. package/dist/generated-assets.d.ts.map +1 -1
  29. package/dist/generated-assets.js +5 -4
  30. package/dist/generated-assets.js.map +1 -1
  31. package/dist/github.d.ts.map +1 -1
  32. package/dist/github.js +242 -4
  33. package/dist/github.js.map +1 -1
  34. package/dist/guard.js +2 -2
  35. package/dist/guard.js.map +1 -1
  36. package/dist/handoff.js +2 -2
  37. package/dist/handoff.js.map +1 -1
  38. package/dist/learnings.d.ts +31 -0
  39. package/dist/learnings.d.ts.map +1 -0
  40. package/dist/learnings.js +265 -0
  41. package/dist/learnings.js.map +1 -0
  42. package/dist/platform.d.ts +7 -0
  43. package/dist/platform.d.ts.map +1 -1
  44. package/dist/platform.js +25 -1
  45. package/dist/platform.js.map +1 -1
  46. package/dist/reliability-store.d.ts +31 -0
  47. package/dist/reliability-store.d.ts.map +1 -1
  48. package/dist/reliability-store.js +89 -0
  49. package/dist/reliability-store.js.map +1 -1
  50. package/dist/runtime.d.ts +13 -0
  51. package/dist/runtime.d.ts.map +1 -1
  52. package/dist/runtime.js +119 -28
  53. package/dist/runtime.js.map +1 -1
  54. package/dist/self-host.d.ts +2 -2
  55. package/dist/self-host.d.ts.map +1 -1
  56. package/dist/self-host.js +4 -3
  57. package/dist/self-host.js.map +1 -1
  58. package/dist/skills.d.ts.map +1 -1
  59. package/dist/skills.js +24 -4
  60. package/dist/skills.js.map +1 -1
  61. package/dist/uninstall.d.ts.map +1 -1
  62. package/dist/uninstall.js +4 -3
  63. package/dist/uninstall.js.map +1 -1
  64. package/dist/update.d.ts.map +1 -1
  65. package/dist/update.js +4 -23
  66. package/dist/update.js.map +1 -1
  67. package/package.json +3 -3
package/dist/runtime.js CHANGED
@@ -13,7 +13,7 @@ import { dirname, parse, relative, resolve } from "node:path";
13
13
  import { homedir } from "node:os";
14
14
  import { fileURLToPath, pathToFileURL } from "node:url";
15
15
  import { promisify } from "node:util";
16
- import { boundedUtf8Text, defaultGitPushBranches, repositoryScopeFromGitRoot, } from "./repository.js";
16
+ import { boundedUtf8Text, defaultGitPushBranches, repositoryRemoteScope, repositoryScopeFromGitRoot, } from "./repository.js";
17
17
  import { ReliabilityStore, ReliabilityStoreError, atomicWriteJson as durableAtomicWriteJson, classifyRetryFailure, createIntegrationInvocationAttempt, durableUnlink, recordLocalGuardMetric, recordLocalRetrievalMetric, } from "./reliability-store.js";
18
18
  import { writeInvocationAttemptBounded, writeInvocationCompletionBounded, } from "./invocation-health-writer.js";
19
19
  import { SignedSnapshotError, verifySignedSnapshot, } from "./signed-snapshot.js";
@@ -456,7 +456,12 @@ async function repositoryRoot(cwd) {
456
456
  }
457
457
  }
458
458
  }
459
- async function repositoryScope(cwd) {
459
+ /**
460
+ * Captured scope records only an origin-derived repository, so a checkout
461
+ * without a remote never stores its folder name as one. Guard keeps the folder
462
+ * label for that case because local overrides are keyed by it.
463
+ */
464
+ async function repositoryScope(cwd, repository = repositoryRemoteScope) {
460
465
  if (cwd === undefined) {
461
466
  return undefined;
462
467
  }
@@ -465,8 +470,9 @@ async function repositoryScope(cwd) {
465
470
  return undefined;
466
471
  }
467
472
  const scopedPath = normalizedRepositoryPath(relative(root, resolve(cwd)));
473
+ const repo = await repository(root);
468
474
  return {
469
- repo: await repositoryScopeFromGitRoot(root),
475
+ ...(repo === undefined ? {} : { repo }),
470
476
  ...(scopedPath === "" ? {} : { path: scopedPath }),
471
477
  };
472
478
  }
@@ -485,20 +491,16 @@ async function gitOutput(root, args) {
485
491
  }
486
492
  async function collectGitContext(cwd) {
487
493
  const scope = await repositoryScope(cwd);
488
- if (cwd === undefined || scope?.repo === undefined) {
494
+ if (cwd === undefined || scope === undefined) {
489
495
  return {
490
496
  ...(scope === undefined ? {} : { scope }),
491
497
  learningScope: {},
492
498
  };
493
499
  }
500
+ const learningScope = scope.repo === undefined ? {} : { repo: scope.repo };
494
501
  const root = await repositoryRoot(cwd);
495
502
  if (root === null) {
496
- return {
497
- scope,
498
- learningScope: {
499
- repo: scope.repo,
500
- },
501
- };
503
+ return { scope, learningScope };
502
504
  }
503
505
  const [trackedFiles, untrackedFiles, rawDiff] = await Promise.all([
504
506
  gitOutput(root, [
@@ -527,9 +529,7 @@ async function collectGitContext(cwd) {
527
529
  const diff = boundedUtf8Text(redactSecrets(rawDiff), MAX_GIT_DIFF_BYTES, "\n\n[diff truncated by Lore]").trim();
528
530
  return {
529
531
  scope,
530
- learningScope: {
531
- repo: scope.repo,
532
- },
532
+ learningScope,
533
533
  ...(files.length === 0 ? {} : { files }),
534
534
  ...(diff === "" ? {} : { diff }),
535
535
  };
@@ -813,6 +813,64 @@ function deliveryFromResponse(value) {
813
813
  }
814
814
  class IncompatibleReliabilityError extends Error {
815
815
  incompatible = true;
816
+ reason;
817
+ constructor(message, reason = "invalid_response") {
818
+ super(message);
819
+ this.reason = reason;
820
+ }
821
+ }
822
+ const RETRIEVAL_FAILURE_STAGE = Symbol("lore.retrievalFailureStage");
823
+ function markKeyRefreshFailure(error) {
824
+ if (typeof error === "object" && error !== null) {
825
+ Object.defineProperty(error, RETRIEVAL_FAILURE_STAGE, {
826
+ value: "key_refresh",
827
+ enumerable: false,
828
+ configurable: true,
829
+ });
830
+ }
831
+ return error;
832
+ }
833
+ export function classifyRetrievalFailure(error) {
834
+ if (typeof error === "object" &&
835
+ error !== null &&
836
+ error[RETRIEVAL_FAILURE_STAGE] === "key_refresh") {
837
+ return "key_refresh";
838
+ }
839
+ if (error instanceof IncompatibleReliabilityError) {
840
+ return error.reason;
841
+ }
842
+ if (error instanceof SignedSnapshotError) {
843
+ return "snapshot_unverified";
844
+ }
845
+ if (error instanceof RuntimeHttpError) {
846
+ return `http_${error.httpStatus}`;
847
+ }
848
+ if (error instanceof Error) {
849
+ if (["AbortError", "TimeoutError", "LoreTimeoutError"].includes(error.name)) {
850
+ return "timeout";
851
+ }
852
+ const own = error.code;
853
+ const cause = error.cause;
854
+ const code = typeof own === "string"
855
+ ? own
856
+ : isObject(cause) && typeof cause.code === "string"
857
+ ? cause.code
858
+ : undefined;
859
+ if ((code !== undefined && isContextTransportFailure({ code })) ||
860
+ (code === undefined && error instanceof TypeError)) {
861
+ return "network";
862
+ }
863
+ const status = error;
864
+ const httpStatus = typeof status.httpStatus === "number"
865
+ ? status.httpStatus
866
+ : typeof status.status === "number"
867
+ ? status.status
868
+ : undefined;
869
+ if (httpStatus !== undefined && Number.isInteger(httpStatus)) {
870
+ return `http_${httpStatus}`;
871
+ }
872
+ }
873
+ return "unknown";
816
874
  }
817
875
  async function protocolResponseJson(response) {
818
876
  try {
@@ -853,7 +911,7 @@ function deliveryFromSignedContext(payload, expectedRequest) {
853
911
  !Array.isArray(payload.memories) ||
854
912
  !Array.isArray(payload.hits) ||
855
913
  !isObject(payload.packing)) {
856
- throw new IncompatibleReliabilityError("Lore context snapshot does not bind the request or signed delivery");
914
+ throw new IncompatibleReliabilityError("Lore context snapshot does not bind the request or signed delivery", "binding_mismatch");
857
915
  }
858
916
  return deliveryFromResponse({
859
917
  context: payload.context,
@@ -1075,15 +1133,29 @@ async function verifySnapshot(compactJws, kind, config, store, fetchImplementati
1075
1133
  keys,
1076
1134
  allowExpired,
1077
1135
  }).payload;
1136
+ let keys;
1137
+ try {
1138
+ keys = await pinnedKeys(config, store, fetchImplementation);
1139
+ }
1140
+ catch (error) {
1141
+ throw markKeyRefreshFailure(error);
1142
+ }
1078
1143
  try {
1079
- return verifyWith(await pinnedKeys(config, store, fetchImplementation));
1144
+ return verifyWith(keys);
1080
1145
  }
1081
1146
  catch (error) {
1082
1147
  if (!(error instanceof SignedSnapshotError) ||
1083
1148
  error.code !== "UNTRUSTED_KEY") {
1084
1149
  throw error;
1085
1150
  }
1086
- return verifyWith(await refreshTrustKeys(config, store, fetchImplementation));
1151
+ let refreshed;
1152
+ try {
1153
+ refreshed = await refreshTrustKeys(config, store, fetchImplementation);
1154
+ }
1155
+ catch (refreshError) {
1156
+ throw markKeyRefreshFailure(refreshError);
1157
+ }
1158
+ return verifyWith(refreshed);
1087
1159
  }
1088
1160
  }
1089
1161
  function committedAcknowledgement(value, externalEventId, serverEventId) {
@@ -1136,13 +1208,13 @@ async function postTurn(config, request, fetchImplementation, store) {
1136
1208
  typeof value.turn.event.id !== "string" ||
1137
1209
  !isObject(value.retrieval) ||
1138
1210
  !committedAcknowledgement(value.capture, request.eventId, value.turn.event.id)) {
1139
- throw new IncompatibleReliabilityError("Lore turn response has no matching durable acknowledgement");
1211
+ throw new IncompatibleReliabilityError("Lore turn response has no matching durable acknowledgement", "acknowledgement_mismatch");
1140
1212
  }
1141
1213
  const payload = await verifySnapshot(value.contextSnapshot, "context", config, store, fetchImplementation, true);
1142
1214
  if (payload.eventId !== value.turn.event.id ||
1143
1215
  !isObject(value.turn.receipt) ||
1144
1216
  payload.receiptId !== value.turn.receipt.id) {
1145
- throw new IncompatibleReliabilityError("Lore turn snapshot does not bind the returned event and receipt");
1217
+ throw new IncompatibleReliabilityError("Lore turn snapshot does not bind the returned event and receipt", "binding_mismatch");
1146
1218
  }
1147
1219
  const reliability = retrievalReliability(value.retrieval, payload);
1148
1220
  if (Date.now() - Date.parse(payload.validUntil) >
@@ -1172,7 +1244,8 @@ async function postTurn(config, request, fetchImplementation, store) {
1172
1244
  task: {
1173
1245
  agent: request.agent,
1174
1246
  ...(request.scope === undefined ? {} : { scope: request.scope }),
1175
- task: request.task ?? request.currentUser.content.trim(),
1247
+ // The server trims the retrieval task before searching and signing.
1248
+ task: (request.task ?? request.currentUser.content).trim(),
1176
1249
  },
1177
1250
  });
1178
1251
  await store
@@ -1287,7 +1360,7 @@ async function getPromptContext(config, agent, sessionId, sourceEventId, prompt,
1287
1360
  const payload = await verifySnapshot(value.snapshot, "context", config, store, fetchImplementation, true);
1288
1361
  if (payload.eventId !== value.delivery.event.id ||
1289
1362
  payload.receiptId !== value.delivery.receipt.id) {
1290
- throw new IncompatibleReliabilityError("Lore context snapshot does not bind the returned event and receipt");
1363
+ throw new IncompatibleReliabilityError("Lore context snapshot does not bind the returned event and receipt", "binding_mismatch");
1291
1364
  }
1292
1365
  const reliability = retrievalReliability(value.reliability, payload);
1293
1366
  if (Date.now() - Date.parse(payload.validUntil) >
@@ -1399,11 +1472,16 @@ async function getCachedPromptContext(config, prompt, gitContext, store, now, re
1399
1472
  },
1400
1473
  };
1401
1474
  }
1402
- async function getPromptContextWithFallback(config, agent, sessionId, sourceEventId, prompt, gitContext, fetchImplementation, store, now, retained = []) {
1475
+ async function getPromptContextWithFallback(config, agent, sessionId, sourceEventId, prompt, gitContext, fetchImplementation, store, now, retained = [], failures) {
1403
1476
  try {
1404
1477
  return await getPromptContext(config, agent, sessionId, sourceEventId, prompt, gitContext, fetchImplementation, store, retained);
1405
1478
  }
1406
1479
  catch (error) {
1480
+ failures?.push({
1481
+ reason: classifyRetrievalFailure(error),
1482
+ agent,
1483
+ path: "context",
1484
+ });
1407
1485
  if (isContextTransportFailure(error)) {
1408
1486
  const cached = await getCachedPromptContext(config, prompt, gitContext, store, now, retained).catch(() => null);
1409
1487
  if (cached !== null) {
@@ -1623,6 +1701,7 @@ async function flushOne(config, fetchImplementation, store, preferredIdempotency
1623
1701
  : failed.state === "dead"
1624
1702
  ? "dead"
1625
1703
  : "ready",
1704
+ failureReason: classifyRetrievalFailure(error),
1626
1705
  };
1627
1706
  }
1628
1707
  }
@@ -1956,7 +2035,7 @@ async function handleGuardEvent(input, agent, config, sessionId, options, now) {
1956
2035
  return undefined;
1957
2036
  }
1958
2037
  const cwd = stringField(input.cwd);
1959
- const scope = await repositoryScope(cwd);
2038
+ const scope = await repositoryScope(cwd, repositoryScopeFromGitRoot);
1960
2039
  const root = cwd === undefined ? null : await repositoryRoot(cwd);
1961
2040
  const files = (trigger.files ?? [])
1962
2041
  .map((file) => {
@@ -2128,7 +2207,7 @@ export async function handleHookEvent(value, agent, options = {}) {
2128
2207
  const prior = await readGuardState(agent, sessionId, options.home, config.connectionId).catch(() => null);
2129
2208
  const saved = await runtimeStore(config, options.home).then(store => store.readState("guard-policy-state")).catch(() => null);
2130
2209
  // Use the same scope as live Guard: non-Git folders have no repo override.
2131
- const scope = await repositoryScope(stringField(input.cwd)).catch(() => undefined);
2210
+ const scope = await repositoryScope(stringField(input.cwd), repositoryScopeFromGitRoot).catch(() => undefined);
2132
2211
  const override = guardRepositoryOverride(isObject(saved) ? saved.overrides : undefined, scope?.repo);
2133
2212
  if (prior?.mode === "enforce" || (isObject(override) ? override.mode : isObject(saved) ? saved.mode : undefined) === "enforce") {
2134
2213
  const reason = "Lore cannot verify workspace repository settings. Restore the connection before retrying.";
@@ -2187,7 +2266,7 @@ export async function handleHookEvent(value, agent, options = {}) {
2187
2266
  catch {
2188
2267
  const prior = await readGuardState(agent, sessionId, options.home, config.connectionId).catch(() => null);
2189
2268
  const saved = await runtimeStore(config, options.home).then((store) => store.readState("guard-policy-state")).catch(() => null);
2190
- const scope = await repositoryScope(stringField(input.cwd)).catch(() => undefined);
2269
+ const scope = await repositoryScope(stringField(input.cwd), repositoryScopeFromGitRoot).catch(() => undefined);
2191
2270
  const override = guardRepositoryOverride(isObject(saved) ? saved.overrides : undefined, scope?.repo);
2192
2271
  if (prior?.mode === "enforce" || (isObject(override) ? override.mode : isObject(saved) ? saved.mode : undefined) === "enforce") {
2193
2272
  const reason = "Lore Guard cannot verify its enforcement policy. Restore Lore before retrying.";
@@ -2288,6 +2367,8 @@ async function handlePromptEvent(input, agent, sessionId, config, options, now,
2288
2367
  throw new InvalidHookInputError();
2289
2368
  }
2290
2369
  const notices = [];
2370
+ // Classified reasons only: never the prompt, task, or request payload.
2371
+ const retrievalFailures = [];
2291
2372
  let store;
2292
2373
  try {
2293
2374
  store = await runtimeStore(config, options.home);
@@ -2402,8 +2483,15 @@ async function handlePromptEvent(input, agent, sessionId, config, options, now,
2402
2483
  else if (!terminal(flushed, request.eventId)) {
2403
2484
  notices.push("Lore saved this capture locally; server upload is pending.");
2404
2485
  }
2486
+ if (retrieve &&
2487
+ flushed?.idempotencyKey === request.eventId &&
2488
+ flushed.failureReason !== undefined) {
2489
+ retrievalFailures.push({ reason: flushed.failureReason, agent, path: "turn" });
2490
+ }
2405
2491
  }
2406
2492
  catch {
2493
+ // Local store errors are not retrieval failures; the context
2494
+ // delivery below records its own reason if it also fails.
2407
2495
  notices.push("Lore saved this capture locally; server upload is pending.");
2408
2496
  }
2409
2497
  }
@@ -2414,9 +2502,10 @@ async function handlePromptEvent(input, agent, sessionId, config, options, now,
2414
2502
  learningScope: request.learningScope,
2415
2503
  ...(request.diff === undefined ? {} : { diff: request.diff }),
2416
2504
  ...(request.files === undefined ? {} : { files: request.files }),
2417
- }, fetchImplementation, store, now, retained);
2505
+ }, fetchImplementation, store, now, retained, retrievalFailures);
2418
2506
  }
2419
- catch {
2507
+ catch (error) {
2508
+ retrievalFailures.push({ reason: classifyRetrievalFailure(error), agent, path: "context" });
2420
2509
  delivery = {
2421
2510
  ...emptyDelivery(),
2422
2511
  reliability: {
@@ -2476,9 +2565,10 @@ async function handlePromptEvent(input, agent, sessionId, config, options, now,
2476
2565
  }
2477
2566
  if (retrieve) {
2478
2567
  try {
2479
- delivery = await getPromptContextWithFallback(config, agent, sessionId, observation.eventId, task, gitContext, fetchImplementation, store, now, retained);
2568
+ delivery = await getPromptContextWithFallback(config, agent, sessionId, observation.eventId, task, gitContext, fetchImplementation, store, now, retained, retrievalFailures);
2480
2569
  }
2481
- catch {
2570
+ catch (error) {
2571
+ retrievalFailures.push({ reason: classifyRetrievalFailure(error), agent, path: "context" });
2482
2572
  delivery = {
2483
2573
  ...emptyDelivery(),
2484
2574
  reliability: {
@@ -2513,6 +2603,7 @@ async function handlePromptEvent(input, agent, sessionId, config, options, now,
2513
2603
  ...(reliability?.freshness.ageMs === undefined
2514
2604
  ? {}
2515
2605
  : { cacheAgeMs: reliability.freshness.ageMs }),
2606
+ ...(retrievalFailures.length === 0 ? {} : { failures: retrievalFailures }),
2516
2607
  }, now).catch(() => undefined);
2517
2608
  const contextReliabilityNotice = await retrievalNotice(delivery, sessionId, store, now);
2518
2609
  if (contextReliabilityNotice !== undefined) {