memorix 1.4.1 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -307,7 +307,7 @@ function closeAllDatabases() {
307
307
  _dbCache.delete(key);
308
308
  }
309
309
  }
310
- var BetterSqlite3, CREATE_OBSERVATIONS_TABLE, CREATE_MINI_SKILLS_TABLE, CREATE_SESSIONS_TABLE, CREATE_META_TABLE, CREATE_TEAM_AGENTS_TABLE, CREATE_TEAM_MESSAGES_TABLE, CREATE_TEAM_TASKS_TABLE, CREATE_TEAM_TASK_DEPS_TABLE, CREATE_TEAM_LOCKS_TABLE, CREATE_TEAM_ROLES_TABLE, CREATE_CHAT_TRANSCRIPT_TABLE, CREATE_GRAPH_ENTITIES_TABLE, CREATE_GRAPH_RELATIONS_TABLE, CREATE_CODE_FILES_TABLE, CREATE_CODE_SYMBOLS_TABLE, CREATE_CODE_EDGES_TABLE, CREATE_OBSERVATION_CODE_REFS_TABLE, CREATE_SCHEMA_MIGRATIONS_TABLE, CREATE_CODE_STATE_SNAPSHOTS_TABLE, CREATE_KNOWLEDGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_CLAIM_EVIDENCE_TABLE, CREATE_KNOWLEDGE_CLAIM_EVENTS_TABLE, CREATE_KNOWLEDGE_WORKSPACES_TABLE, CREATE_KNOWLEDGE_PAGES_TABLE, CREATE_KNOWLEDGE_PAGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_PAGE_LINKS_TABLE, CREATE_KNOWLEDGE_PROPOSALS_TABLE, CREATE_KNOWLEDGE_WORKFLOWS_TABLE, CREATE_KNOWLEDGE_WORKFLOW_RUNS_TABLE, CREATE_LONG_TERM_MEMORIES_TABLE, CREATE_LONG_TERM_MEMORY_EVIDENCE_TABLE, CREATE_LONG_TERM_MEMORY_EVENTS_TABLE, CREATE_MEDIA_ASSETS_TABLE, CREATE_MEDIA_ASSET_LINKS_TABLE, CREATE_MEDIA_DERIVATIONS_TABLE, CREATE_MEDIA_EMBEDDING_PROFILES_TABLE, CREATE_MEDIA_EMBEDDINGS_TABLE, CREATE_MEDIA_JOBS_TABLE, CREATE_MAINTENANCE_JOBS_TABLE, CREATE_MAINTENANCE_TARGETS_TABLE, CREATE_COMPACTION_CHECKPOINTS_TABLE, CREATE_INDEXES, SCHEMA_MIGRATIONS, _dbCache;
310
+ var BetterSqlite3, CREATE_OBSERVATIONS_TABLE, CREATE_MINI_SKILLS_TABLE, CREATE_SESSIONS_TABLE, CREATE_META_TABLE, CREATE_TEAM_AGENTS_TABLE, CREATE_TEAM_MESSAGES_TABLE, CREATE_TEAM_TASKS_TABLE, CREATE_TEAM_TASK_DEPS_TABLE, CREATE_TEAM_LOCKS_TABLE, CREATE_TEAM_ROLES_TABLE, CREATE_CHAT_TRANSCRIPT_TABLE, CREATE_GRAPH_ENTITIES_TABLE, CREATE_GRAPH_RELATIONS_TABLE, CREATE_CODE_FILES_TABLE, CREATE_CODE_SYMBOLS_TABLE, CREATE_CODE_EDGES_TABLE, CREATE_OBSERVATION_CODE_REFS_TABLE, CREATE_SCHEMA_MIGRATIONS_TABLE, CREATE_CODE_STATE_SNAPSHOTS_TABLE, CREATE_CODE_STATE_SNAPSHOT_FILES_TABLE, CREATE_KNOWLEDGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_CLAIM_EVIDENCE_TABLE, CREATE_KNOWLEDGE_CLAIM_EVENTS_TABLE, CREATE_KNOWLEDGE_WORKSPACES_TABLE, CREATE_KNOWLEDGE_PAGES_TABLE, CREATE_KNOWLEDGE_PAGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_PAGE_LINKS_TABLE, CREATE_KNOWLEDGE_PROPOSALS_TABLE, CREATE_KNOWLEDGE_WORKFLOWS_TABLE, CREATE_KNOWLEDGE_WORKFLOW_RUNS_TABLE, CREATE_LONG_TERM_MEMORIES_TABLE, CREATE_LONG_TERM_MEMORY_EVIDENCE_TABLE, CREATE_LONG_TERM_MEMORY_EVENTS_TABLE, CREATE_MEMORY_OUTCOME_SIGNALS_TABLE, CREATE_MEDIA_ASSETS_TABLE, CREATE_MEDIA_ASSET_LINKS_TABLE, CREATE_MEDIA_DERIVATIONS_TABLE, CREATE_MEDIA_EMBEDDING_PROFILES_TABLE, CREATE_MEDIA_EMBEDDINGS_TABLE, CREATE_MEDIA_JOBS_TABLE, CREATE_MAINTENANCE_JOBS_TABLE, CREATE_MAINTENANCE_TARGETS_TABLE, CREATE_COMPACTION_CHECKPOINTS_TABLE, CREATE_INDEXES, SCHEMA_MIGRATIONS, _dbCache;
311
311
  var init_sqlite_db = __esm({
312
312
  "src/store/sqlite-db.ts"() {
313
313
  "use strict";
@@ -573,6 +573,17 @@ CREATE TABLE IF NOT EXISTS observation_code_refs (
573
573
  " UNIQUE(projectId, sourceEpoch)",
574
574
  ");"
575
575
  ].join("\n");
576
+ CREATE_CODE_STATE_SNAPSHOT_FILES_TABLE = [
577
+ "CREATE TABLE IF NOT EXISTS code_state_snapshot_files (",
578
+ " snapshotId TEXT NOT NULL,",
579
+ " projectId TEXT NOT NULL,",
580
+ " fileId TEXT NOT NULL,",
581
+ " path TEXT NOT NULL,",
582
+ " contentHash TEXT NOT NULL,",
583
+ " PRIMARY KEY (snapshotId, path),",
584
+ " FOREIGN KEY (snapshotId) REFERENCES code_state_snapshots(id) ON DELETE CASCADE",
585
+ ");"
586
+ ].join("\n");
576
587
  CREATE_KNOWLEDGE_CLAIMS_TABLE = `
577
588
  CREATE TABLE IF NOT EXISTS knowledge_claims (
578
589
  id TEXT PRIMARY KEY,
@@ -783,6 +794,19 @@ CREATE TABLE IF NOT EXISTS long_term_memory_events (
783
794
  createdAt TEXT NOT NULL,
784
795
  FOREIGN KEY (memoryId) REFERENCES long_term_memories(id) ON DELETE CASCADE
785
796
  );
797
+ `;
798
+ CREATE_MEMORY_OUTCOME_SIGNALS_TABLE = `
799
+ CREATE TABLE IF NOT EXISTS memory_outcome_signals (
800
+ id TEXT PRIMARY KEY,
801
+ projectId TEXT NOT NULL,
802
+ candidateKind TEXT NOT NULL,
803
+ candidateId TEXT NOT NULL,
804
+ kind TEXT NOT NULL,
805
+ sourceRef TEXT NOT NULL,
806
+ snapshotId TEXT,
807
+ detail TEXT,
808
+ observedAt TEXT NOT NULL
809
+ );
786
810
  `;
787
811
  CREATE_MEDIA_ASSETS_TABLE = `
788
812
  CREATE TABLE IF NOT EXISTS media_assets (
@@ -1025,6 +1049,20 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
1025
1049
  db2.exec("CREATE INDEX IF NOT EXISTS idx_code_edges_snapshot ON code_edges(projectId, snapshotId)");
1026
1050
  }
1027
1051
  },
1052
+ {
1053
+ id: "1.4-codegraph-snapshot-manifest",
1054
+ apply: (db2) => {
1055
+ db2.exec(CREATE_CODE_STATE_SNAPSHOT_FILES_TABLE);
1056
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_code_snapshot_files_project_path ON code_state_snapshot_files(projectId, path)");
1057
+ }
1058
+ },
1059
+ {
1060
+ id: "1.4-memory-outcome-signals",
1061
+ apply: (db2) => {
1062
+ db2.exec(CREATE_MEMORY_OUTCOME_SIGNALS_TABLE);
1063
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_memory_outcomes_candidate ON memory_outcome_signals(projectId, candidateKind, candidateId, observedAt DESC)");
1064
+ }
1065
+ },
1028
1066
  {
1029
1067
  id: "1.2-knowledge-claim-ledger",
1030
1068
  apply: (db2) => {
@@ -1985,8 +2023,8 @@ async function readResponseText(response, signal, maxBytes = MAX_LLM_RESPONSE_BY
1985
2023
  }
1986
2024
  }
1987
2025
  async function readResponseJson(response, signal, maxBytes = MAX_LLM_RESPONSE_BYTES) {
1988
- const text = await readResponseText(response, signal, maxBytes);
1989
- return JSON.parse(text);
2026
+ const text2 = await readResponseText(response, signal, maxBytes);
2027
+ return JSON.parse(text2);
1990
2028
  }
1991
2029
  async function* callLLMWithToolsStream(messages, tools) {
1992
2030
  if (!currentConfig) {
@@ -3158,8 +3196,8 @@ var init_mini_skill_store = __esm({
3158
3196
 
3159
3197
  // src/compact/token-budget.ts
3160
3198
  import { countTokens, isWithinTokenLimit } from "gpt-tokenizer";
3161
- function countTextTokens(text) {
3162
- return countTokens(text);
3199
+ function countTextTokens(text2) {
3200
+ return countTokens(text2);
3163
3201
  }
3164
3202
  var init_token_budget = __esm({
3165
3203
  "src/compact/token-budget.ts"() {
@@ -3265,8 +3303,8 @@ import { createHash } from "crypto";
3265
3303
  import { readFile, writeFile, mkdir } from "fs/promises";
3266
3304
  import { join as join2 } from "path";
3267
3305
  import { homedir as homedir6 } from "os";
3268
- function textHash(text) {
3269
- return createHash("sha256").update(text).digest("hex").slice(0, 16);
3306
+ function textHash(text2) {
3307
+ return createHash("sha256").update(text2).digest("hex").slice(0, 16);
3270
3308
  }
3271
3309
  async function loadDiskCache() {
3272
3310
  try {
@@ -3329,11 +3367,11 @@ var init_fastembed_provider = __esm({
3329
3367
  async embedInputs(inputs, options = {}) {
3330
3368
  return Promise.all(inputs.map((input) => this.embedInput(input, options)));
3331
3369
  }
3332
- async embed(text) {
3333
- const hash4 = textHash(text);
3370
+ async embed(text2) {
3371
+ const hash4 = textHash(text2);
3334
3372
  const cached = cache.get(hash4);
3335
3373
  if (cached) return cached;
3336
- const raw = await this.model.queryEmbed(text);
3374
+ const raw = await this.model.queryEmbed(text2);
3337
3375
  const result = Array.from(raw);
3338
3376
  if (result.length !== this.dimensions) {
3339
3377
  throw new Error(`Expected ${this.dimensions}d embedding, got ${result.length}d`);
@@ -3429,10 +3467,10 @@ var init_transformers_provider = __esm({
3429
3467
  async embedInputs(inputs, options = {}) {
3430
3468
  return Promise.all(inputs.map((input) => this.embedInput(input, options)));
3431
3469
  }
3432
- async embed(text) {
3433
- const cached = cache2.get(text);
3470
+ async embed(text2) {
3471
+ const cached = cache2.get(text2);
3434
3472
  if (cached) return cached;
3435
- const output = await this.extractor(text, {
3473
+ const output = await this.extractor(text2, {
3436
3474
  pooling: "mean",
3437
3475
  normalize: true
3438
3476
  });
@@ -3440,7 +3478,7 @@ var init_transformers_provider = __esm({
3440
3478
  if (result.length !== this.dimensions) {
3441
3479
  throw new Error(`Expected ${this.dimensions}d embedding, got ${result.length}d`);
3442
3480
  }
3443
- this.cacheSet(text, result);
3481
+ this.cacheSet(text2, result);
3444
3482
  return result;
3445
3483
  }
3446
3484
  async embedBatch(texts) {
@@ -3503,8 +3541,8 @@ function dimsCacheFile() {
3503
3541
  function cacheMetaFile() {
3504
3542
  return join3(cacheDir(), ".embedding-api-cache-meta.json");
3505
3543
  }
3506
- function normalizeText(text) {
3507
- return text.replace(/\s+/g, " ").trim().slice(0, MAX_INPUT_CHARS);
3544
+ function normalizeText(text2) {
3545
+ return text2.replace(/\s+/g, " ").trim().slice(0, MAX_INPUT_CHARS);
3508
3546
  }
3509
3547
  function cacheNamespace(config) {
3510
3548
  return [
@@ -3514,8 +3552,8 @@ function cacheNamespace(config) {
3514
3552
  config.requestedDimensions ?? "native"
3515
3553
  ].join("|");
3516
3554
  }
3517
- function textHash2(text, namespace) {
3518
- return createHash2("sha256").update(`${namespace}\0${text}`).digest("hex").slice(0, 16);
3555
+ function textHash2(text2, namespace) {
3556
+ return createHash2("sha256").update(`${namespace}\0${text2}`).digest("hex").slice(0, 16);
3519
3557
  }
3520
3558
  function inputIdentity(input, options = {}) {
3521
3559
  return JSON.stringify({
@@ -3927,8 +3965,8 @@ var init_api_provider = __esm({
3927
3965
  if (!embedding) throw new Error("API probe returned no embeddings; check model name and API key");
3928
3966
  return embedding.length;
3929
3967
  }
3930
- async embed(text, options) {
3931
- const normalized = normalizeText(text);
3968
+ async embed(text2, options) {
3969
+ const normalized = normalizeText(text2);
3932
3970
  if (isNativeGeminiEndpoint(this.config.baseUrl)) {
3933
3971
  return this.embedInput({ modality: "text", text: normalized }, { intent: "document", ...options });
3934
3972
  }
@@ -3985,7 +4023,7 @@ var init_api_provider = __esm({
3985
4023
  }
3986
4024
  async embedBatch(texts, options) {
3987
4025
  if (isNativeGeminiEndpoint(this.config.baseUrl)) {
3988
- return Promise.all(texts.map((text) => this.embedInput({ modality: "text", text }, { intent: "document", ...options })));
4026
+ return Promise.all(texts.map((text2) => this.embedInput({ modality: "text", text: text2 }, { intent: "document", ...options })));
3989
4027
  }
3990
4028
  await ensureDiskCacheLoaded();
3991
4029
  const normalizedTexts = texts.map(normalizeText);
@@ -4064,8 +4102,8 @@ var init_api_provider = __esm({
4064
4102
  }
4065
4103
  async getCachedEmbeddings(texts) {
4066
4104
  await ensureDiskCacheLoaded();
4067
- return texts.map((text) => {
4068
- const hash4 = textHash2(normalizeText(text), this.cacheKeyNamespace);
4105
+ return texts.map((text2) => {
4106
+ const hash4 = textHash2(normalizeText(text2), this.cacheKeyNamespace);
4069
4107
  return cache3.get(hash4) ?? null;
4070
4108
  });
4071
4109
  }
@@ -4295,9 +4333,9 @@ function wrapProvider(candidate) {
4295
4333
  return {
4296
4334
  name: candidate.name,
4297
4335
  dimensions: candidate.dimensions,
4298
- async embed(text, options) {
4336
+ async embed(text2, options) {
4299
4337
  try {
4300
- return await candidate.embed(text, options);
4338
+ return await candidate.embed(text2, options);
4301
4339
  } catch (error) {
4302
4340
  if (kind === "api" && getEmbeddingMode2() === "auto" && isTemporaryEmbeddingFailure(error)) {
4303
4341
  warnOnce("[memorix] API embedding temporarily unavailable \u2014 switching to local fallback provider");
@@ -4305,7 +4343,7 @@ function wrapProvider(candidate) {
4305
4343
  if (fallback) {
4306
4344
  provider = wrapProvider(fallback);
4307
4345
  warnOnce(`[memorix] Embedding fallback activated: ${provider.name} (${provider.dimensions}d)`);
4308
- return provider.embed(text, options);
4346
+ return provider.embed(text2, options);
4309
4347
  }
4310
4348
  }
4311
4349
  if (isTemporaryEmbeddingFailure(error)) {
@@ -4804,8 +4842,8 @@ function isCjkHeavy(query) {
4804
4842
  const cjkCount = (query.match(CJK_PATTERN) || []).length;
4805
4843
  return query.length > 0 && cjkCount / query.length > 0.3;
4806
4844
  }
4807
- function normalizeExpansion(text) {
4808
- return text.trim().replace(/^[-*]\s*/, "").replace(/^["'`]+|["'`]+$/g, "").split(/\r?\n/)[0].trim();
4845
+ function normalizeExpansion(text2) {
4846
+ return text2.trim().replace(/^[-*]\s*/, "").replace(/^["'`]+|["'`]+$/g, "").split(/\r?\n/)[0].trim();
4809
4847
  }
4810
4848
  async function maybeExpandSearchQuery(query) {
4811
4849
  if (!query || !isCjkHeavy(query) || COMMAND_LIKE_QUERY.test(query)) {
@@ -5168,9 +5206,9 @@ function shouldSkipCompression(narrative, type) {
5168
5206
  if (specialChars / narrative.length > 0.35) return true;
5169
5207
  return false;
5170
5208
  }
5171
- function estimateTokens(text) {
5172
- const cjkChars = (text.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length;
5173
- const otherChars = text.length - cjkChars;
5209
+ function estimateTokens(text2) {
5210
+ const cjkChars = (text2.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length;
5211
+ const otherChars = text2.length - cjkChars;
5174
5212
  return Math.ceil(cjkChars / 1.5 + otherChars / 4);
5175
5213
  }
5176
5214
  var COMPRESS_PROMPT, COMPRESS_PROMPT_GENTLE, RERANK_PROMPT, SKIP_PATTERNS, LOW_COMPRESSION_TYPES;
@@ -5473,10 +5511,10 @@ function isEmbeddingEnabled() {
5473
5511
  function getVectorDimensions() {
5474
5512
  return embeddingEnabled ? embeddingDimensions : null;
5475
5513
  }
5476
- async function generateEmbedding(text) {
5514
+ async function generateEmbedding(text2) {
5477
5515
  const provider2 = await getEmbeddingProvider();
5478
5516
  if (!provider2) return null;
5479
- return provider2.embedInput ? provider2.embedInput({ modality: "text", text }, { intent: "document" }) : provider2.embed(text);
5517
+ return provider2.embedInput ? provider2.embedInput({ modality: "text", text: text2 }, { intent: "document" }) : provider2.embed(text2);
5480
5518
  }
5481
5519
  async function generateEmbeddingInput(input, options = {}) {
5482
5520
  const provider2 = await getEmbeddingProvider();
@@ -6322,9 +6360,9 @@ var init_entity_extractor = __esm({
6322
6360
  });
6323
6361
 
6324
6362
  // src/memory/secret-filter.ts
6325
- function applyRedaction(text) {
6326
- if (!text) return text;
6327
- let out = text;
6363
+ function applyRedaction(text2) {
6364
+ if (!text2) return text2;
6365
+ let out = text2;
6328
6366
  out = out.replace(
6329
6367
  /((?:password|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|access[_-]?key|private[_-]?key|client[_-]?secret)\s*[:=]\s*["']?)([^\s"',;\n]{6,})/gi,
6330
6368
  "$1[REDACTED]"
@@ -6338,8 +6376,8 @@ function applyRedaction(text) {
6338
6376
  out = out.replace(/\beyJ[A-Za-z0-9._-]{40,}\b/g, "[REDACTED]");
6339
6377
  return out;
6340
6378
  }
6341
- function sanitizeCredentials(text) {
6342
- return applyRedaction(text);
6379
+ function sanitizeCredentials(text2) {
6380
+ return applyRedaction(text2);
6343
6381
  }
6344
6382
  var init_secret_filter = __esm({
6345
6383
  "src/memory/secret-filter.ts"() {
@@ -6390,8 +6428,8 @@ function clampPositiveInteger(value, fallback) {
6390
6428
  return Math.max(1, Math.floor(value));
6391
6429
  }
6392
6430
  function errorText(error) {
6393
- const text = error instanceof Error ? error.message : String(error);
6394
- return sanitizeCredentials(text).slice(0, MAX_ERROR_LENGTH);
6431
+ const text2 = error instanceof Error ? error.message : String(error);
6432
+ return sanitizeCredentials(text2).slice(0, MAX_ERROR_LENGTH);
6395
6433
  }
6396
6434
  function normalizedKinds(kinds) {
6397
6435
  if (!kinds) return void 0;
@@ -7340,9 +7378,9 @@ __export(consolidation_exports, {
7340
7378
  executeConsolidation: () => executeConsolidation,
7341
7379
  findConsolidationCandidates: () => findConsolidationCandidates
7342
7380
  });
7343
- function tokenize(text) {
7381
+ function tokenize(text2) {
7344
7382
  return new Set(
7345
- text.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff\s-]/g, " ").split(/\s+/).filter((t2) => t2.length > 1)
7383
+ text2.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff\s-]/g, " ").split(/\s+/).filter((t2) => t2.length > 1)
7346
7384
  );
7347
7385
  }
7348
7386
  function jaccardSimilarity(a, b) {
@@ -16300,8 +16338,8 @@ var init_headers = __esm({
16300
16338
  });
16301
16339
 
16302
16340
  // packages/ai/dist/utils/sanitize-unicode.js
16303
- function sanitizeSurrogates(text) {
16304
- return text.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "");
16341
+ function sanitizeSurrogates(text2) {
16342
+ return text2.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "");
16305
16343
  }
16306
16344
  var init_sanitize_unicode = __esm({
16307
16345
  "packages/ai/dist/utils/sanitize-unicode.js"() {
@@ -16783,9 +16821,9 @@ var init_values = __esm({
16783
16821
  }
16784
16822
  return n;
16785
16823
  };
16786
- safeJSON = (text) => {
16824
+ safeJSON = (text2) => {
16787
16825
  try {
16788
- return JSON.parse(text);
16826
+ return JSON.parse(text2);
16789
16827
  } catch (err) {
16790
16828
  return void 0;
16791
16829
  }
@@ -17948,8 +17986,8 @@ async function defaultParseResponse(client, props) {
17948
17986
  const json2 = await response.json();
17949
17987
  return addRequestID(json2, response);
17950
17988
  }
17951
- const text = await response.text();
17952
- return text;
17989
+ const text2 = await response.text();
17990
+ return text2;
17953
17991
  })();
17954
17992
  loggerFor(client).debug(`[${requestLogID}] response parsed`, formatRequestDetails({
17955
17993
  retryOfRequestLogID,
@@ -43556,9 +43594,9 @@ var init_values2 = __esm({
43556
43594
  }
43557
43595
  return n;
43558
43596
  };
43559
- safeJSON2 = (text) => {
43597
+ safeJSON2 = (text2) => {
43560
43598
  try {
43561
- return JSON.parse(text);
43599
+ return JSON.parse(text2);
43562
43600
  } catch (err) {
43563
43601
  return void 0;
43564
43602
  }
@@ -44332,8 +44370,8 @@ async function defaultParseResponse2(client, props) {
44332
44370
  const json2 = await response.json();
44333
44371
  return addRequestID2(json2, response);
44334
44372
  }
44335
- const text = await response.text();
44336
- return text;
44373
+ const text2 = await response.text();
44374
+ return text2;
44337
44375
  })();
44338
44376
  loggerFor2(client).debug(`[${requestLogID}] response parsed`, formatRequestDetails2({
44339
44377
  retryOfRequestLogID,
@@ -50926,9 +50964,9 @@ function decodeSseLine(line, state) {
50926
50964
  }
50927
50965
  return null;
50928
50966
  }
50929
- function nextLineBreakIndex(text) {
50930
- const carriageReturnIndex = text.indexOf("\r");
50931
- const newlineIndex = text.indexOf("\n");
50967
+ function nextLineBreakIndex(text2) {
50968
+ const carriageReturnIndex = text2.indexOf("\r");
50969
+ const newlineIndex = text2.indexOf("\n");
50932
50970
  if (carriageReturnIndex === -1) {
50933
50971
  return newlineIndex;
50934
50972
  }
@@ -50937,18 +50975,18 @@ function nextLineBreakIndex(text) {
50937
50975
  }
50938
50976
  return Math.min(carriageReturnIndex, newlineIndex);
50939
50977
  }
50940
- function consumeLine(text) {
50941
- const lineBreakIndex = nextLineBreakIndex(text);
50978
+ function consumeLine(text2) {
50979
+ const lineBreakIndex = nextLineBreakIndex(text2);
50942
50980
  if (lineBreakIndex === -1) {
50943
50981
  return null;
50944
50982
  }
50945
50983
  let nextIndex = lineBreakIndex + 1;
50946
- if (text[lineBreakIndex] === "\r" && text[nextIndex] === "\n") {
50984
+ if (text2[lineBreakIndex] === "\r" && text2[nextIndex] === "\n") {
50947
50985
  nextIndex += 1;
50948
50986
  }
50949
50987
  return {
50950
- line: text.slice(0, lineBreakIndex),
50951
- rest: text.slice(nextIndex)
50988
+ line: text2.slice(0, lineBreakIndex),
50989
+ rest: text2.slice(nextIndex)
50952
50990
  };
50953
50991
  }
50954
50992
  async function* iterateSseMessages(body, signal) {
@@ -53115,8 +53153,8 @@ var require_common = __commonJS({
53115
53153
  }
53116
53154
  function redactString(obj, key) {
53117
53155
  if (typeof obj === "object" && obj !== null && typeof obj[key] === "string") {
53118
- const text = obj[key];
53119
- if (/grant_type=/i.test(text) || /assertion=/i.test(text) || /secret/i.test(text)) {
53156
+ const text2 = obj[key];
53157
+ if (/grant_type=/i.test(text2) || /assertion=/i.test(text2) || /secret/i.test(text2)) {
53120
53158
  obj[key] = REDACT;
53121
53159
  }
53122
53160
  }
@@ -59944,8 +59982,8 @@ var init_body = __esm({
59944
59982
  * @return Promise
59945
59983
  */
59946
59984
  async json() {
59947
- const text = await this.text();
59948
- return JSON.parse(text);
59985
+ const text2 = await this.text();
59986
+ return JSON.parse(text2);
59949
59987
  }
59950
59988
  /**
59951
59989
  * Decode response as text
@@ -61097,7 +61135,7 @@ var require_gaxios = __commonJS({
61097
61135
  var retry_js_1 = require_retry3();
61098
61136
  var stream_1 = __require("stream");
61099
61137
  var interceptor_js_1 = require_interceptor();
61100
- var randomUUID10 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
61138
+ var randomUUID11 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
61101
61139
  var HTTP_STATUS_NO_CONTENT = 204;
61102
61140
  var Gaxios = class {
61103
61141
  agentCache = /* @__PURE__ */ new Map();
@@ -61370,7 +61408,7 @@ var require_gaxios = __commonJS({
61370
61408
  */
61371
61409
  ["Blob", "File", "FormData"].includes(opts.data?.constructor?.name || "");
61372
61410
  if (opts.multipart?.length) {
61373
- const boundary = await randomUUID10();
61411
+ const boundary = await randomUUID11();
61374
61412
  preparedHeaders.set("content-type", `multipart/related; boundary=${boundary}`);
61375
61413
  opts.body = stream_1.Readable.from(this.getMultipartRequest(opts.multipart, boundary));
61376
61414
  } else if (shouldDirectlyPassData) {
@@ -63121,18 +63159,18 @@ var require_parse = __commonJS({
63121
63159
  n: "\n",
63122
63160
  r: "\r",
63123
63161
  t: " "
63124
- }, text, error = function(m2) {
63162
+ }, text2, error = function(m2) {
63125
63163
  throw {
63126
63164
  name: "SyntaxError",
63127
63165
  message: m2,
63128
63166
  at,
63129
- text
63167
+ text: text2
63130
63168
  };
63131
63169
  }, next = function(c) {
63132
63170
  if (c && c !== ch) {
63133
63171
  error("Expected '" + c + "' instead of '" + ch + "'");
63134
63172
  }
63135
- ch = text.charAt(at);
63173
+ ch = text2.charAt(at);
63136
63174
  at += 1;
63137
63175
  return ch;
63138
63176
  }, number44 = function() {
@@ -63179,12 +63217,12 @@ var require_parse = __commonJS({
63179
63217
  var startAt = at;
63180
63218
  while (next()) {
63181
63219
  if (ch === '"') {
63182
- if (at - 1 > startAt) string474 += text.substring(startAt, at - 1);
63220
+ if (at - 1 > startAt) string474 += text2.substring(startAt, at - 1);
63183
63221
  next();
63184
63222
  return string474;
63185
63223
  }
63186
63224
  if (ch === "\\") {
63187
- if (at - 1 > startAt) string474 += text.substring(startAt, at - 1);
63225
+ if (at - 1 > startAt) string474 += text2.substring(startAt, at - 1);
63188
63226
  next();
63189
63227
  if (ch === "u") {
63190
63228
  uffff = 0;
@@ -63317,7 +63355,7 @@ var require_parse = __commonJS({
63317
63355
  };
63318
63356
  return function(source, reviver) {
63319
63357
  var result;
63320
- text = source + "";
63358
+ text2 = source + "";
63321
63359
  at = 0;
63322
63360
  ch = " ";
63323
63361
  result = value();
@@ -64229,8 +64267,8 @@ var require_crypto = __commonJS({
64229
64267
  const result = new TextDecoder().decode(uint8array);
64230
64268
  return result;
64231
64269
  }
64232
- encodeBase64StringUtf8(text) {
64233
- const uint8array = new TextEncoder().encode(text);
64270
+ encodeBase64StringUtf8(text2) {
64271
+ const uint8array = new TextEncoder().encode(text2);
64234
64272
  const result = base64js.fromByteArray(uint8array);
64235
64273
  return result;
64236
64274
  }
@@ -64299,8 +64337,8 @@ var require_crypto2 = __commonJS({
64299
64337
  decodeBase64StringUtf8(base64) {
64300
64338
  return Buffer.from(base64, "base64").toString("utf-8");
64301
64339
  }
64302
- encodeBase64StringUtf8(text) {
64303
- return Buffer.from(text, "utf-8").toString("base64");
64340
+ encodeBase64StringUtf8(text2) {
64341
+ return Buffer.from(text2, "utf-8").toString("base64");
64304
64342
  }
64305
64343
  /**
64306
64344
  * Computes the SHA-256 hash of the provided string.
@@ -84745,8 +84783,8 @@ async function defaultParseResponse3(client, props) {
84745
84783
  const json2 = await response.json();
84746
84784
  return json2;
84747
84785
  }
84748
- const text = await response.text();
84749
- return text;
84786
+ const text2 = await response.text();
84787
+ return text2;
84750
84788
  })();
84751
84789
  loggerFor3(client).debug(`[${requestLogID}] response parsed`, formatRequestDetails3({
84752
84790
  retryOfRequestLogID,
@@ -86323,7 +86361,7 @@ var init_node = __esm({
86323
86361
  if (this.candidates && this.candidates.length > 1) {
86324
86362
  console.warn("there are multiple candidates in the response, returning text from the first one.");
86325
86363
  }
86326
- let text = "";
86364
+ let text2 = "";
86327
86365
  let anyTextPartText = false;
86328
86366
  const nonTextParts = [];
86329
86367
  for (const part of (_h = (_g = (_f = (_e = this.candidates) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.content) === null || _g === void 0 ? void 0 : _g.parts) !== null && _h !== void 0 ? _h : []) {
@@ -86337,13 +86375,13 @@ var init_node = __esm({
86337
86375
  continue;
86338
86376
  }
86339
86377
  anyTextPartText = true;
86340
- text += part.text;
86378
+ text2 += part.text;
86341
86379
  }
86342
86380
  }
86343
86381
  if (nonTextParts.length > 0) {
86344
86382
  console.warn(`there are non-text parts ${nonTextParts} in the response, returning concatenation of all text parts. Please refer to the non text parts for a full response from model.`);
86345
86383
  }
86346
- return anyTextPartText ? text : void 0;
86384
+ return anyTextPartText ? text2 : void 0;
86347
86385
  }
86348
86386
  /**
86349
86387
  * Returns the concatenation of all inline data parts from the first candidate
@@ -86596,7 +86634,7 @@ var init_node = __esm({
86596
86634
  */
86597
86635
  get text() {
86598
86636
  var _a5, _b, _c;
86599
- let text = "";
86637
+ let text2 = "";
86600
86638
  let anyTextPartFound = false;
86601
86639
  const nonTextParts = [];
86602
86640
  for (const part of (_c = (_b = (_a5 = this.serverContent) === null || _a5 === void 0 ? void 0 : _a5.modelTurn) === null || _b === void 0 ? void 0 : _b.parts) !== null && _c !== void 0 ? _c : []) {
@@ -86610,13 +86648,13 @@ var init_node = __esm({
86610
86648
  continue;
86611
86649
  }
86612
86650
  anyTextPartFound = true;
86613
- text += part.text;
86651
+ text2 += part.text;
86614
86652
  }
86615
86653
  }
86616
86654
  if (nonTextParts.length > 0) {
86617
86655
  console.warn(`there are non-text parts ${nonTextParts} in the response, returning concatenation of all text parts. Please refer to the non text parts for a full response from model.`);
86618
86656
  }
86619
- return anyTextPartFound ? text : void 0;
86657
+ return anyTextPartFound ? text2 : void 0;
86620
86658
  }
86621
86659
  /**
86622
86660
  * Returns the concatenation of all inline data parts from the server content if present.
@@ -91288,9 +91326,9 @@ var init_node = __esm({
91288
91326
  }
91289
91327
  return n;
91290
91328
  };
91291
- safeJSON3 = (text) => {
91329
+ safeJSON3 = (text2) => {
91292
91330
  try {
91293
- return JSON.parse(text);
91331
+ return JSON.parse(text2);
91294
91332
  } catch (err) {
91295
91333
  return void 0;
91296
91334
  }
@@ -109153,8 +109191,8 @@ function findBoundary(buf) {
109153
109191
  return null;
109154
109192
  }
109155
109193
  function parseMessage2(chunk, parse2, state, dataRequired) {
109156
- const text = new TextDecoder().decode(chunk);
109157
- const lines = text.split(/\r\n|\r|\n/);
109194
+ const text2 = new TextDecoder().decode(chunk);
109195
+ const lines = text2.split(/\r\n|\r|\n/);
109158
109196
  const dataLines = [];
109159
109197
  const ret = {};
109160
109198
  let ignore = true;
@@ -132028,10 +132066,10 @@ function formatMistralError(error) {
132028
132066
  }
132029
132067
  return safeJsonStringify(error);
132030
132068
  }
132031
- function truncateErrorText(text, maxChars) {
132032
- if (text.length <= maxChars)
132033
- return text;
132034
- return `${text.slice(0, maxChars)}... [truncated ${text.length - maxChars} chars]`;
132069
+ function truncateErrorText(text2, maxChars) {
132070
+ if (text2.length <= maxChars)
132071
+ return text2;
132072
+ return `${text2.slice(0, maxChars)}... [truncated ${text2.length - maxChars} chars]`;
132035
132073
  }
132036
132074
  function safeJsonStringify(value) {
132037
132075
  try {
@@ -132151,7 +132189,7 @@ async function consumeChatStream(model, output, stream2, mistralStream) {
132151
132189
  continue;
132152
132190
  }
132153
132191
  if (item.type === "thinking") {
132154
- const deltaText = item.thinking.map((part) => "text" in part ? part.text : "").filter((text) => text.length > 0).join("");
132192
+ const deltaText = item.thinking.map((part) => "text" in part ? part.text : "").filter((text2) => text2.length > 0).join("");
132155
132193
  const thinkingDelta = sanitizeSurrogates(deltaText);
132156
132194
  if (!thinkingDelta)
132157
132195
  continue;
@@ -132348,8 +132386,8 @@ function toChatMessages(messages, supportsImages) {
132348
132386
  }
132349
132387
  return result;
132350
132388
  }
132351
- function buildToolResultText(text, hasImages, supportsImages, isError) {
132352
- const trimmed = text.trim();
132389
+ function buildToolResultText(text2, hasImages, supportsImages, isError) {
132390
+ const trimmed = text2.trim();
132353
132391
  const errorPrefix = isError ? "[tool error] " : "";
132354
132392
  if (trimmed.length > 0) {
132355
132393
  const imageSuffix = hasImages && !supportsImages ? "\n[tool image omitted: model does not support images]" : "";
@@ -133150,14 +133188,14 @@ async function* parseWebSocket(socket, signal, idleTimeoutMs) {
133150
133188
  };
133151
133189
  const onMessage = (event) => {
133152
133190
  void (async () => {
133153
- let text = null;
133191
+ let text2 = null;
133154
133192
  try {
133155
133193
  if (!event || typeof event !== "object" || !("data" in event))
133156
133194
  return;
133157
- text = await decodeWebSocketData(event.data);
133158
- if (!text)
133195
+ text2 = await decodeWebSocketData(event.data);
133196
+ if (!text2)
133159
133197
  return;
133160
- const parsed = JSON.parse(text);
133198
+ const parsed = JSON.parse(text2);
133161
133199
  const type = typeof parsed.type === "string" ? parsed.type : "";
133162
133200
  if (type === "response.completed" || type === "response.done" || type === "response.incomplete") {
133163
133201
  sawCompletion = true;
@@ -133168,7 +133206,7 @@ async function* parseWebSocket(socket, signal, idleTimeoutMs) {
133168
133206
  } catch (cause) {
133169
133207
  failed = new CodexProtocolError(`Invalid Codex WebSocket JSON: ${formatThrownValue(cause)}`, {
133170
133208
  cause,
133171
- payload: text
133209
+ payload: text2
133172
133210
  });
133173
133211
  done = true;
133174
133212
  wake();
@@ -141332,11 +141370,11 @@ function videoRequestConfig(input) {
141332
141370
  };
141333
141371
  }
141334
141372
  async function readMiniMaxJson(response) {
141335
- const text = await response.text();
141373
+ const text2 = await response.text();
141336
141374
  let body = {};
141337
- if (text) {
141375
+ if (text2) {
141338
141376
  try {
141339
- const parsed = JSON.parse(text);
141377
+ const parsed = JSON.parse(text2);
141340
141378
  if (isRecord(parsed)) body = parsed;
141341
141379
  } catch {
141342
141380
  throw new Error("MiniMax video API returned invalid JSON");
@@ -142157,6 +142195,7 @@ __export(store_exports, {
142157
142195
  CodeGraphStore: () => CodeGraphStore
142158
142196
  });
142159
142197
  import { randomUUID as randomUUID6 } from "crypto";
142198
+ import { posix as pathPosix } from "path";
142160
142199
  function rowToFile(row) {
142161
142200
  return {
142162
142201
  id: row.id,
@@ -142207,6 +142246,26 @@ function rowToEdge(row) {
142207
142246
  ...row.sourceEpoch != null ? { sourceEpoch: Number(row.sourceEpoch) } : {}
142208
142247
  };
142209
142248
  }
142249
+ function resolveRelativeImportTarget(sourcePath, evidence, fileByPath) {
142250
+ const target = evidence?.trim().replace(/\\/g, "/");
142251
+ if (!target?.startsWith(".")) return void 0;
142252
+ const base = normalizeCodePath(pathPosix.normalize(pathPosix.join(pathPosix.dirname(sourcePath), target)));
142253
+ if (!base || base === ".." || base.startsWith("../")) return void 0;
142254
+ const candidates = [base];
142255
+ const extensionMatch = base.match(/(\.[A-Za-z0-9]+)$/);
142256
+ const hasExtension = Boolean(extensionMatch);
142257
+ if (!hasExtension) {
142258
+ const extensions = [...new Set([...fileByPath.keys()].map((path19) => path19.match(/(\.[A-Za-z0-9]+)$/)?.[1]).filter((extension3) => Boolean(extension3)))];
142259
+ for (const extension3 of extensions) {
142260
+ candidates.push(`${base}${extension3}`, `${base}/index${extension3}`);
142261
+ }
142262
+ } else {
142263
+ const sourceStem = base.slice(0, -extensionMatch[1].length);
142264
+ const sourceExtensions = extensionMatch[1] === ".js" ? [".ts", ".tsx", ".js", ".jsx"] : extensionMatch[1] === ".mjs" ? [".mts", ".mjs"] : extensionMatch[1] === ".cjs" ? [".cts", ".cjs"] : [];
142265
+ for (const extension3 of sourceExtensions) candidates.push(`${sourceStem}${extension3}`);
142266
+ }
142267
+ return candidates.map((candidate) => fileByPath.get(candidate)).find((file) => Boolean(file));
142268
+ }
142210
142269
  function rowToRef(row) {
142211
142270
  return {
142212
142271
  id: row.id,
@@ -142267,6 +142326,18 @@ function rowToSnapshot(row) {
142267
142326
  ...row.previousSnapshotId ? { previousSnapshotId: row.previousSnapshotId } : {}
142268
142327
  };
142269
142328
  }
142329
+ function rowToSnapshotFile(row) {
142330
+ return {
142331
+ snapshotId: row.snapshotId,
142332
+ projectId: row.projectId,
142333
+ fileId: row.fileId,
142334
+ path: row.path,
142335
+ contentHash: row.contentHash
142336
+ };
142337
+ }
142338
+ function snapshotIsIncomplete(snapshot) {
142339
+ return snapshot.completeness.skippedOversizedFiles > 0 || (snapshot.completeness.unreadableFiles ?? 0) > 0 || snapshot.completeness.removalScanDeferred;
142340
+ }
142270
142341
  var CodeGraphStore;
142271
142342
  var init_store = __esm({
142272
142343
  "src/codegraph/store.ts"() {
@@ -142438,6 +142509,7 @@ var init_store = __esm({
142438
142509
  }
142439
142510
  });
142440
142511
  tx();
142512
+ this.reconcileLocalImportTargets(projectId);
142441
142513
  }
142442
142514
  /**
142443
142515
  * Reconcile only files whose source changed plus files that disappeared.
@@ -142447,7 +142519,10 @@ var init_store = __esm({
142447
142519
  const changed = input.changed.filter((delta) => delta.file.projectId === projectId);
142448
142520
  const metadataOnly = (input.metadataOnly ?? []).filter((file) => file.projectId === projectId);
142449
142521
  const removedFileIds = input.removedFileIds ?? [];
142450
- if (changed.length === 0 && metadataOnly.length === 0 && removedFileIds.length === 0) return;
142522
+ if (changed.length === 0 && metadataOnly.length === 0 && removedFileIds.length === 0) {
142523
+ this.reconcileLocalImportTargets(projectId);
142524
+ return;
142525
+ }
142451
142526
  const staleRefsForFile = this.db.prepare(`
142452
142527
  UPDATE observation_code_refs
142453
142528
  SET status = 'stale', updatedAt = ?
@@ -142457,9 +142532,18 @@ var init_store = __esm({
142457
142532
  )
142458
142533
  )
142459
142534
  `);
142460
- const deleteEdgesForFile = this.db.prepare(`
142535
+ const deleteEdgesFromFile = this.db.prepare(`
142536
+ DELETE FROM code_edges
142537
+ WHERE projectId = ? AND fromFileId = ?
142538
+ `);
142539
+ const clearImportTargetsForFile = this.db.prepare(`
142540
+ UPDATE code_edges
142541
+ SET toFileId = NULL
142542
+ WHERE projectId = ? AND toFileId = ? AND type = 'imports'
142543
+ `);
142544
+ const deleteOtherIncomingEdgesForFile = this.db.prepare(`
142461
142545
  DELETE FROM code_edges
142462
- WHERE projectId = ? AND (fromFileId = ? OR toFileId = ?)
142546
+ WHERE projectId = ? AND toFileId = ? AND type != 'imports'
142463
142547
  `);
142464
142548
  const deleteSymbolsForFile = this.db.prepare(`DELETE FROM code_symbols WHERE projectId = ? AND fileId = ?`);
142465
142549
  const deleteFile = this.db.prepare(`DELETE FROM code_files WHERE projectId = ? AND id = ?`);
@@ -142496,14 +142580,18 @@ var init_store = __esm({
142496
142580
  const staleAt = (/* @__PURE__ */ new Date()).toISOString();
142497
142581
  for (const fileId of removedFileIds) {
142498
142582
  staleRefsForFile.run(staleAt, projectId, fileId, projectId, fileId);
142499
- deleteEdgesForFile.run(projectId, fileId, fileId);
142583
+ deleteEdgesFromFile.run(projectId, fileId);
142584
+ clearImportTargetsForFile.run(projectId, fileId);
142585
+ deleteOtherIncomingEdgesForFile.run(projectId, fileId);
142500
142586
  deleteSymbolsForFile.run(projectId, fileId);
142501
142587
  deleteFile.run(projectId, fileId);
142502
142588
  }
142503
142589
  for (const delta of changed) {
142504
142590
  const fileId = delta.file.id;
142505
142591
  staleRefsForFile.run(staleAt, projectId, fileId, projectId, fileId);
142506
- deleteEdgesForFile.run(projectId, fileId, fileId);
142592
+ deleteEdgesFromFile.run(projectId, fileId);
142593
+ clearImportTargetsForFile.run(projectId, fileId);
142594
+ deleteOtherIncomingEdgesForFile.run(projectId, fileId);
142507
142595
  deleteSymbolsForFile.run(projectId, fileId);
142508
142596
  writeFile5(delta.file);
142509
142597
  for (const symbol of delta.symbols) {
@@ -142541,6 +142629,7 @@ var init_store = __esm({
142541
142629
  for (const file of metadataOnly) writeFile5(file);
142542
142630
  });
142543
142631
  tx();
142632
+ this.reconcileLocalImportTargets(projectId);
142544
142633
  }
142545
142634
  upsertObservationRefs(refs) {
142546
142635
  if (refs.length === 0) return;
@@ -142602,6 +142691,25 @@ var init_store = __esm({
142602
142691
  });
142603
142692
  tx();
142604
142693
  }
142694
+ /**
142695
+ * Lite import extraction records the original import text first. Resolve only
142696
+ * local relative imports against the current index after each refresh, so an
142697
+ * added or removed target cannot leave a fabricated file-to-file relation.
142698
+ */
142699
+ reconcileLocalImportTargets(projectId) {
142700
+ const fileById = new Map(this.listFiles(projectId).map((file) => [file.id, file]));
142701
+ const fileByPath = new Map([...fileById.values()].map((file) => [file.path, file]));
142702
+ const update2 = this.db.prepare("UPDATE code_edges SET toFileId = ? WHERE id = ?");
142703
+ const tx = this.db.transaction(() => {
142704
+ for (const edge of this.listEdges(projectId)) {
142705
+ if (edge.type !== "imports" || !edge.fromFileId) continue;
142706
+ const source = fileById.get(edge.fromFileId);
142707
+ const target = source ? resolveRelativeImportTarget(source.path, edge.evidence, fileByPath) : void 0;
142708
+ update2.run(target?.id ?? null, edge.id);
142709
+ }
142710
+ });
142711
+ tx();
142712
+ }
142605
142713
  getFile(projectId, path19) {
142606
142714
  const row = this.db.prepare(`SELECT * FROM code_files WHERE projectId = ? AND path = ?`).get(projectId, normalizeCodePath(path19));
142607
142715
  return row ? rowToFile(row) : null;
@@ -142676,6 +142784,18 @@ var init_store = __esm({
142676
142784
  ORDER BY observationId, status, id
142677
142785
  `).all(projectId).map(rowToRef);
142678
142786
  }
142787
+ /** Count distinct observations whose binding to these exact files is stale. */
142788
+ countStaleObservationRefsForFiles(projectId, fileIds) {
142789
+ const ids = [...new Set(fileIds.filter(Boolean))];
142790
+ if (ids.length === 0) return 0;
142791
+ const placeholders = ids.map(() => "?").join(", ");
142792
+ const row = this.db.prepare(`
142793
+ SELECT COUNT(DISTINCT observationId) AS count
142794
+ FROM observation_code_refs
142795
+ WHERE projectId = ? AND status = 'stale' AND fileId IN (${placeholders})
142796
+ `).get(projectId, ...ids);
142797
+ return Number(row?.count ?? 0);
142798
+ }
142679
142799
  listReferencedSymbols(projectId) {
142680
142800
  return this.db.prepare(`
142681
142801
  SELECT DISTINCT symbols.*
@@ -142697,6 +142817,94 @@ var init_store = __esm({
142697
142817
  "SELECT * FROM code_state_snapshots WHERE projectId = ? ORDER BY sourceEpoch DESC LIMIT ?"
142698
142818
  ).all(projectId, safeLimit).map(rowToSnapshot);
142699
142819
  }
142820
+ listSnapshotFiles(snapshotId) {
142821
+ return this.db.prepare(`
142822
+ SELECT snapshotId, projectId, fileId, path, contentHash
142823
+ FROM code_state_snapshot_files
142824
+ WHERE snapshotId = ?
142825
+ ORDER BY path
142826
+ `).all(snapshotId).map(rowToSnapshotFile);
142827
+ }
142828
+ diffSnapshots(projectId, fromSnapshotId, toSnapshotId) {
142829
+ const snapshots = this.db.prepare(`
142830
+ SELECT * FROM code_state_snapshots WHERE id IN (?, ?)
142831
+ `).all(fromSnapshotId, toSnapshotId).map(rowToSnapshot);
142832
+ if (snapshots.length !== 2) {
142833
+ return { projectId, fromSnapshotId, toSnapshotId, available: false, reason: "missing-snapshot", changes: [] };
142834
+ }
142835
+ if (snapshots.some((snapshot) => snapshot.projectId !== projectId)) {
142836
+ return { projectId, fromSnapshotId, toSnapshotId, available: false, reason: "project-mismatch", changes: [] };
142837
+ }
142838
+ if (snapshots.some(snapshotIsIncomplete)) {
142839
+ return { projectId, fromSnapshotId, toSnapshotId, available: false, reason: "incomplete-snapshot", changes: [] };
142840
+ }
142841
+ const before = this.listSnapshotFiles(fromSnapshotId);
142842
+ const after = this.listSnapshotFiles(toSnapshotId);
142843
+ if (before.length === 0 || after.length === 0) {
142844
+ return {
142845
+ projectId,
142846
+ fromSnapshotId,
142847
+ toSnapshotId,
142848
+ available: false,
142849
+ reason: "legacy-snapshot-without-manifest",
142850
+ changes: []
142851
+ };
142852
+ }
142853
+ const beforeByPath = new Map(before.map((file) => [file.path, file]));
142854
+ const afterByPath = new Map(after.map((file) => [file.path, file]));
142855
+ const paths = [.../* @__PURE__ */ new Set([...beforeByPath.keys(), ...afterByPath.keys()])].sort();
142856
+ const changes = [];
142857
+ for (const path19 of paths) {
142858
+ const oldFile = beforeByPath.get(path19);
142859
+ const newFile = afterByPath.get(path19);
142860
+ if (!oldFile && newFile) {
142861
+ changes.push({ path: path19, kind: "added", afterHash: newFile.contentHash });
142862
+ } else if (oldFile && !newFile) {
142863
+ changes.push({ path: path19, kind: "removed", beforeHash: oldFile.contentHash });
142864
+ } else if (oldFile && newFile && oldFile.contentHash !== newFile.contentHash) {
142865
+ changes.push({ path: path19, kind: "modified", beforeHash: oldFile.contentHash, afterHash: newFile.contentHash });
142866
+ }
142867
+ }
142868
+ return { projectId, fromSnapshotId, toSnapshotId, available: true, changes };
142869
+ }
142870
+ impactSlice(projectId, changedPaths, maxRelations = 24) {
142871
+ const safeLimit = Number.isFinite(maxRelations) ? Math.max(1, Math.min(100, Math.floor(maxRelations))) : 24;
142872
+ const normalizedPaths = [...new Set(changedPaths.map(normalizeCodePath).filter(Boolean))].sort();
142873
+ const files = this.listFiles(projectId);
142874
+ const fileById = new Map(files.map((file) => [file.id, file.path]));
142875
+ const fileIdBySymbolId = new Map(this.listSymbols(projectId).map((symbol) => [symbol.id, symbol.fileId]));
142876
+ const changedIds = new Set(files.filter((file) => normalizedPaths.includes(file.path)).map((file) => file.id));
142877
+ const direct = /* @__PURE__ */ new Set();
142878
+ let relationCount = 0;
142879
+ let truncated = false;
142880
+ for (const edge of this.listEdges(projectId)) {
142881
+ const endpoints = [
142882
+ edge.fromFileId,
142883
+ edge.toFileId,
142884
+ edge.fromSymbolId ? fileIdBySymbolId.get(edge.fromSymbolId) : void 0,
142885
+ edge.toSymbolId ? fileIdBySymbolId.get(edge.toSymbolId) : void 0
142886
+ ].filter((id) => Boolean(id));
142887
+ if (!endpoints.some((id) => changedIds.has(id))) continue;
142888
+ const relatedPaths = endpoints.filter((id) => !changedIds.has(id)).map((id) => fileById.get(id)).filter((path19) => Boolean(path19));
142889
+ if (relatedPaths.length === 0) continue;
142890
+ if (relationCount >= safeLimit) {
142891
+ truncated = true;
142892
+ break;
142893
+ }
142894
+ relationCount += 1;
142895
+ for (const path19 of relatedPaths) direct.add(path19);
142896
+ }
142897
+ const snapshot = this.latestSnapshot(projectId);
142898
+ return {
142899
+ projectId,
142900
+ ...snapshot ? { snapshotId: snapshot.id } : {},
142901
+ changedPaths: normalizedPaths,
142902
+ directlyConnectedPaths: [...direct].sort(),
142903
+ relationCount,
142904
+ truncated,
142905
+ provider: snapshot?.provider ?? "lite"
142906
+ };
142907
+ }
142700
142908
  /**
142701
142909
  * Record a completed scan and mark all current structural facts with its
142702
142910
  * epoch. The snapshot is written only after refresh reconciliation succeeds,
@@ -142738,6 +142946,16 @@ var init_store = __esm({
142738
142946
  this.db.prepare(
142739
142947
  "UPDATE observation_code_refs SET snapshotId = ? WHERE projectId = ? AND status = 'current'"
142740
142948
  ).run(id, input.projectId);
142949
+ const snapshotFileRows = this.db.prepare(`
142950
+ SELECT id, projectId, path, contentHash FROM code_files WHERE projectId = ? ORDER BY path
142951
+ `).all(input.projectId);
142952
+ const insertSnapshotFile = this.db.prepare(`
142953
+ INSERT INTO code_state_snapshot_files (snapshotId, projectId, fileId, path, contentHash)
142954
+ VALUES (?, ?, ?, ?, ?)
142955
+ `);
142956
+ for (const file of snapshotFileRows) {
142957
+ insertSnapshotFile.run(id, file.projectId, file.id, file.path, file.contentHash);
142958
+ }
142741
142959
  snapshot = {
142742
142960
  ...input,
142743
142961
  id,
@@ -142903,8 +143121,8 @@ __export(lite_provider_exports, {
142903
143121
  import { createHash as createHash7 } from "crypto";
142904
143122
  import { readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
142905
143123
  import { join as join4, relative } from "path";
142906
- function hashText(text) {
142907
- return createHash7("sha256").update(text).digest("hex");
143124
+ function hashText(text2) {
143125
+ return createHash7("sha256").update(text2).digest("hex");
142908
143126
  }
142909
143127
  function extension2(path19) {
142910
143128
  const index = path19.lastIndexOf(".");
@@ -142948,19 +143166,19 @@ function walk(root, exclude, maxFiles) {
142948
143166
  visit(root);
142949
143167
  return out;
142950
143168
  }
142951
- function lineOf(text, index) {
142952
- return text.slice(0, index).split(/\r?\n/).length;
143169
+ function lineOf(text2, index) {
143170
+ return text2.slice(0, index).split(/\r?\n/).length;
142953
143171
  }
142954
- function extractSymbols(projectId, file, text, indexedAt) {
143172
+ function extractSymbols(projectId, file, text2, indexedAt) {
142955
143173
  const symbols = [];
142956
143174
  const profile = file.language ? languageProfiles[file.language] : void 0;
142957
143175
  const patterns = profile?.symbols ?? [];
142958
143176
  for (const { kind, re } of patterns) {
142959
143177
  re.lastIndex = 0;
142960
- for (const match2 of text.matchAll(re)) {
143178
+ for (const match2 of text2.matchAll(re)) {
142961
143179
  const name = match2[1];
142962
143180
  if (!name || name.includes("\n")) continue;
142963
- const startLine = lineOf(text, match2.index ?? 0);
143181
+ const startLine = lineOf(text2, match2.index ?? 0);
142964
143182
  const id = makeCodeSymbolId({ projectId, path: file.path, qualifiedName: name, kind });
142965
143183
  symbols.push({
142966
143184
  id,
@@ -142980,13 +143198,13 @@ function extractSymbols(projectId, file, text, indexedAt) {
142980
143198
  }
142981
143199
  return symbols;
142982
143200
  }
142983
- function extractImportEdges(projectId, file, text, indexedAt) {
143201
+ function extractImportEdges(projectId, file, text2, indexedAt) {
142984
143202
  const edges = [];
142985
143203
  const profile = file.language ? languageProfiles[file.language] : void 0;
142986
143204
  const imports = profile?.imports ?? [];
142987
143205
  for (const re of imports) {
142988
143206
  re.lastIndex = 0;
142989
- for (const match2 of text.matchAll(re)) {
143207
+ for (const match2 of text2.matchAll(re)) {
142990
143208
  const target = (match2[1] ?? "").trim();
142991
143209
  if (!target) continue;
142992
143210
  if (target.includes("\n")) {
@@ -143021,14 +143239,14 @@ function extractImportEdges(projectId, file, text, indexedAt) {
143021
143239
  function indexFileLite(projectId, projectRoot, abs, indexedAt, fileStat) {
143022
143240
  const rel = normalizeCodePath(relative(projectRoot, abs));
143023
143241
  try {
143024
- const text = readFileSync6(abs, "utf-8");
143242
+ const text2 = readFileSync6(abs, "utf-8");
143025
143243
  const stat4 = fileStat ?? statSync3(abs);
143026
143244
  const file = {
143027
143245
  id: makeCodeFileId(projectId, rel),
143028
143246
  projectId,
143029
143247
  path: rel,
143030
143248
  language: languageForPath(rel),
143031
- contentHash: hashText(text),
143249
+ contentHash: hashText(text2),
143032
143250
  // Preserve filesystem precision. Rounding here made same-size edits in
143033
143251
  // a short Windows timestamp window indistinguishable to refresh.
143034
143252
  mtimeMs: Number(stat4.mtimeMs),
@@ -143038,8 +143256,8 @@ function indexFileLite(projectId, projectRoot, abs, indexedAt, fileStat) {
143038
143256
  return {
143039
143257
  delta: {
143040
143258
  file,
143041
- symbols: extractSymbols(projectId, file, text, indexedAt),
143042
- edges: extractImportEdges(projectId, file, text, indexedAt)
143259
+ symbols: extractSymbols(projectId, file, text2, indexedAt),
143260
+ edges: extractImportEdges(projectId, file, text2, indexedAt)
143043
143261
  },
143044
143262
  unreadable: false
143045
143263
  };
@@ -143318,18 +143536,18 @@ __export(binder_exports, {
143318
143536
  function observationText(obs) {
143319
143537
  return [obs.title, obs.narrative, ...obs.facts ?? []].join("\n");
143320
143538
  }
143321
- function mentionsSymbol(text, symbol) {
143539
+ function mentionsSymbol(text2, symbol) {
143322
143540
  const name = symbol.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
143323
- return new RegExp(`(^|[^\\w$])${name}([^\\w$]|$)`).test(text);
143541
+ return new RegExp(`(^|[^\\w$])${name}([^\\w$]|$)`).test(text2);
143324
143542
  }
143325
- function identifierCandidates(text) {
143326
- return [...new Set(text.match(/[A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?/g) ?? [])];
143543
+ function identifierCandidates(text2) {
143544
+ return [...new Set(text2.match(/[A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?/g) ?? [])];
143327
143545
  }
143328
- function codeIdentifierCandidates(text) {
143546
+ function codeIdentifierCandidates(text2) {
143329
143547
  const explicit = /* @__PURE__ */ new Set();
143330
- for (const match2 of text.matchAll(/`([A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?)`/g)) explicit.add(match2[1]);
143331
- for (const match2 of text.matchAll(/\b([A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?)\s*\(/g)) explicit.add(match2[1]);
143332
- return identifierCandidates(text).filter((candidate) => explicit.has(candidate) || candidate.includes("::") || /[!?=]$/.test(candidate) || /^[A-Z]/.test(candidate) || /[_$]/.test(candidate) || /[a-z0-9][A-Z]/.test(candidate) || /[A-Z]{2}/.test(candidate));
143548
+ for (const match2 of text2.matchAll(/`([A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?)`/g)) explicit.add(match2[1]);
143549
+ for (const match2 of text2.matchAll(/\b([A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?)\s*\(/g)) explicit.add(match2[1]);
143550
+ return identifierCandidates(text2).filter((candidate) => explicit.has(candidate) || candidate.includes("::") || /[!?=]$/.test(candidate) || /^[A-Z]/.test(candidate) || /[_$]/.test(candidate) || /[a-z0-9][A-Z]/.test(candidate) || /[A-Z]{2}/.test(candidate));
143333
143551
  }
143334
143552
  function fileRef(projectId, obs, file) {
143335
143553
  return {
@@ -143359,7 +143577,7 @@ function symbolRef(projectId, obs, file, symbol) {
143359
143577
  }
143360
143578
  function resolveObservationCodeRefs(obs, lookup2) {
143361
143579
  const refs = /* @__PURE__ */ new Map();
143362
- const text = observationText(obs);
143580
+ const text2 = observationText(obs);
143363
143581
  const candidateFiles = /* @__PURE__ */ new Map();
143364
143582
  for (const rawPath of obs.filesModified ?? []) {
143365
143583
  const file = lookup2.findFile(normalizeCodePath(rawPath));
@@ -143369,7 +143587,7 @@ function resolveObservationCodeRefs(obs, lookup2) {
143369
143587
  refs.set(ref.id, ref);
143370
143588
  }
143371
143589
  const hintedFileIds = new Set(candidateFiles.keys());
143372
- const symbolNames = hintedFileIds.size > 0 ? identifierCandidates(text) : codeIdentifierCandidates(text);
143590
+ const symbolNames = hintedFileIds.size > 0 ? identifierCandidates(text2) : codeIdentifierCandidates(text2);
143373
143591
  const symbols = lookup2.findSymbols(symbolNames, [...hintedFileIds]);
143374
143592
  const symbolsByName = /* @__PURE__ */ new Map();
143375
143593
  for (const symbol of symbols) {
@@ -143380,7 +143598,7 @@ function resolveObservationCodeRefs(obs, lookup2) {
143380
143598
  for (const group of symbolsByName.values()) {
143381
143599
  const candidates = group.length === 1 ? group : [];
143382
143600
  for (const symbol of candidates) {
143383
- if (!mentionsSymbol(text, symbol)) continue;
143601
+ if (!mentionsSymbol(text2, symbol)) continue;
143384
143602
  const file = candidateFiles.get(symbol.fileId) ?? lookup2.findFile(symbol.path);
143385
143603
  if (!file) continue;
143386
143604
  candidateFiles.set(file.id, file);
@@ -144108,8 +144326,8 @@ function taskTokens(task) {
144108
144326
  return normalizedTokens(task);
144109
144327
  }
144110
144328
  function scoreClaim(claim, tokens) {
144111
- const text = [claim.subject, claim.predicate, claim.objectValue].join(" ").toLocaleLowerCase("en-US");
144112
- const words = normalizedTokens(text);
144329
+ const text2 = [claim.subject, claim.predicate, claim.objectValue].join(" ").toLocaleLowerCase("en-US");
144330
+ const words = normalizedTokens(text2);
144113
144331
  let matches = 0;
144114
144332
  for (const token of tokens) {
144115
144333
  if (words.has(token)) matches += 1;
@@ -145005,29 +145223,141 @@ var init_workflow_store = __esm({
145005
145223
  }
145006
145224
  });
145007
145225
 
145226
+ // src/knowledge/outcome-types.ts
145227
+ var MEMORY_OUTCOME_CANDIDATE_KINDS, MEMORY_OUTCOME_SIGNAL_KINDS;
145228
+ var init_outcome_types = __esm({
145229
+ "src/knowledge/outcome-types.ts"() {
145230
+ "use strict";
145231
+ init_esm_shims();
145232
+ MEMORY_OUTCOME_CANDIDATE_KINDS = ["claim", "durable-memory", "workflow"];
145233
+ MEMORY_OUTCOME_SIGNAL_KINDS = [
145234
+ "verification-passed",
145235
+ "verification-failed",
145236
+ "verified-reuse",
145237
+ "user-pin",
145238
+ "user-correction",
145239
+ "source-changed",
145240
+ "conflict-confirmed",
145241
+ "manual-review"
145242
+ ];
145243
+ }
145244
+ });
145245
+
145246
+ // src/knowledge/outcome-store.ts
145247
+ import { randomUUID as randomUUID10 } from "crypto";
145248
+ function optionalText4(value) {
145249
+ return typeof value === "string" && value.trim() ? value : void 0;
145250
+ }
145251
+ function text(value, field, max = 1e3) {
145252
+ const safe = sanitizeCredentials(value).replace(/\s+/g, " ").trim();
145253
+ if (!safe) throw new Error("Outcome signal " + field + " is required.");
145254
+ if (safe.length > max) throw new Error("Outcome signal " + field + " is too long.");
145255
+ return safe;
145256
+ }
145257
+ function oneOf(value, values, field) {
145258
+ if (typeof value !== "string" || !values.includes(value)) {
145259
+ throw new Error("Outcome signal " + field + " is invalid.");
145260
+ }
145261
+ return value;
145262
+ }
145263
+ function rowToSignal(row) {
145264
+ return {
145265
+ id: row.id,
145266
+ projectId: row.projectId,
145267
+ candidateKind: row.candidateKind,
145268
+ candidateId: row.candidateId,
145269
+ kind: row.kind,
145270
+ sourceRef: row.sourceRef,
145271
+ ...optionalText4(row.snapshotId) ? { snapshotId: row.snapshotId } : {},
145272
+ ...optionalText4(row.detail) ? { detail: row.detail } : {},
145273
+ observedAt: row.observedAt
145274
+ };
145275
+ }
145276
+ var OutcomeStore;
145277
+ var init_outcome_store = __esm({
145278
+ "src/knowledge/outcome-store.ts"() {
145279
+ "use strict";
145280
+ init_esm_shims();
145281
+ init_secret_filter();
145282
+ init_sqlite_db();
145283
+ init_outcome_types();
145284
+ OutcomeStore = class {
145285
+ db = null;
145286
+ async init(dataDir) {
145287
+ this.db = getDatabase(dataDir);
145288
+ }
145289
+ record(input) {
145290
+ const signal = {
145291
+ id: randomUUID10(),
145292
+ projectId: text(input.projectId, "project id"),
145293
+ candidateKind: oneOf(input.candidateKind, MEMORY_OUTCOME_CANDIDATE_KINDS, "candidate kind"),
145294
+ candidateId: text(input.candidateId, "candidate id"),
145295
+ kind: oneOf(input.kind, MEMORY_OUTCOME_SIGNAL_KINDS, "kind"),
145296
+ sourceRef: text(input.sourceRef, "source reference"),
145297
+ ...input.snapshotId ? { snapshotId: text(input.snapshotId, "snapshot id") } : {},
145298
+ ...input.detail ? { detail: text(input.detail, "detail") } : {},
145299
+ observedAt: input.observedAt ?? (/* @__PURE__ */ new Date()).toISOString()
145300
+ };
145301
+ this.db.prepare(`
145302
+ INSERT INTO memory_outcome_signals (
145303
+ id, projectId, candidateKind, candidateId, kind, sourceRef, snapshotId, detail, observedAt
145304
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
145305
+ `).run(
145306
+ signal.id,
145307
+ signal.projectId,
145308
+ signal.candidateKind,
145309
+ signal.candidateId,
145310
+ signal.kind,
145311
+ signal.sourceRef,
145312
+ signal.snapshotId ?? null,
145313
+ signal.detail ?? null,
145314
+ signal.observedAt
145315
+ );
145316
+ return signal;
145317
+ }
145318
+ latestForCandidates(projectId, candidateKind, candidateIds) {
145319
+ const ids = [...new Set(candidateIds.filter(Boolean))];
145320
+ if (ids.length === 0) return /* @__PURE__ */ new Map();
145321
+ const placeholders = ids.map(() => "?").join(", ");
145322
+ const rows = this.db.prepare(`
145323
+ SELECT * FROM memory_outcome_signals
145324
+ WHERE projectId = ? AND candidateKind = ? AND candidateId IN (${placeholders})
145325
+ ORDER BY observedAt DESC, id DESC
145326
+ `).all(projectId, candidateKind, ...ids);
145327
+ const latest = /* @__PURE__ */ new Map();
145328
+ for (const row of rows) {
145329
+ const signal = rowToSignal(row);
145330
+ if (!latest.has(signal.candidateId)) latest.set(signal.candidateId, signal);
145331
+ }
145332
+ return latest;
145333
+ }
145334
+ };
145335
+ }
145336
+ });
145337
+
145008
145338
  // src/codegraph/task-lens.ts
145009
- function containsTaskKeyword(text, keyword) {
145339
+ function containsTaskKeyword(text2, keyword) {
145010
145340
  const matcher = /^[a-z0-9_-]+$/i.test(keyword) ? new RegExp(`(^|[^a-z0-9_-])${keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?=[^a-z0-9_-]|$)`, "gi") : new RegExp(keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi");
145011
145341
  let match2;
145012
- while ((match2 = matcher.exec(text)) !== null) {
145342
+ while ((match2 = matcher.exec(text2)) !== null) {
145013
145343
  const keywordIndex = match2.index + (match2[1]?.length ?? 0);
145014
- if (!isNegatedTaskKeyword(text, keywordIndex)) return true;
145344
+ if (!isNegatedTaskKeyword(text2, keywordIndex)) return true;
145015
145345
  }
145016
145346
  return false;
145017
145347
  }
145018
- function isNegatedTaskKeyword(text, keywordIndex) {
145348
+ function isNegatedTaskKeyword(text2, keywordIndex) {
145019
145349
  const boundary = Math.max(
145020
- text.lastIndexOf(".", keywordIndex - 1),
145021
- text.lastIndexOf("!", keywordIndex - 1),
145022
- text.lastIndexOf("?", keywordIndex - 1),
145023
- text.lastIndexOf(";", keywordIndex - 1),
145024
- text.lastIndexOf("\n", keywordIndex - 1),
145025
- text.lastIndexOf("\u3002", keywordIndex - 1),
145026
- text.lastIndexOf("\uFF01", keywordIndex - 1),
145027
- text.lastIndexOf("\uFF1F", keywordIndex - 1),
145028
- text.lastIndexOf("\uFF1B", keywordIndex - 1)
145350
+ text2.lastIndexOf(".", keywordIndex - 1),
145351
+ text2.lastIndexOf("!", keywordIndex - 1),
145352
+ text2.lastIndexOf("?", keywordIndex - 1),
145353
+ text2.lastIndexOf(";", keywordIndex - 1),
145354
+ text2.lastIndexOf("\n", keywordIndex - 1),
145355
+ text2.lastIndexOf("\u3002", keywordIndex - 1),
145356
+ text2.lastIndexOf("\uFF01", keywordIndex - 1),
145357
+ text2.lastIndexOf("\uFF1F", keywordIndex - 1),
145358
+ text2.lastIndexOf("\uFF1B", keywordIndex - 1)
145029
145359
  );
145030
- let prefix = text.slice(boundary + 1, keywordIndex).toLowerCase();
145360
+ let prefix = text2.slice(boundary + 1, keywordIndex).toLowerCase();
145031
145361
  const contrast = /(?:,|\uff0c)\s*(?:but|however|instead|yet|\u4f46\u662f|\u4f46|\u800c\u662f)\s*/gi;
145032
145362
  let contrastMatch;
145033
145363
  let contrastEnd = -1;
@@ -145086,7 +145416,7 @@ function requiredText(data, key) {
145086
145416
  }
145087
145417
  return value.trim();
145088
145418
  }
145089
- function optionalText4(data, key) {
145419
+ function optionalText5(data, key) {
145090
145420
  const value = data[key];
145091
145421
  if (value === void 0 || value === null || value === "") return void 0;
145092
145422
  if (typeof value !== "string") throw new Error("workflow frontmatter field " + key + " must be text");
@@ -145129,11 +145459,11 @@ function phaseFromValue(value, index) {
145129
145459
  }
145130
145460
  const data = value;
145131
145461
  const title = requiredText(data, "title");
145132
- const phaseId = optionalText4(data, "id") ?? slug(title) + "-" + (index + 1);
145462
+ const phaseId = optionalText5(data, "id") ?? slug(title) + "-" + (index + 1);
145133
145463
  return {
145134
145464
  id: phaseId,
145135
145465
  title,
145136
- instructions: optionalText4(data, "instructions") ?? "",
145466
+ instructions: optionalText5(data, "instructions") ?? "",
145137
145467
  branches: optionalStringArray(data, "branches"),
145138
145468
  expectedOutputs: optionalStringArray(data, "expectedOutputs"),
145139
145469
  verificationGates: optionalStringArray(data, "verificationGates")
@@ -145242,7 +145572,7 @@ function parsedWorkflow(data, body, input) {
145242
145572
  id: requiredText(data, "id"),
145243
145573
  workspaceId: input.workspaceId,
145244
145574
  title: requiredText(data, "title"),
145245
- description: optionalText4(data, "description") ?? "",
145575
+ description: optionalText5(data, "description") ?? "",
145246
145576
  status: optionalStatus(data),
145247
145577
  version: optionalNumber(data, "version", 1),
145248
145578
  taskLenses: optionalStringArray(data, "taskLenses"),
@@ -145261,9 +145591,9 @@ function parsedWorkflow(data, body, input) {
145261
145591
  sourcePath: normalizeSourcePath(input.sourcePath),
145262
145592
  sourceHash: "",
145263
145593
  contentHash: input.contentHash,
145264
- createdAt: optionalText4(data, "createdAt") ?? now2(),
145265
- updatedAt: optionalText4(data, "updatedAt") ?? now2(),
145266
- ...optionalText4(data, "importedFrom") ? { importedFrom: optionalText4(data, "importedFrom") } : {}
145594
+ createdAt: optionalText5(data, "createdAt") ?? now2(),
145595
+ updatedAt: optionalText5(data, "updatedAt") ?? now2(),
145596
+ ...optionalText5(data, "importedFrom") ? { importedFrom: optionalText5(data, "importedFrom") } : {}
145267
145597
  };
145268
145598
  if (candidate.phases.length === 0) {
145269
145599
  throw new Error("workflow must contain at least one phase or a non-empty body");
@@ -145362,11 +145692,11 @@ async function syncCanonicalWorkflows(workspace) {
145362
145692
  return { workflows, errors };
145363
145693
  }
145364
145694
  function inferTaskLenses(value) {
145365
- const text = value.toLowerCase();
145366
- return Object.entries(LENS_TERMS).filter(([, terms]) => terms.some((term) => text.includes(term))).map(([lens]) => lens);
145695
+ const text2 = value.toLowerCase();
145696
+ return Object.entries(LENS_TERMS).filter(([, terms]) => terms.some((term) => text2.includes(term))).map(([lens]) => lens);
145367
145697
  }
145368
145698
  function taskScore(workflow, task) {
145369
- const text = task.toLowerCase();
145699
+ const text2 = task.toLowerCase();
145370
145700
  let score = 0;
145371
145701
  const reasons = [];
145372
145702
  const matchedLenses = /* @__PURE__ */ new Set();
@@ -145633,7 +145963,31 @@ async function recordWorkflowRun(input) {
145633
145963
  throw new Error("Workflow was not found for this workspace");
145634
145964
  }
145635
145965
  if (!input.run.task.trim()) throw new Error("Workflow run task is required");
145636
- return store.recordRun(input.run);
145966
+ const run = store.recordRun(input.run);
145967
+ const kind = run.outcome === "passed" && run.verificationVerdict === "passed" ? "verification-passed" : run.outcome === "failed" || run.verificationVerdict === "failed" ? "verification-failed" : void 0;
145968
+ if (!kind) return run;
145969
+ const evidence = /* @__PURE__ */ new Map();
145970
+ evidence.set(workflow.id, "workflow");
145971
+ for (const reference of run.selectedEvidence) {
145972
+ const match2 = /^(claim|durable):(.+)$/.exec(reference);
145973
+ if (!match2) continue;
145974
+ evidence.set(match2[2], match2[1] === "claim" ? "claim" : "durable-memory");
145975
+ }
145976
+ const outcomes = new OutcomeStore();
145977
+ await outcomes.init(workflowStoreDataDir(input.workspace));
145978
+ for (const [candidateId, candidateKind] of evidence) {
145979
+ outcomes.record({
145980
+ projectId: run.projectId,
145981
+ candidateKind,
145982
+ candidateId,
145983
+ kind,
145984
+ sourceRef: "workflow-run:" + run.id,
145985
+ ...run.startingSnapshotId ? { snapshotId: run.startingSnapshotId } : {},
145986
+ ...run.failureReason ? { detail: run.failureReason } : {},
145987
+ observedAt: run.completedAt ?? run.startedAt
145988
+ });
145989
+ }
145990
+ return run;
145637
145991
  }
145638
145992
  async function selectWorkspaceWorkflows(input) {
145639
145993
  const synced = await syncCanonicalWorkflows(input.workspace);
@@ -145658,6 +146012,7 @@ var init_workflows2 = __esm({
145658
146012
  init_workflow_sync();
145659
146013
  init_workspace();
145660
146014
  init_workflow_store();
146015
+ init_outcome_store();
145661
146016
  init_task_lens();
145662
146017
  KNOWN_AGENTS = [
145663
146018
  "windsurf",