@merittdev/horus 0.12.1 → 0.12.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +548 -235
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -65880,8 +65880,8 @@ init_cjs_shims();
|
|
|
65880
65880
|
|
|
65881
65881
|
// ../../packages/core/src/version.ts
|
|
65882
65882
|
init_cjs_shims();
|
|
65883
|
-
var HORUS_VERSION = true ? "0.12.
|
|
65884
|
-
var PINNED_SOURCE_VERSION = "2.0.
|
|
65883
|
+
var HORUS_VERSION = true ? "0.12.3" : "dev";
|
|
65884
|
+
var PINNED_SOURCE_VERSION = "2.0.2";
|
|
65885
65885
|
|
|
65886
65886
|
// ../../packages/core/src/evidence.ts
|
|
65887
65887
|
init_cjs_shims();
|
|
@@ -71127,6 +71127,42 @@ async function assertSourceVersionPinned() {
|
|
|
71127
71127
|
function isAnalyzed(root) {
|
|
71128
71128
|
return (0, import_node_fs3.existsSync)((0, import_node_path3.join)(root, ".horus", "source"));
|
|
71129
71129
|
}
|
|
71130
|
+
var CURRENT_STORE_BACKEND = (process.env["HORUS_SOURCE_STORAGE_BACKEND"] ?? "sqlite").trim().toLowerCase();
|
|
71131
|
+
function readSourceMeta(root) {
|
|
71132
|
+
try {
|
|
71133
|
+
const raw = (0, import_node_fs3.readFileSync)((0, import_node_path3.join)(root, ".horus", "source", "meta.json"), "utf8");
|
|
71134
|
+
const j3 = JSON.parse(raw);
|
|
71135
|
+
const stats = j3["stats"] ?? void 0;
|
|
71136
|
+
return {
|
|
71137
|
+
storeBackend: typeof j3["store_backend"] === "string" ? j3["store_backend"].toLowerCase() : void 0,
|
|
71138
|
+
embeddingsComplete: typeof j3["embeddings_complete"] === "boolean" ? j3["embeddings_complete"] : void 0,
|
|
71139
|
+
stats: stats ? { symbols: stats["symbols"], embeddings: stats["embeddings"] } : void 0
|
|
71140
|
+
};
|
|
71141
|
+
} catch {
|
|
71142
|
+
return null;
|
|
71143
|
+
}
|
|
71144
|
+
}
|
|
71145
|
+
function metaHasEmbeddings(meta) {
|
|
71146
|
+
if (meta.embeddingsComplete === false) return false;
|
|
71147
|
+
const embeddings = meta.stats?.embeddings ?? 0;
|
|
71148
|
+
const symbols = meta.stats?.symbols ?? 0;
|
|
71149
|
+
return !(symbols > 0 && embeddings === 0);
|
|
71150
|
+
}
|
|
71151
|
+
function indexNeedsReanalyze(root) {
|
|
71152
|
+
if (!isAnalyzed(root)) return null;
|
|
71153
|
+
if (CURRENT_STORE_BACKEND !== "kuzu" && (0, import_node_fs3.existsSync)((0, import_node_path3.join)(root, ".horus", "source", "kuzu"))) {
|
|
71154
|
+
return "legacy K\xF9zuDB store present";
|
|
71155
|
+
}
|
|
71156
|
+
const meta = readSourceMeta(root);
|
|
71157
|
+
if (meta === null) return null;
|
|
71158
|
+
if (meta.storeBackend && meta.storeBackend !== CURRENT_STORE_BACKEND) {
|
|
71159
|
+
return `store backend changed (${meta.storeBackend} -> ${CURRENT_STORE_BACKEND})`;
|
|
71160
|
+
}
|
|
71161
|
+
if (!metaHasEmbeddings(meta)) {
|
|
71162
|
+
return "index has no semantic embeddings (symbols present but 0 vectors)";
|
|
71163
|
+
}
|
|
71164
|
+
return null;
|
|
71165
|
+
}
|
|
71130
71166
|
async function analyzeRepo(root) {
|
|
71131
71167
|
const bin = await resolveSourceBin();
|
|
71132
71168
|
if (!bin) throw new Error("horus-source not found on PATH. Install it: curl -fsSL https://horus.sh/install.sh | bash");
|
|
@@ -71457,7 +71493,7 @@ var SourceCodeProvider = class {
|
|
|
71457
71493
|
if (n4.className) symbol.className = n4.className;
|
|
71458
71494
|
if (n4.language) symbol.language = n4.language;
|
|
71459
71495
|
const content = n4.content;
|
|
71460
|
-
const
|
|
71496
|
+
const snippet2 = typeof content === "string" ? content.slice(0, 600) : void 0;
|
|
71461
71497
|
const sourceBody = typeof content === "string" ? content.slice(0, 2e4) : void 0;
|
|
71462
71498
|
const callees = (detail.callees ?? []).map((c4) => this.serializedToSymbol(c4.node));
|
|
71463
71499
|
const callers = (detail.callers ?? []).map((c4) => this.serializedToSymbol(c4.node));
|
|
@@ -71471,7 +71507,7 @@ var SourceCodeProvider = class {
|
|
|
71471
71507
|
}));
|
|
71472
71508
|
return {
|
|
71473
71509
|
symbol,
|
|
71474
|
-
snippet,
|
|
71510
|
+
snippet: snippet2,
|
|
71475
71511
|
...sourceBody !== void 0 ? { sourceBody } : {},
|
|
71476
71512
|
callers,
|
|
71477
71513
|
callees,
|
|
@@ -93004,7 +93040,13 @@ var MEMORY_RELS = /* @__PURE__ */ new Set([
|
|
|
93004
93040
|
function isSymmetricRel(rel) {
|
|
93005
93041
|
return rel === "recurs-with";
|
|
93006
93042
|
}
|
|
93007
|
-
var INCIDENT_DERIVED_KINDS = /* @__PURE__ */ new Set([
|
|
93043
|
+
var INCIDENT_DERIVED_KINDS = /* @__PURE__ */ new Set([
|
|
93044
|
+
"confirmed-outcome",
|
|
93045
|
+
"incident-pattern",
|
|
93046
|
+
// HOR-432: auto-captured investigation memories carry the same incident-family signature/tags so
|
|
93047
|
+
// they participate in recurrence detection. CONTEXT-ONLY — never read by the scoring path.
|
|
93048
|
+
"investigation"
|
|
93049
|
+
]);
|
|
93008
93050
|
function normalizeStoredSignature(sig) {
|
|
93009
93051
|
const s5 = (sig ?? "").trim();
|
|
93010
93052
|
if (s5 === "" || /^\|*$/.test(s5)) return null;
|
|
@@ -93109,6 +93151,35 @@ function createLocalMemoryStore(db) {
|
|
|
93109
93151
|
});
|
|
93110
93152
|
return row;
|
|
93111
93153
|
},
|
|
93154
|
+
async update(id, patch, opts) {
|
|
93155
|
+
const current = await getById(id);
|
|
93156
|
+
if (current === null) throw new Error(`memory item not found: ${id}`);
|
|
93157
|
+
const set = {};
|
|
93158
|
+
if (patch.claim !== void 0) {
|
|
93159
|
+
assertClaimClean(patch.claim);
|
|
93160
|
+
set.claim = patch.claim;
|
|
93161
|
+
}
|
|
93162
|
+
if (patch.confidence !== void 0) set.confidence = patch.confidence;
|
|
93163
|
+
if (patch.payload !== void 0) set.payload = patch.payload;
|
|
93164
|
+
if (Object.keys(set).length > 0) {
|
|
93165
|
+
await db.update(memoryItem).set(set).where(eq(memoryItem.id, id));
|
|
93166
|
+
}
|
|
93167
|
+
await appendAudit({
|
|
93168
|
+
memoryId: id,
|
|
93169
|
+
action: opts.action ?? "update",
|
|
93170
|
+
actor: opts.audit.actor,
|
|
93171
|
+
fromStatus: null,
|
|
93172
|
+
toStatus: null,
|
|
93173
|
+
note: opts.audit.note ?? null,
|
|
93174
|
+
detail: {
|
|
93175
|
+
...opts.detection ? { detection: opts.detection } : {},
|
|
93176
|
+
...opts.detail ?? {}
|
|
93177
|
+
}
|
|
93178
|
+
});
|
|
93179
|
+
const updated = await getById(id);
|
|
93180
|
+
if (updated === null) throw new Error(`memory item vanished during update: ${id}`);
|
|
93181
|
+
return updated;
|
|
93182
|
+
},
|
|
93112
93183
|
get: getById,
|
|
93113
93184
|
async query(q2) {
|
|
93114
93185
|
const repo = q2.repo.trim();
|
|
@@ -93296,6 +93367,395 @@ function createLocalMemoryStore(db) {
|
|
|
93296
93367
|
};
|
|
93297
93368
|
}
|
|
93298
93369
|
|
|
93370
|
+
// ../../packages/engine/src/auto-investigation-memory.ts
|
|
93371
|
+
init_cjs_shims();
|
|
93372
|
+
|
|
93373
|
+
// ../../packages/engine/src/memory-detect.ts
|
|
93374
|
+
init_cjs_shims();
|
|
93375
|
+
var import_node_crypto3 = require("crypto");
|
|
93376
|
+
|
|
93377
|
+
// ../../packages/engine/src/memory-store.ts
|
|
93378
|
+
init_cjs_shims();
|
|
93379
|
+
var HIDDEN_STATUSES = [
|
|
93380
|
+
"forgotten",
|
|
93381
|
+
"deprecated",
|
|
93382
|
+
"contradicted"
|
|
93383
|
+
];
|
|
93384
|
+
function tokenize2(text2) {
|
|
93385
|
+
const seen = /* @__PURE__ */ new Set();
|
|
93386
|
+
const out2 = [];
|
|
93387
|
+
for (const raw of text2.toLowerCase().split(/[^a-z0-9]+/)) {
|
|
93388
|
+
if (raw !== "" && !seen.has(raw)) {
|
|
93389
|
+
seen.add(raw);
|
|
93390
|
+
out2.push(raw);
|
|
93391
|
+
}
|
|
93392
|
+
}
|
|
93393
|
+
return out2;
|
|
93394
|
+
}
|
|
93395
|
+
function scopeSpecificity(scope) {
|
|
93396
|
+
if (scope.startsWith("symbol:")) return 3;
|
|
93397
|
+
if (scope.startsWith("module:")) return 2;
|
|
93398
|
+
if (scope === "repo") return 1;
|
|
93399
|
+
return 0;
|
|
93400
|
+
}
|
|
93401
|
+
var NoopVectorIndex = class {
|
|
93402
|
+
entries = /* @__PURE__ */ new Map();
|
|
93403
|
+
async upsert(i3) {
|
|
93404
|
+
this.entries.set(i3.memoryId, {
|
|
93405
|
+
memoryId: i3.memoryId,
|
|
93406
|
+
repo: i3.repo,
|
|
93407
|
+
scope: i3.scope,
|
|
93408
|
+
tokens: tokenize2(i3.claim)
|
|
93409
|
+
});
|
|
93410
|
+
}
|
|
93411
|
+
async search(i3) {
|
|
93412
|
+
const repo = i3.repo.trim();
|
|
93413
|
+
if (repo === "") return [];
|
|
93414
|
+
const queryTokens = tokenize2(i3.query);
|
|
93415
|
+
if (queryTokens.length === 0) return [];
|
|
93416
|
+
const scored = [...this.entries.values()].filter((e) => e.repo === repo).map((e) => ({
|
|
93417
|
+
memoryId: e.memoryId,
|
|
93418
|
+
score: tagOverlap(queryTokens, e.tokens),
|
|
93419
|
+
specificity: scopeSpecificity(e.scope)
|
|
93420
|
+
})).filter((h3) => h3.score > 0).sort(
|
|
93421
|
+
(a2, b5) => b5.score - a2.score || b5.specificity - a2.specificity || (a2.memoryId < b5.memoryId ? -1 : a2.memoryId > b5.memoryId ? 1 : 0)
|
|
93422
|
+
);
|
|
93423
|
+
const limit = Number.isFinite(i3.limit) && i3.limit > 0 ? Math.floor(i3.limit) : scored.length;
|
|
93424
|
+
return scored.slice(0, limit).map((h3) => ({ memoryId: h3.memoryId, score: h3.score }));
|
|
93425
|
+
}
|
|
93426
|
+
async remove(memoryId) {
|
|
93427
|
+
this.entries.delete(memoryId);
|
|
93428
|
+
}
|
|
93429
|
+
};
|
|
93430
|
+
|
|
93431
|
+
// ../../packages/engine/src/memory-detect.ts
|
|
93432
|
+
var TAG_OVERLAP_MIN = 0.6;
|
|
93433
|
+
var CLAIM_JACCARD_MIN = 0.6;
|
|
93434
|
+
var MIN_CLAIM_TOKENS = 4;
|
|
93435
|
+
var INCIDENT_DERIVED_KINDS2 = /* @__PURE__ */ new Set([
|
|
93436
|
+
"confirmed-outcome",
|
|
93437
|
+
"incident-pattern"
|
|
93438
|
+
]);
|
|
93439
|
+
function usableSignature(sig) {
|
|
93440
|
+
const s5 = (sig ?? "").trim();
|
|
93441
|
+
if (s5 === "" || /^\|*$/.test(s5)) return null;
|
|
93442
|
+
const components = s5.split(/[|,]/).map((c4) => c4.trim()).filter((c4) => c4 !== "");
|
|
93443
|
+
if (components.length === 0) return null;
|
|
93444
|
+
if (components.every((c4) => isGenericTag(c4))) return null;
|
|
93445
|
+
return s5;
|
|
93446
|
+
}
|
|
93447
|
+
function sharedNonGenericTags(a2, b5) {
|
|
93448
|
+
const setB = new Set(b5);
|
|
93449
|
+
const seen = /* @__PURE__ */ new Set();
|
|
93450
|
+
const out2 = [];
|
|
93451
|
+
for (const t2 of a2) {
|
|
93452
|
+
if (setB.has(t2) && !isGenericTag(t2) && !seen.has(t2)) {
|
|
93453
|
+
seen.add(t2);
|
|
93454
|
+
out2.push(t2);
|
|
93455
|
+
}
|
|
93456
|
+
}
|
|
93457
|
+
return out2;
|
|
93458
|
+
}
|
|
93459
|
+
function recurrenceEdgeId(idA, idB) {
|
|
93460
|
+
const [lo, hi] = idA <= idB ? [idA, idB] : [idB, idA];
|
|
93461
|
+
const h3 = (0, import_node_crypto3.createHash)("sha256").update(`recurs-with\0${lo}\0${hi}`).digest("hex");
|
|
93462
|
+
return `lnk_recurs_${h3.slice(0, 32)}`;
|
|
93463
|
+
}
|
|
93464
|
+
function normalizeCause(s5) {
|
|
93465
|
+
return (s5 ?? "").trim().toLowerCase().replace(/\s+/g, " ");
|
|
93466
|
+
}
|
|
93467
|
+
function outcomeConflict(a2, b5) {
|
|
93468
|
+
const opposite = a2.resolved === "yes" && b5.resolved === "no" || a2.resolved === "no" && b5.resolved === "yes";
|
|
93469
|
+
if (opposite) {
|
|
93470
|
+
return `outcome verdicts disagree (${a2.resolved} vs ${b5.resolved})`;
|
|
93471
|
+
}
|
|
93472
|
+
const ca = normalizeCause(a2.confirmedCause);
|
|
93473
|
+
const cb = normalizeCause(b5.confirmedCause);
|
|
93474
|
+
if (ca !== "" && cb !== "" && ca !== cb) {
|
|
93475
|
+
return `confirmed causes differ ("${a2.confirmedCause}" vs "${b5.confirmedCause}")`;
|
|
93476
|
+
}
|
|
93477
|
+
return null;
|
|
93478
|
+
}
|
|
93479
|
+
function recurrenceReason(a2, b5) {
|
|
93480
|
+
const sigA = usableSignature(a2.signature);
|
|
93481
|
+
const sigB = usableSignature(b5.signature);
|
|
93482
|
+
if (sigA !== null && sigB !== null && sigA === sigB) {
|
|
93483
|
+
return `identical incident signature "${sigA}"`;
|
|
93484
|
+
}
|
|
93485
|
+
const tagsA = a2.tags ?? [];
|
|
93486
|
+
const tagsB = b5.tags ?? [];
|
|
93487
|
+
const shared = sharedNonGenericTags(tagsA, tagsB);
|
|
93488
|
+
const overlap = tagOverlap([...tagsA], [...tagsB]);
|
|
93489
|
+
if (overlap >= TAG_OVERLAP_MIN && shared.length >= 1) {
|
|
93490
|
+
return `tag overlap ${overlap.toFixed(2)} on [${shared.join(", ")}]`;
|
|
93491
|
+
}
|
|
93492
|
+
if (sigA === null && sigB === null && shared.length >= 1) {
|
|
93493
|
+
const ta = tokenize2(a2.claim);
|
|
93494
|
+
const tb = tokenize2(b5.claim);
|
|
93495
|
+
if (ta.length >= MIN_CLAIM_TOKENS && tb.length >= MIN_CLAIM_TOKENS) {
|
|
93496
|
+
const j3 = tagOverlap(ta, tb);
|
|
93497
|
+
if (j3 >= CLAIM_JACCARD_MIN) {
|
|
93498
|
+
return `claim similarity ${j3.toFixed(2)} corroborated by [${shared.join(", ")}]`;
|
|
93499
|
+
}
|
|
93500
|
+
}
|
|
93501
|
+
}
|
|
93502
|
+
return null;
|
|
93503
|
+
}
|
|
93504
|
+
function edgeKey(rel, from, to) {
|
|
93505
|
+
if (rel === "recurs-with" || rel === "contradicts") {
|
|
93506
|
+
const [lo, hi] = from <= to ? [from, to] : [to, from];
|
|
93507
|
+
return `${rel}|${lo}|${hi}`;
|
|
93508
|
+
}
|
|
93509
|
+
return `${rel}|${from}|${to}`;
|
|
93510
|
+
}
|
|
93511
|
+
async function detectMemoryEdges(store, opts, deps = {}) {
|
|
93512
|
+
const repo = opts.repo.trim();
|
|
93513
|
+
if (repo === "") return [];
|
|
93514
|
+
const items = await store.query({ repo, limit: opts.limit ?? 1e3 });
|
|
93515
|
+
const candidates = items.filter((i3) => INCIDENT_DERIVED_KINDS2.has(i3.kind));
|
|
93516
|
+
if (candidates.length < 2) return [];
|
|
93517
|
+
const existing = /* @__PURE__ */ new Set();
|
|
93518
|
+
const investigationOf = /* @__PURE__ */ new Map();
|
|
93519
|
+
for (const item of candidates) {
|
|
93520
|
+
const out2 = await store.links(item.id, { direction: "out" });
|
|
93521
|
+
for (const l2 of out2) {
|
|
93522
|
+
if (l2.toKind === "memory") {
|
|
93523
|
+
existing.add(edgeKey(l2.rel, l2.fromMemoryId, l2.toRef));
|
|
93524
|
+
} else if (l2.toKind === "incident" && l2.rel === "about-incident") {
|
|
93525
|
+
if (!investigationOf.has(item.id)) investigationOf.set(item.id, l2.toRef);
|
|
93526
|
+
}
|
|
93527
|
+
}
|
|
93528
|
+
}
|
|
93529
|
+
const proposals = [];
|
|
93530
|
+
const outcomeCache = /* @__PURE__ */ new Map();
|
|
93531
|
+
const resolveOutcome = async (investigationId) => {
|
|
93532
|
+
if (deps.outcomeFor === void 0) return null;
|
|
93533
|
+
if (outcomeCache.has(investigationId)) return outcomeCache.get(investigationId) ?? null;
|
|
93534
|
+
let v4 = null;
|
|
93535
|
+
try {
|
|
93536
|
+
v4 = await deps.outcomeFor(investigationId);
|
|
93537
|
+
} catch {
|
|
93538
|
+
v4 = null;
|
|
93539
|
+
}
|
|
93540
|
+
outcomeCache.set(investigationId, v4);
|
|
93541
|
+
return v4;
|
|
93542
|
+
};
|
|
93543
|
+
for (let i3 = 0; i3 < candidates.length; i3++) {
|
|
93544
|
+
const a2 = candidates[i3];
|
|
93545
|
+
for (let j3 = i3 + 1; j3 < candidates.length; j3++) {
|
|
93546
|
+
const b5 = candidates[j3];
|
|
93547
|
+
if (!existing.has(edgeKey("recurs-with", a2.id, b5.id))) {
|
|
93548
|
+
const reason = recurrenceReason(a2, b5);
|
|
93549
|
+
if (reason !== null) {
|
|
93550
|
+
const [lo, hi] = a2.id <= b5.id ? [a2.id, b5.id] : [b5.id, a2.id];
|
|
93551
|
+
proposals.push({
|
|
93552
|
+
id: recurrenceEdgeId(a2.id, b5.id),
|
|
93553
|
+
fromMemoryId: lo,
|
|
93554
|
+
toMemoryId: hi,
|
|
93555
|
+
rel: "recurs-with",
|
|
93556
|
+
detection: "auto:recurrence",
|
|
93557
|
+
reason
|
|
93558
|
+
});
|
|
93559
|
+
}
|
|
93560
|
+
}
|
|
93561
|
+
if (deps.outcomeFor !== void 0 && a2.kind === "confirmed-outcome" && b5.kind === "confirmed-outcome" && !existing.has(edgeKey("contradicts", a2.id, b5.id))) {
|
|
93562
|
+
const sigA = usableSignature(a2.signature);
|
|
93563
|
+
const sigB = usableSignature(b5.signature);
|
|
93564
|
+
const invA = investigationOf.get(a2.id);
|
|
93565
|
+
const invB = investigationOf.get(b5.id);
|
|
93566
|
+
if (sigA !== null && sigA === sigB && invA !== void 0 && invB !== void 0) {
|
|
93567
|
+
const oa = await resolveOutcome(invA);
|
|
93568
|
+
const ob = await resolveOutcome(invB);
|
|
93569
|
+
if (oa !== null && ob !== null) {
|
|
93570
|
+
const reason = outcomeConflict(oa, ob);
|
|
93571
|
+
if (reason !== null) {
|
|
93572
|
+
const [lo, hi] = a2.id <= b5.id ? [a2.id, b5.id] : [b5.id, a2.id];
|
|
93573
|
+
const invLo = lo === a2.id ? invA : invB;
|
|
93574
|
+
const invHi = lo === a2.id ? invB : invA;
|
|
93575
|
+
proposals.push({
|
|
93576
|
+
id: "",
|
|
93577
|
+
// directional — the store mints the id
|
|
93578
|
+
fromMemoryId: lo,
|
|
93579
|
+
toMemoryId: hi,
|
|
93580
|
+
rel: "contradicts",
|
|
93581
|
+
detection: "auto:contradiction",
|
|
93582
|
+
reason,
|
|
93583
|
+
detail: { investigationIds: [invLo, invHi] }
|
|
93584
|
+
});
|
|
93585
|
+
}
|
|
93586
|
+
}
|
|
93587
|
+
}
|
|
93588
|
+
}
|
|
93589
|
+
}
|
|
93590
|
+
}
|
|
93591
|
+
return proposals;
|
|
93592
|
+
}
|
|
93593
|
+
|
|
93594
|
+
// ../../packages/engine/src/auto-investigation-memory.ts
|
|
93595
|
+
var RECURRENCE_SCAN_LIMIT = 100;
|
|
93596
|
+
var MAX_INVESTIGATION_IDS = 20;
|
|
93597
|
+
function snippet(text2, max = 140) {
|
|
93598
|
+
const s5 = text2.trim().replace(/\s+/g, " ");
|
|
93599
|
+
return s5.length > max ? `${s5.slice(0, max - 1)}\u2026` : s5;
|
|
93600
|
+
}
|
|
93601
|
+
function autoInvestigationMemoryEnabled() {
|
|
93602
|
+
const v4 = (process.env.HORUS_AUTO_INVESTIGATION_MEMORY ?? "").trim().toLowerCase();
|
|
93603
|
+
return v4 !== "0" && v4 !== "false" && v4 !== "off" && v4 !== "no";
|
|
93604
|
+
}
|
|
93605
|
+
function deriveInvestigationFields(report) {
|
|
93606
|
+
const hint = report.input.hint;
|
|
93607
|
+
const topHyp = report.hypotheses[0];
|
|
93608
|
+
const headline = topHyp !== void 0 ? snippet(topHyp.statement || topHyp.category || report.summary) : snippet(report.summary || "(no leading hypothesis)");
|
|
93609
|
+
return {
|
|
93610
|
+
hint,
|
|
93611
|
+
claim: `Investigation: ${snippet(hint)} -> ${headline}`,
|
|
93612
|
+
confidence: report.confidence,
|
|
93613
|
+
signature: deriveSignature(report),
|
|
93614
|
+
tags: deriveTags(report),
|
|
93615
|
+
topHypothesisCategory: topHyp !== void 0 ? topHyp.category ?? null : null
|
|
93616
|
+
};
|
|
93617
|
+
}
|
|
93618
|
+
function recurrenceCountOf(payload) {
|
|
93619
|
+
if (payload !== null && typeof payload === "object") {
|
|
93620
|
+
const c4 = payload.recurrenceCount;
|
|
93621
|
+
if (typeof c4 === "number" && Number.isFinite(c4) && c4 >= 1) return Math.floor(c4);
|
|
93622
|
+
}
|
|
93623
|
+
return 1;
|
|
93624
|
+
}
|
|
93625
|
+
async function createInvestigationMemory(store, investigationId, report, audit) {
|
|
93626
|
+
const repo = report.input.repo?.trim() || null;
|
|
93627
|
+
if (repo === null) return null;
|
|
93628
|
+
const fields = deriveInvestigationFields(report);
|
|
93629
|
+
const item = {
|
|
93630
|
+
id: "",
|
|
93631
|
+
kind: "investigation",
|
|
93632
|
+
source: "investigation",
|
|
93633
|
+
scope: "repo",
|
|
93634
|
+
visibility: "private",
|
|
93635
|
+
// HONEST confidence — the report's own value, never inflated.
|
|
93636
|
+
confidence: fields.confidence,
|
|
93637
|
+
claim: fields.claim,
|
|
93638
|
+
repo,
|
|
93639
|
+
evidence: [],
|
|
93640
|
+
// Incident-family recall keys (same derivation as incident_memory) so this memory recurs.
|
|
93641
|
+
signature: fields.signature,
|
|
93642
|
+
tags: fields.tags,
|
|
93643
|
+
payload: {
|
|
93644
|
+
investigationId,
|
|
93645
|
+
hint: fields.hint,
|
|
93646
|
+
topHypothesis: fields.topHypothesisCategory,
|
|
93647
|
+
// First sighting: a single investigation is "recurred 1×". Bumped on each later recurrence.
|
|
93648
|
+
recurrenceCount: 1
|
|
93649
|
+
}
|
|
93650
|
+
};
|
|
93651
|
+
const created = await store.add(item, audit);
|
|
93652
|
+
if (investigationId !== null && investigationId.trim() !== "") {
|
|
93653
|
+
await store.addLink(
|
|
93654
|
+
{
|
|
93655
|
+
id: "",
|
|
93656
|
+
fromMemoryId: created.id,
|
|
93657
|
+
rel: "about-incident",
|
|
93658
|
+
toKind: "incident",
|
|
93659
|
+
toRef: investigationId
|
|
93660
|
+
},
|
|
93661
|
+
{ detection: "structural", audit }
|
|
93662
|
+
);
|
|
93663
|
+
}
|
|
93664
|
+
return created;
|
|
93665
|
+
}
|
|
93666
|
+
async function detectRecurrence(store, probe2, repo) {
|
|
93667
|
+
const r = repo.trim();
|
|
93668
|
+
if (r === "") return null;
|
|
93669
|
+
const existing = await store.query({
|
|
93670
|
+
repo: r,
|
|
93671
|
+
kind: ["investigation"],
|
|
93672
|
+
limit: RECURRENCE_SCAN_LIMIT
|
|
93673
|
+
});
|
|
93674
|
+
for (const candidate of existing) {
|
|
93675
|
+
if (recurrenceReason(candidate, probe2) !== null) return candidate;
|
|
93676
|
+
}
|
|
93677
|
+
return null;
|
|
93678
|
+
}
|
|
93679
|
+
async function consolidateRecurrence(store, existing, investigationId, fields, reportTime, audit) {
|
|
93680
|
+
const prev = existing.payload ?? {};
|
|
93681
|
+
const recurrenceCount = recurrenceCountOf(prev) + 1;
|
|
93682
|
+
const prevIds = Array.isArray(prev.investigationIds) ? prev.investigationIds.filter((x4) => typeof x4 === "string") : [];
|
|
93683
|
+
const seedIds = prevIds.length > 0 ? prevIds : typeof prev.investigationId === "string" ? [prev.investigationId] : [];
|
|
93684
|
+
const thisId = investigationId !== null && investigationId.trim() !== "" ? investigationId : null;
|
|
93685
|
+
const investigationIds = (thisId !== null ? [...seedIds, thisId] : seedIds).slice(
|
|
93686
|
+
-MAX_INVESTIGATION_IDS
|
|
93687
|
+
);
|
|
93688
|
+
const payload = {
|
|
93689
|
+
...prev,
|
|
93690
|
+
recurrenceCount,
|
|
93691
|
+
lastSeenAt: reportTime.toISOString(),
|
|
93692
|
+
investigationIds,
|
|
93693
|
+
// Refresh the "latest finding" provenance; keep the original first-seen id for reference.
|
|
93694
|
+
hint: fields.hint,
|
|
93695
|
+
topHypothesis: fields.topHypothesisCategory,
|
|
93696
|
+
investigationId: typeof prev.investigationId === "string" ? prev.investigationId : seedIds[0] ?? thisId
|
|
93697
|
+
};
|
|
93698
|
+
const updated = await store.update(
|
|
93699
|
+
existing.id,
|
|
93700
|
+
{
|
|
93701
|
+
claim: fields.claim,
|
|
93702
|
+
// HONEST: the LATEST report's confidence, verbatim — never inflated, never an average.
|
|
93703
|
+
confidence: fields.confidence,
|
|
93704
|
+
payload
|
|
93705
|
+
},
|
|
93706
|
+
{
|
|
93707
|
+
audit,
|
|
93708
|
+
action: "recurrence",
|
|
93709
|
+
detection: "auto:recurrence-consolidate",
|
|
93710
|
+
detail: { recurrenceCount, ...thisId !== null ? { investigationId: thisId } : {} }
|
|
93711
|
+
}
|
|
93712
|
+
);
|
|
93713
|
+
if (thisId !== null) {
|
|
93714
|
+
await store.addLink(
|
|
93715
|
+
{
|
|
93716
|
+
id: "",
|
|
93717
|
+
fromMemoryId: existing.id,
|
|
93718
|
+
rel: "about-incident",
|
|
93719
|
+
toKind: "incident",
|
|
93720
|
+
toRef: thisId
|
|
93721
|
+
},
|
|
93722
|
+
{ detection: "structural", audit }
|
|
93723
|
+
);
|
|
93724
|
+
}
|
|
93725
|
+
return updated;
|
|
93726
|
+
}
|
|
93727
|
+
async function captureInvestigationMemory(store, investigationId, report, reportTime, audit) {
|
|
93728
|
+
const repo = report.input.repo?.trim() || null;
|
|
93729
|
+
if (repo === null) return { action: "skipped", memoryId: null, recurrenceCount: 0 };
|
|
93730
|
+
const fields = deriveInvestigationFields(report);
|
|
93731
|
+
const existing = await detectRecurrence(
|
|
93732
|
+
store,
|
|
93733
|
+
{ signature: fields.signature, tags: fields.tags, claim: fields.claim },
|
|
93734
|
+
repo
|
|
93735
|
+
);
|
|
93736
|
+
if (existing !== null) {
|
|
93737
|
+
const updated = await consolidateRecurrence(
|
|
93738
|
+
store,
|
|
93739
|
+
existing,
|
|
93740
|
+
investigationId,
|
|
93741
|
+
fields,
|
|
93742
|
+
reportTime,
|
|
93743
|
+
audit
|
|
93744
|
+
);
|
|
93745
|
+
return {
|
|
93746
|
+
action: "consolidated",
|
|
93747
|
+
memoryId: updated.id,
|
|
93748
|
+
recurrenceCount: recurrenceCountOf(updated.payload)
|
|
93749
|
+
};
|
|
93750
|
+
}
|
|
93751
|
+
const created = await createInvestigationMemory(store, investigationId, report, audit);
|
|
93752
|
+
return {
|
|
93753
|
+
action: created !== null ? "created" : "skipped",
|
|
93754
|
+
memoryId: created?.id ?? null,
|
|
93755
|
+
recurrenceCount: 1
|
|
93756
|
+
};
|
|
93757
|
+
}
|
|
93758
|
+
|
|
93299
93759
|
// ../../packages/engine/src/gaps.ts
|
|
93300
93760
|
init_cjs_shims();
|
|
93301
93761
|
function detectMissingEvidence(r, connectors = {}, mode) {
|
|
@@ -93865,7 +94325,7 @@ function scoreSeed(s5, index2, hintTokens, changedFiles, hintHasCode, qualifier,
|
|
|
93865
94325
|
score -= 4;
|
|
93866
94326
|
}
|
|
93867
94327
|
score += Math.max(0, 5 - index2) * (hintHasCode ? 0.8 : 0.1);
|
|
93868
|
-
if (s5.score !== void 0) score += s5.score * (hintHasCode ? 8 :
|
|
94328
|
+
if (s5.score !== void 0) score += s5.score * (hintHasCode ? 8 : 2);
|
|
93869
94329
|
if (/\.[jt]sx?$/i.test(s5.name)) score -= 5;
|
|
93870
94330
|
if (hintTokens !== void 0 && hintTokens.length > 0) {
|
|
93871
94331
|
let hintBoost = 0;
|
|
@@ -93886,17 +94346,27 @@ function isDeprioritizedSeedPath(filePath) {
|
|
|
93886
94346
|
filePath
|
|
93887
94347
|
);
|
|
93888
94348
|
}
|
|
94349
|
+
function isAnchoredExactSeed(s5, qualifier, hintHasCode, preferNamed) {
|
|
94350
|
+
if (qualifier && qualifierBoost(s5, qualifier) >= 40) return true;
|
|
94351
|
+
if (preferNamed !== void 0 && s5.name.toLowerCase() === preferNamed.toLowerCase()) return true;
|
|
94352
|
+
if (hintHasCode === true && s5.score !== void 0 && s5.score >= 0.9) return true;
|
|
94353
|
+
return false;
|
|
94354
|
+
}
|
|
93889
94355
|
function rankSeeds(seeds, hintTokens, changedFiles, hintHasCode, qualifier, preferNamed) {
|
|
93890
94356
|
const scored = seeds.map((symbol, i3) => ({
|
|
93891
94357
|
symbol,
|
|
93892
94358
|
score: scoreSeed(symbol, i3, hintTokens, changedFiles, hintHasCode, qualifier, preferNamed),
|
|
93893
94359
|
role: seedRole(symbol),
|
|
93894
94360
|
i: i3,
|
|
93895
|
-
deprio: isDeprioritizedSeedPath(symbol.filePath)
|
|
94361
|
+
deprio: isDeprioritizedSeedPath(symbol.filePath),
|
|
94362
|
+
// HOR-430: a deprioritized candidate the request anchors on exactly is exempt from the
|
|
94363
|
+
// hard demotion below — it keeps only the soft path penalty so a strong/exact match in an
|
|
94364
|
+
// example/test file can still surface.
|
|
94365
|
+
anchored: isAnchoredExactSeed(symbol, qualifier, hintHasCode, preferNamed)
|
|
93896
94366
|
}));
|
|
93897
94367
|
const hasReal = scored.some((s5) => !s5.deprio);
|
|
93898
94368
|
if (hasReal) {
|
|
93899
|
-
for (const s5 of scored) if (s5.deprio) s5.score -= 1e3;
|
|
94369
|
+
for (const s5 of scored) if (s5.deprio && !s5.anchored) s5.score -= 1e3;
|
|
93900
94370
|
}
|
|
93901
94371
|
return scored.sort((a2, b5) => b5.score === a2.score ? a2.i - b5.i : b5.score - a2.score).map(({ symbol, score, role }) => ({ symbol, score, role }));
|
|
93902
94372
|
}
|
|
@@ -94848,8 +95318,8 @@ async function investigate(input, deps) {
|
|
|
94848
95318
|
// HOR-386 — empty-return: route to `search` for the right name (deterministic).
|
|
94849
95319
|
nextSteps: route({ command: "investigate", empty: true, query: hint })
|
|
94850
95320
|
};
|
|
94851
|
-
const
|
|
94852
|
-
if (
|
|
95321
|
+
const persistedEmpty = await persist(db, input, report2);
|
|
95322
|
+
if (persistedEmpty) report2.id = persistedEmpty.id;
|
|
94853
95323
|
return report2;
|
|
94854
95324
|
}
|
|
94855
95325
|
let label = input.service ?? hint;
|
|
@@ -96243,13 +96713,26 @@ async function investigate(input, deps) {
|
|
|
96243
96713
|
query: hint
|
|
96244
96714
|
});
|
|
96245
96715
|
report.sourceStatus = buildRuntimeSourceStatus(evidence2, connectorFlags);
|
|
96246
|
-
const
|
|
96247
|
-
|
|
96716
|
+
const persisted = await persist(db, input, report);
|
|
96717
|
+
const persistedId = persisted?.id ?? null;
|
|
96718
|
+
if (persistedId !== null) report.id = persistedId;
|
|
96248
96719
|
report.persisted = persistedId !== null;
|
|
96249
96720
|
if (persistedId !== null) {
|
|
96250
96721
|
const tags = deriveTags(report);
|
|
96251
96722
|
report.similarIncidents = await recallSimilar(db, tags, persistedId, input.repo ?? null);
|
|
96252
96723
|
await storeIncidentMemory(db, persistedId, report);
|
|
96724
|
+
const store = deps.store;
|
|
96725
|
+
if (store !== void 0 && autoInvestigationMemoryEnabled()) {
|
|
96726
|
+
try {
|
|
96727
|
+
const audit = {
|
|
96728
|
+
actor: { kind: "system" },
|
|
96729
|
+
note: "auto: investigation memory (HOR-432)"
|
|
96730
|
+
};
|
|
96731
|
+
const reportTime = persisted?.createdAt ?? /* @__PURE__ */ new Date();
|
|
96732
|
+
await captureInvestigationMemory(store, persistedId, report, reportTime, audit);
|
|
96733
|
+
} catch {
|
|
96734
|
+
}
|
|
96735
|
+
}
|
|
96253
96736
|
}
|
|
96254
96737
|
if (persistedId !== null) {
|
|
96255
96738
|
try {
|
|
@@ -96293,7 +96776,7 @@ async function persist(db, input, report) {
|
|
|
96293
96776
|
incidentInput: input,
|
|
96294
96777
|
status: "open",
|
|
96295
96778
|
summary: report.summary
|
|
96296
|
-
}).returning({ id: investigations.id });
|
|
96779
|
+
}).returning({ id: investigations.id, createdAt: investigations.createdAt });
|
|
96297
96780
|
const row = inserted[0];
|
|
96298
96781
|
if (!row) return null;
|
|
96299
96782
|
const investigationId = row.id;
|
|
@@ -96337,7 +96820,7 @@ async function persist(db, input, report) {
|
|
|
96337
96820
|
}))
|
|
96338
96821
|
);
|
|
96339
96822
|
}
|
|
96340
|
-
return investigationId;
|
|
96823
|
+
return { id: investigationId, createdAt: row.createdAt ?? null };
|
|
96341
96824
|
} catch {
|
|
96342
96825
|
return null;
|
|
96343
96826
|
}
|
|
@@ -97046,225 +97529,6 @@ function blastRadiusToJSON(r) {
|
|
|
97046
97529
|
return JSON.stringify(r, null, 2);
|
|
97047
97530
|
}
|
|
97048
97531
|
|
|
97049
|
-
// ../../packages/engine/src/memory-store.ts
|
|
97050
|
-
init_cjs_shims();
|
|
97051
|
-
var HIDDEN_STATUSES = [
|
|
97052
|
-
"forgotten",
|
|
97053
|
-
"deprecated",
|
|
97054
|
-
"contradicted"
|
|
97055
|
-
];
|
|
97056
|
-
function tokenize2(text2) {
|
|
97057
|
-
const seen = /* @__PURE__ */ new Set();
|
|
97058
|
-
const out2 = [];
|
|
97059
|
-
for (const raw of text2.toLowerCase().split(/[^a-z0-9]+/)) {
|
|
97060
|
-
if (raw !== "" && !seen.has(raw)) {
|
|
97061
|
-
seen.add(raw);
|
|
97062
|
-
out2.push(raw);
|
|
97063
|
-
}
|
|
97064
|
-
}
|
|
97065
|
-
return out2;
|
|
97066
|
-
}
|
|
97067
|
-
function scopeSpecificity(scope) {
|
|
97068
|
-
if (scope.startsWith("symbol:")) return 3;
|
|
97069
|
-
if (scope.startsWith("module:")) return 2;
|
|
97070
|
-
if (scope === "repo") return 1;
|
|
97071
|
-
return 0;
|
|
97072
|
-
}
|
|
97073
|
-
var NoopVectorIndex = class {
|
|
97074
|
-
entries = /* @__PURE__ */ new Map();
|
|
97075
|
-
async upsert(i3) {
|
|
97076
|
-
this.entries.set(i3.memoryId, {
|
|
97077
|
-
memoryId: i3.memoryId,
|
|
97078
|
-
repo: i3.repo,
|
|
97079
|
-
scope: i3.scope,
|
|
97080
|
-
tokens: tokenize2(i3.claim)
|
|
97081
|
-
});
|
|
97082
|
-
}
|
|
97083
|
-
async search(i3) {
|
|
97084
|
-
const repo = i3.repo.trim();
|
|
97085
|
-
if (repo === "") return [];
|
|
97086
|
-
const queryTokens = tokenize2(i3.query);
|
|
97087
|
-
if (queryTokens.length === 0) return [];
|
|
97088
|
-
const scored = [...this.entries.values()].filter((e) => e.repo === repo).map((e) => ({
|
|
97089
|
-
memoryId: e.memoryId,
|
|
97090
|
-
score: tagOverlap(queryTokens, e.tokens),
|
|
97091
|
-
specificity: scopeSpecificity(e.scope)
|
|
97092
|
-
})).filter((h3) => h3.score > 0).sort(
|
|
97093
|
-
(a2, b5) => b5.score - a2.score || b5.specificity - a2.specificity || (a2.memoryId < b5.memoryId ? -1 : a2.memoryId > b5.memoryId ? 1 : 0)
|
|
97094
|
-
);
|
|
97095
|
-
const limit = Number.isFinite(i3.limit) && i3.limit > 0 ? Math.floor(i3.limit) : scored.length;
|
|
97096
|
-
return scored.slice(0, limit).map((h3) => ({ memoryId: h3.memoryId, score: h3.score }));
|
|
97097
|
-
}
|
|
97098
|
-
async remove(memoryId) {
|
|
97099
|
-
this.entries.delete(memoryId);
|
|
97100
|
-
}
|
|
97101
|
-
};
|
|
97102
|
-
|
|
97103
|
-
// ../../packages/engine/src/memory-detect.ts
|
|
97104
|
-
init_cjs_shims();
|
|
97105
|
-
var import_node_crypto3 = require("crypto");
|
|
97106
|
-
var TAG_OVERLAP_MIN = 0.6;
|
|
97107
|
-
var CLAIM_JACCARD_MIN = 0.6;
|
|
97108
|
-
var MIN_CLAIM_TOKENS = 4;
|
|
97109
|
-
var INCIDENT_DERIVED_KINDS2 = /* @__PURE__ */ new Set([
|
|
97110
|
-
"confirmed-outcome",
|
|
97111
|
-
"incident-pattern"
|
|
97112
|
-
]);
|
|
97113
|
-
function usableSignature(sig) {
|
|
97114
|
-
const s5 = (sig ?? "").trim();
|
|
97115
|
-
if (s5 === "" || /^\|*$/.test(s5)) return null;
|
|
97116
|
-
const components = s5.split(/[|,]/).map((c4) => c4.trim()).filter((c4) => c4 !== "");
|
|
97117
|
-
if (components.length === 0) return null;
|
|
97118
|
-
if (components.every((c4) => isGenericTag(c4))) return null;
|
|
97119
|
-
return s5;
|
|
97120
|
-
}
|
|
97121
|
-
function sharedNonGenericTags(a2, b5) {
|
|
97122
|
-
const setB = new Set(b5);
|
|
97123
|
-
const seen = /* @__PURE__ */ new Set();
|
|
97124
|
-
const out2 = [];
|
|
97125
|
-
for (const t2 of a2) {
|
|
97126
|
-
if (setB.has(t2) && !isGenericTag(t2) && !seen.has(t2)) {
|
|
97127
|
-
seen.add(t2);
|
|
97128
|
-
out2.push(t2);
|
|
97129
|
-
}
|
|
97130
|
-
}
|
|
97131
|
-
return out2;
|
|
97132
|
-
}
|
|
97133
|
-
function recurrenceEdgeId(idA, idB) {
|
|
97134
|
-
const [lo, hi] = idA <= idB ? [idA, idB] : [idB, idA];
|
|
97135
|
-
const h3 = (0, import_node_crypto3.createHash)("sha256").update(`recurs-with\0${lo}\0${hi}`).digest("hex");
|
|
97136
|
-
return `lnk_recurs_${h3.slice(0, 32)}`;
|
|
97137
|
-
}
|
|
97138
|
-
function normalizeCause(s5) {
|
|
97139
|
-
return (s5 ?? "").trim().toLowerCase().replace(/\s+/g, " ");
|
|
97140
|
-
}
|
|
97141
|
-
function outcomeConflict(a2, b5) {
|
|
97142
|
-
const opposite = a2.resolved === "yes" && b5.resolved === "no" || a2.resolved === "no" && b5.resolved === "yes";
|
|
97143
|
-
if (opposite) {
|
|
97144
|
-
return `outcome verdicts disagree (${a2.resolved} vs ${b5.resolved})`;
|
|
97145
|
-
}
|
|
97146
|
-
const ca = normalizeCause(a2.confirmedCause);
|
|
97147
|
-
const cb = normalizeCause(b5.confirmedCause);
|
|
97148
|
-
if (ca !== "" && cb !== "" && ca !== cb) {
|
|
97149
|
-
return `confirmed causes differ ("${a2.confirmedCause}" vs "${b5.confirmedCause}")`;
|
|
97150
|
-
}
|
|
97151
|
-
return null;
|
|
97152
|
-
}
|
|
97153
|
-
function recurrenceReason(a2, b5) {
|
|
97154
|
-
const sigA = usableSignature(a2.signature);
|
|
97155
|
-
const sigB = usableSignature(b5.signature);
|
|
97156
|
-
if (sigA !== null && sigB !== null && sigA === sigB) {
|
|
97157
|
-
return `identical incident signature "${sigA}"`;
|
|
97158
|
-
}
|
|
97159
|
-
const tagsA = a2.tags ?? [];
|
|
97160
|
-
const tagsB = b5.tags ?? [];
|
|
97161
|
-
const shared = sharedNonGenericTags(tagsA, tagsB);
|
|
97162
|
-
const overlap = tagOverlap([...tagsA], [...tagsB]);
|
|
97163
|
-
if (overlap >= TAG_OVERLAP_MIN && shared.length >= 1) {
|
|
97164
|
-
return `tag overlap ${overlap.toFixed(2)} on [${shared.join(", ")}]`;
|
|
97165
|
-
}
|
|
97166
|
-
if (sigA === null && sigB === null && shared.length >= 1) {
|
|
97167
|
-
const ta = tokenize2(a2.claim);
|
|
97168
|
-
const tb = tokenize2(b5.claim);
|
|
97169
|
-
if (ta.length >= MIN_CLAIM_TOKENS && tb.length >= MIN_CLAIM_TOKENS) {
|
|
97170
|
-
const j3 = tagOverlap(ta, tb);
|
|
97171
|
-
if (j3 >= CLAIM_JACCARD_MIN) {
|
|
97172
|
-
return `claim similarity ${j3.toFixed(2)} corroborated by [${shared.join(", ")}]`;
|
|
97173
|
-
}
|
|
97174
|
-
}
|
|
97175
|
-
}
|
|
97176
|
-
return null;
|
|
97177
|
-
}
|
|
97178
|
-
function edgeKey(rel, from, to) {
|
|
97179
|
-
if (rel === "recurs-with" || rel === "contradicts") {
|
|
97180
|
-
const [lo, hi] = from <= to ? [from, to] : [to, from];
|
|
97181
|
-
return `${rel}|${lo}|${hi}`;
|
|
97182
|
-
}
|
|
97183
|
-
return `${rel}|${from}|${to}`;
|
|
97184
|
-
}
|
|
97185
|
-
async function detectMemoryEdges(store, opts, deps = {}) {
|
|
97186
|
-
const repo = opts.repo.trim();
|
|
97187
|
-
if (repo === "") return [];
|
|
97188
|
-
const items = await store.query({ repo, limit: opts.limit ?? 1e3 });
|
|
97189
|
-
const candidates = items.filter((i3) => INCIDENT_DERIVED_KINDS2.has(i3.kind));
|
|
97190
|
-
if (candidates.length < 2) return [];
|
|
97191
|
-
const existing = /* @__PURE__ */ new Set();
|
|
97192
|
-
const investigationOf = /* @__PURE__ */ new Map();
|
|
97193
|
-
for (const item of candidates) {
|
|
97194
|
-
const out2 = await store.links(item.id, { direction: "out" });
|
|
97195
|
-
for (const l2 of out2) {
|
|
97196
|
-
if (l2.toKind === "memory") {
|
|
97197
|
-
existing.add(edgeKey(l2.rel, l2.fromMemoryId, l2.toRef));
|
|
97198
|
-
} else if (l2.toKind === "incident" && l2.rel === "about-incident") {
|
|
97199
|
-
if (!investigationOf.has(item.id)) investigationOf.set(item.id, l2.toRef);
|
|
97200
|
-
}
|
|
97201
|
-
}
|
|
97202
|
-
}
|
|
97203
|
-
const proposals = [];
|
|
97204
|
-
const outcomeCache = /* @__PURE__ */ new Map();
|
|
97205
|
-
const resolveOutcome = async (investigationId) => {
|
|
97206
|
-
if (deps.outcomeFor === void 0) return null;
|
|
97207
|
-
if (outcomeCache.has(investigationId)) return outcomeCache.get(investigationId) ?? null;
|
|
97208
|
-
let v4 = null;
|
|
97209
|
-
try {
|
|
97210
|
-
v4 = await deps.outcomeFor(investigationId);
|
|
97211
|
-
} catch {
|
|
97212
|
-
v4 = null;
|
|
97213
|
-
}
|
|
97214
|
-
outcomeCache.set(investigationId, v4);
|
|
97215
|
-
return v4;
|
|
97216
|
-
};
|
|
97217
|
-
for (let i3 = 0; i3 < candidates.length; i3++) {
|
|
97218
|
-
const a2 = candidates[i3];
|
|
97219
|
-
for (let j3 = i3 + 1; j3 < candidates.length; j3++) {
|
|
97220
|
-
const b5 = candidates[j3];
|
|
97221
|
-
if (!existing.has(edgeKey("recurs-with", a2.id, b5.id))) {
|
|
97222
|
-
const reason = recurrenceReason(a2, b5);
|
|
97223
|
-
if (reason !== null) {
|
|
97224
|
-
const [lo, hi] = a2.id <= b5.id ? [a2.id, b5.id] : [b5.id, a2.id];
|
|
97225
|
-
proposals.push({
|
|
97226
|
-
id: recurrenceEdgeId(a2.id, b5.id),
|
|
97227
|
-
fromMemoryId: lo,
|
|
97228
|
-
toMemoryId: hi,
|
|
97229
|
-
rel: "recurs-with",
|
|
97230
|
-
detection: "auto:recurrence",
|
|
97231
|
-
reason
|
|
97232
|
-
});
|
|
97233
|
-
}
|
|
97234
|
-
}
|
|
97235
|
-
if (deps.outcomeFor !== void 0 && a2.kind === "confirmed-outcome" && b5.kind === "confirmed-outcome" && !existing.has(edgeKey("contradicts", a2.id, b5.id))) {
|
|
97236
|
-
const sigA = usableSignature(a2.signature);
|
|
97237
|
-
const sigB = usableSignature(b5.signature);
|
|
97238
|
-
const invA = investigationOf.get(a2.id);
|
|
97239
|
-
const invB = investigationOf.get(b5.id);
|
|
97240
|
-
if (sigA !== null && sigA === sigB && invA !== void 0 && invB !== void 0) {
|
|
97241
|
-
const oa = await resolveOutcome(invA);
|
|
97242
|
-
const ob = await resolveOutcome(invB);
|
|
97243
|
-
if (oa !== null && ob !== null) {
|
|
97244
|
-
const reason = outcomeConflict(oa, ob);
|
|
97245
|
-
if (reason !== null) {
|
|
97246
|
-
const [lo, hi] = a2.id <= b5.id ? [a2.id, b5.id] : [b5.id, a2.id];
|
|
97247
|
-
const invLo = lo === a2.id ? invA : invB;
|
|
97248
|
-
const invHi = lo === a2.id ? invB : invA;
|
|
97249
|
-
proposals.push({
|
|
97250
|
-
id: "",
|
|
97251
|
-
// directional — the store mints the id
|
|
97252
|
-
fromMemoryId: lo,
|
|
97253
|
-
toMemoryId: hi,
|
|
97254
|
-
rel: "contradicts",
|
|
97255
|
-
detection: "auto:contradiction",
|
|
97256
|
-
reason,
|
|
97257
|
-
detail: { investigationIds: [invLo, invHi] }
|
|
97258
|
-
});
|
|
97259
|
-
}
|
|
97260
|
-
}
|
|
97261
|
-
}
|
|
97262
|
-
}
|
|
97263
|
-
}
|
|
97264
|
-
}
|
|
97265
|
-
return proposals;
|
|
97266
|
-
}
|
|
97267
|
-
|
|
97268
97532
|
// ../../packages/engine/src/memory-recall.ts
|
|
97269
97533
|
init_cjs_shims();
|
|
97270
97534
|
var import_node_crypto4 = require("crypto");
|
|
@@ -101905,8 +102169,13 @@ async function runIndex(opts) {
|
|
|
101905
102169
|
console.error(import_picocolors6.default.red(` ${err2.message}`));
|
|
101906
102170
|
return 1;
|
|
101907
102171
|
}
|
|
101908
|
-
|
|
101909
|
-
|
|
102172
|
+
const staleReason = isAnalyzed(root) ? indexNeedsReanalyze(root) : null;
|
|
102173
|
+
if (!isAnalyzed(root) || staleReason) {
|
|
102174
|
+
console.log(
|
|
102175
|
+
import_picocolors6.default.dim(
|
|
102176
|
+
staleReason ? ` existing index is stale (${staleReason}) \u2014 re-analyzing with source-intelligence backend\u2026` : " analyzing with source-intelligence backend (first time \u2014 this can take a while)\u2026"
|
|
102177
|
+
)
|
|
102178
|
+
);
|
|
101910
102179
|
try {
|
|
101911
102180
|
await analyzeRepo(root);
|
|
101912
102181
|
} catch (err2) {
|
|
@@ -115365,6 +115634,9 @@ async function runOneInvestigation(input, ctx, opts = {}) {
|
|
|
115365
115634
|
{
|
|
115366
115635
|
code: ctx.code,
|
|
115367
115636
|
db: ctx.dbHandle.db,
|
|
115637
|
+
// HOR-432 — the authored-memory store so every investigation auto-captures a recurrence-aware
|
|
115638
|
+
// memory. Best-effort + CONTEXT-ONLY in the engine (never feeds scoring, never blocks delivery).
|
|
115639
|
+
store: createLocalMemoryStore(ctx.dbHandle.db),
|
|
115368
115640
|
logs: ctx.logs,
|
|
115369
115641
|
mongo: ctx.mongo,
|
|
115370
115642
|
postgres: ctx.postgres,
|
|
@@ -117422,6 +117694,39 @@ function createCloudMemoryStore(client, cfg) {
|
|
|
117422
117694
|
tags: null
|
|
117423
117695
|
};
|
|
117424
117696
|
},
|
|
117697
|
+
// HOR-432 consolidation mirror: refresh the scalar fields the cloud accepts (claim/confidence).
|
|
117698
|
+
// PRIVACY: `payload` NEVER leaves the device (it can stash a vector) — it is intentionally dropped
|
|
117699
|
+
// here, exactly like `add`/`toSyncInput`. The return is the server echo (or a synthesized stub) and
|
|
117700
|
+
// is only consumed by the dual-write wrapper, which discards it; reads always come from local.
|
|
117701
|
+
async update(id, patch, _opts) {
|
|
117702
|
+
const input = { clientId: id };
|
|
117703
|
+
if (patch.claim !== void 0) input.claim = patch.claim;
|
|
117704
|
+
if (patch.confidence !== void 0) input.confidence = patch.confidence;
|
|
117705
|
+
const res = await client.syncMemoryItems(projectId, { items: [input] });
|
|
117706
|
+
const record2 = res.items?.find((r) => r.clientId === id);
|
|
117707
|
+
if (record2) return fromWire(record2, repoFallback);
|
|
117708
|
+
return {
|
|
117709
|
+
id,
|
|
117710
|
+
kind: "investigation",
|
|
117711
|
+
claim: patch.claim ?? "",
|
|
117712
|
+
scope: "repo",
|
|
117713
|
+
source: "investigation",
|
|
117714
|
+
evidence: [],
|
|
117715
|
+
confidence: patch.confidence ?? 0,
|
|
117716
|
+
status: "fresh",
|
|
117717
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
117718
|
+
lastVerifiedAt: null,
|
|
117719
|
+
lastVerifiedHash: null,
|
|
117720
|
+
orgId: null,
|
|
117721
|
+
workspaceId: null,
|
|
117722
|
+
repo: repoFallback,
|
|
117723
|
+
userId: null,
|
|
117724
|
+
visibility: "private",
|
|
117725
|
+
payload: null,
|
|
117726
|
+
signature: null,
|
|
117727
|
+
tags: null
|
|
117728
|
+
};
|
|
117729
|
+
},
|
|
117425
117730
|
async get(id) {
|
|
117426
117731
|
try {
|
|
117427
117732
|
return await findInList(id);
|
|
@@ -117529,6 +117834,14 @@ function dualWriteMemoryStore(local, cloud, onCloudError = () => {
|
|
|
117529
117834
|
});
|
|
117530
117835
|
return row;
|
|
117531
117836
|
},
|
|
117837
|
+
async update(id, patch, opts) {
|
|
117838
|
+
const row = await local.update(id, patch, opts);
|
|
117839
|
+
await mirror(async () => {
|
|
117840
|
+
await cloud.update(id, patch, opts);
|
|
117841
|
+
await mirrorLatestAudit(id);
|
|
117842
|
+
});
|
|
117843
|
+
return row;
|
|
117844
|
+
},
|
|
117532
117845
|
async setStatus(id, status, audit) {
|
|
117533
117846
|
await local.setStatus(id, status, audit);
|
|
117534
117847
|
await mirror(async () => {
|