@inerrata-corporation/errata 2.0.2-dev.361 → 2.0.2-dev.417

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/errata.mjs CHANGED
@@ -19177,7 +19177,55 @@ function percolate(l1, l2, opts) {
19177
19177
  result.touched = [...touched];
19178
19178
  return result;
19179
19179
  }
19180
- var PERCOLATING_LABELS, PERCOLATING_EDGES, PERCOLATING_DRIFT_EDGES;
19180
+ function migrateProjectAlias(l2, opts) {
19181
+ const { from, to } = opts;
19182
+ let nodesRewritten = 0;
19183
+ let edgesRewritten = 0;
19184
+ if (from === to) return { nodesRewritten, edgesRewritten };
19185
+ const rewrite = (seen) => {
19186
+ if (!seen.includes(from)) return null;
19187
+ const out2 = [];
19188
+ for (const p of seen.map((p2) => p2 === from ? to : p2)) if (!out2.includes(p)) out2.push(p);
19189
+ return out2;
19190
+ };
19191
+ const nodeUpdates = [];
19192
+ const edgeUpdates = [];
19193
+ const seenEdges = /* @__PURE__ */ new Set();
19194
+ for (const label of PROVENANCE_LABELS) {
19195
+ for (const n of l2.findNodesByLabel(label)) {
19196
+ const next = rewrite(observedProjects(n));
19197
+ if (next) nodeUpdates.push({ id: n.id, attrs: { ...n.attrs, observedInProjects: next } });
19198
+ for (const e of l2.outEdges(n.id, [...PERCOLATING_DRIFT_EDGES])) {
19199
+ if (seenEdges.has(e.id)) continue;
19200
+ seenEdges.add(e.id);
19201
+ const eSeen = Array.isArray(e.attrs["observedInProjects"]) ? e.attrs["observedInProjects"] : [];
19202
+ const eNext = rewrite(eSeen);
19203
+ if (eNext) edgeUpdates.push({ id: e.id, attrs: { ...e.attrs, observedInProjects: eNext } });
19204
+ }
19205
+ }
19206
+ }
19207
+ const CHUNK = 200;
19208
+ for (let i2 = 0; i2 < nodeUpdates.length; i2 += CHUNK) {
19209
+ const slice = nodeUpdates.slice(i2, i2 + CHUNK);
19210
+ l2.transaction(() => {
19211
+ for (const u of slice) {
19212
+ l2.updateNode(u.id, { attrs: u.attrs, lastUpdatedAt: opts.ts });
19213
+ nodesRewritten++;
19214
+ }
19215
+ });
19216
+ }
19217
+ for (let i2 = 0; i2 < edgeUpdates.length; i2 += CHUNK) {
19218
+ const slice = edgeUpdates.slice(i2, i2 + CHUNK);
19219
+ l2.transaction(() => {
19220
+ for (const u of slice) {
19221
+ l2.updateEdge(u.id, { attrs: u.attrs, lastSeenAt: opts.ts });
19222
+ edgesRewritten++;
19223
+ }
19224
+ });
19225
+ }
19226
+ return { nodesRewritten, edgesRewritten };
19227
+ }
19228
+ var PERCOLATING_LABELS, PERCOLATING_EDGES, PERCOLATING_DRIFT_EDGES, PROVENANCE_LABELS;
19181
19229
  var init_percolate = __esm({
19182
19230
  "../../packages/local-graph/src/percolate.ts"() {
19183
19231
  "use strict";
@@ -19185,6 +19233,7 @@ var init_percolate = __esm({
19185
19233
  PERCOLATING_LABELS = ["Claim", "Problem", "Solution"];
19186
19234
  PERCOLATING_EDGES = ["CAUSED_BY", "SOLVED_BY", "CONTRADICTS"];
19187
19235
  PERCOLATING_DRIFT_EDGES = ["CONTINUES", "REVEALED_BY", "SUPERSEDED_BY", "SPLIT_INTO"];
19236
+ PROVENANCE_LABELS = ["Claim", "Problem", "Solution", "Triage", "RootCause"];
19188
19237
  }
19189
19238
  });
19190
19239
 
@@ -19937,463 +19986,6 @@ var init_src3 = __esm({
19937
19986
  }
19938
19987
  });
19939
19988
 
19940
- // ../../packages/local-graph/src/abstraction.ts
19941
- function sharedScope(l2, memberIds) {
19942
- const shared = (field) => {
19943
- const vals = /* @__PURE__ */ new Set();
19944
- for (const id of memberIds) {
19945
- const s = l2.getNode(id)?.attrs["scope"] ?? {};
19946
- if (typeof s[field] === "string") vals.add(s[field]);
19947
- }
19948
- return vals.size === 1 ? [...vals][0] : void 0;
19949
- };
19950
- const lang = shared("lang");
19951
- const versionRange = shared("versionRange");
19952
- return { ...lang ? { lang } : {}, ...versionRange ? { versionRange } : {} };
19953
- }
19954
- function buildCandidate(id, ts, members, contexts, originMachine, scope) {
19955
- return {
19956
- id,
19957
- label: "Claim",
19958
- description: "",
19959
- // empty — the harness distills the prose in P3
19960
- extractionConfidence: 0.4,
19961
- extractionSource: "agent-observed",
19962
- embedding: [],
19963
- cumulativeSurprise: 0,
19964
- peakSurprise: 0,
19965
- cumulativeHits: 1,
19966
- lastUpdatedAt: ts,
19967
- createdAt: ts,
19968
- memoryTier: "short-term",
19969
- pageRank: 0,
19970
- isLandmark: false,
19971
- community: null,
19972
- stability: "unstable",
19973
- attrs: {
19974
- abstractionLevel: ABSTRACTION_LEVEL.PRINCIPLE,
19975
- kind: "abstraction-candidate",
19976
- provisional: true,
19977
- pendingDistillation: true,
19978
- // membership is the unit of truth — the harness validates its fence against
19979
- // this set (P3 / C3) and may only phrase, never alter, it.
19980
- members,
19981
- memberContexts: contexts,
19982
- distinctContexts: contexts.length,
19983
- // standard Claim envelope so it crystallizes/syncs unchanged once distilled
19984
- truthKind: "contextual",
19985
- // Derived from members (P1): a single-language cluster becomes a lang-scoped
19986
- // principle; a cross-language one stays universal (`{}`).
19987
- scope,
19988
- groundedSupport: 0,
19989
- sources: [],
19990
- crystallized: "hypothesis",
19991
- confidence: 0.4,
19992
- ...originMachine ? { originMachine } : {}
19993
- }
19994
- };
19995
- }
19996
- function mergeGeneralizes(store, principleId, memberId, ts) {
19997
- store.mergeEdge({
19998
- id: `edge_${digest({ from: principleId, type: "GENERALIZES", to: memberId })}`.slice(0, 24),
19999
- from: principleId,
20000
- to: memberId,
20001
- type: "GENERALIZES",
20002
- confidence: 0.4,
20003
- extractionSource: "agent-observed",
20004
- createdAt: ts,
20005
- lastSeenAt: ts,
20006
- navSuccesses: 0,
20007
- navFailures: 0,
20008
- attrs: { provisional: true }
20009
- });
20010
- }
20011
- function induceAbstractions(l2, opts) {
20012
- const K = opts.minClusterSize ?? DEFAULT_MIN_CLUSTER_SIZE;
20013
- const minCtx = opts.minDistinctContexts ?? DEFAULT_MIN_DISTINCT_CONTEXTS;
20014
- const report = {
20015
- communities: 0,
20016
- candidatesMinted: 0,
20017
- candidatesExisting: 0,
20018
- skippedSmall: 0,
20019
- skippedSingleContext: 0,
20020
- generalizesEdges: 0
20021
- };
20022
- const ids = /* @__PURE__ */ new Set();
20023
- for (const label of PERCOLATING_LABELS) {
20024
- for (const n of l2.findNodesByLabel(label)) {
20025
- if (Number(n.attrs["abstractionLevel"] ?? ABSTRACTION_LEVEL.CONCRETE) >= ABSTRACTION_LEVEL.PRINCIPLE) {
20026
- continue;
20027
- }
20028
- if (n.attrs["resolvedAs"] === PROBLEM_RESOLUTION.FALSE_POSITIVE) continue;
20029
- ids.add(n.id);
20030
- }
20031
- }
20032
- if (ids.size === 0) return report;
20033
- const adj = /* @__PURE__ */ new Map();
20034
- const protect = [];
20035
- const add = (a, b, w) => {
20036
- const l = adj.get(a) ?? [];
20037
- l.push({ to: b, weight: w });
20038
- adj.set(a, l);
20039
- };
20040
- for (const id of ids) {
20041
- for (const e of l2.outEdges(id, [...PERCOLATING_EDGES])) {
20042
- if (!ids.has(e.to)) continue;
20043
- const conf = e.confidence > 0 ? e.confidence : 0.5;
20044
- const w = Math.max(EDGE_WEIGHT[e.type] ?? 1, 0.1) * conf;
20045
- add(id, e.to, w);
20046
- add(e.to, id, w);
20047
- if (isCausalProtected(e.type)) protect.push([id, e.to]);
20048
- }
20049
- }
20050
- const comm = detectCommunitiesLeiden({ nodeIds: [...ids], adj }, { protect });
20051
- report.communities = comm.count;
20052
- const members = /* @__PURE__ */ new Map();
20053
- for (const [id, c] of comm.community) {
20054
- const l = members.get(c) ?? [];
20055
- l.push(id);
20056
- members.set(c, l);
20057
- }
20058
- l2.transaction(() => {
20059
- for (const mem of members.values()) {
20060
- if (opts.touched && !mem.some((id) => opts.touched.has(id))) continue;
20061
- if (mem.length < K) {
20062
- report.skippedSmall++;
20063
- continue;
20064
- }
20065
- if (mem.some((id) => l2.inEdges(id, ["GENERALIZES"]).length > 0)) {
20066
- report.candidatesExisting++;
20067
- continue;
20068
- }
20069
- const contexts = /* @__PURE__ */ new Set();
20070
- for (const id of mem) {
20071
- const n = l2.getNode(id);
20072
- const obs = n?.attrs["observedInProjects"];
20073
- if (Array.isArray(obs)) {
20074
- for (const ws of obs) contexts.add(opts.contextOf(ws) ?? `project:${ws}`);
20075
- }
20076
- }
20077
- if (contexts.size < minCtx) {
20078
- report.skippedSingleContext++;
20079
- continue;
20080
- }
20081
- const sorted = [...mem].sort();
20082
- const principleId = `princ_${digest({ members: sorted })}`.slice(0, 56);
20083
- l2.mergeNode(
20084
- buildCandidate(principleId, opts.ts, sorted, [...contexts].sort(), opts.originMachine, sharedScope(l2, sorted))
20085
- );
20086
- for (const id of sorted) {
20087
- mergeGeneralizes(l2, principleId, id, opts.ts);
20088
- report.generalizesEdges++;
20089
- }
20090
- report.candidatesMinted++;
20091
- }
20092
- });
20093
- return report;
20094
- }
20095
- function pendingAbstractions(l2) {
20096
- const out2 = [];
20097
- for (const n of l2.findNodesByLabel("Claim")) {
20098
- if (Number(n.attrs["abstractionLevel"] ?? ABSTRACTION_LEVEL.CONCRETE) < ABSTRACTION_LEVEL.PRINCIPLE) continue;
20099
- if (n.attrs["provisional"] !== true) continue;
20100
- const memberIds = Array.isArray(n.attrs["members"]) ? n.attrs["members"] : [];
20101
- const members = memberIds.map((id) => {
20102
- const m = l2.getNode(id);
20103
- return m ? { id: m.id, label: m.label, description: m.description } : { id, label: "?", description: "(member missing)" };
20104
- });
20105
- out2.push({
20106
- candidate: n.id,
20107
- distinctContexts: Number(n.attrs["distinctContexts"] ?? 0),
20108
- members
20109
- });
20110
- }
20111
- return out2;
20112
- }
20113
- function parseAbstractionFences(text) {
20114
- const out2 = [];
20115
- const block = /```errata-abstraction[^\n]*\n([\s\S]*?)```/g;
20116
- let m;
20117
- while ((m = block.exec(text)) !== null) {
20118
- const fields = {};
20119
- for (const line of m[1].split(/\r?\n/)) {
20120
- const kv = /^\s*(candidate|principle|covers)\s*:\s*(.+?)\s*$/i.exec(line);
20121
- if (kv) fields[kv[1].toLowerCase()] = kv[2].trim();
20122
- }
20123
- if (!fields["candidate"] || !fields["principle"] || !fields["covers"]) continue;
20124
- const covers = fields["covers"].split(/[,\s]+/).map((s) => s.trim()).filter(Boolean);
20125
- if (covers.length === 0) continue;
20126
- out2.push({ candidate: fields["candidate"], principle: fields["principle"], covers });
20127
- }
20128
- return out2;
20129
- }
20130
- function applyAbstractionFence(l2, fence, ts) {
20131
- const node2 = l2.getNode(fence.candidate);
20132
- if (!node2 || node2.label !== "Claim" || Number(node2.attrs["abstractionLevel"] ?? ABSTRACTION_LEVEL.CONCRETE) < ABSTRACTION_LEVEL.PRINCIPLE) {
20133
- return { applied: false, principleId: fence.candidate, reason: "no such abstraction candidate" };
20134
- }
20135
- if (node2.attrs["provisional"] !== true) {
20136
- return { applied: false, principleId: fence.candidate, reason: "candidate already distilled" };
20137
- }
20138
- const members = Array.isArray(node2.attrs["members"]) ? node2.attrs["members"] : [];
20139
- const want = new Set(members);
20140
- const got = new Set(fence.covers);
20141
- const missing = members.filter((id) => !got.has(id));
20142
- const invented = fence.covers.filter((id) => !want.has(id));
20143
- if (missing.length > 0 || invented.length > 0) {
20144
- return {
20145
- applied: false,
20146
- principleId: fence.candidate,
20147
- reason: `coverage mismatch \u2014 ${missing.length} member(s) uncovered, ${invented.length} non-member(s) invented`
20148
- };
20149
- }
20150
- const principle = fence.principle.trim();
20151
- if (!principle) return { applied: false, principleId: fence.candidate, reason: "empty principle" };
20152
- const { revisit: _r, revisitReason: _rr, revisitSinceTs: _rs, ...rest2 } = node2.attrs;
20153
- void _r;
20154
- void _rr;
20155
- void _rs;
20156
- l2.updateNode(fence.candidate, {
20157
- description: principle,
20158
- attrs: { ...rest2, provisional: false, pendingDistillation: false, distilledAt: ts },
20159
- lastUpdatedAt: ts
20160
- });
20161
- return { applied: true, principleId: fence.candidate };
20162
- }
20163
- function revisitContradictedPrinciples(l2, ts) {
20164
- const report = { flagged: 0 };
20165
- l2.transaction(() => {
20166
- for (const n of l2.findNodesByLabel("Claim")) {
20167
- if (Number(n.attrs["abstractionLevel"] ?? ABSTRACTION_LEVEL.CONCRETE) < ABSTRACTION_LEVEL.PRINCIPLE) continue;
20168
- if (n.attrs["revisit"] === true) continue;
20169
- const members = Array.isArray(n.attrs["members"]) ? n.attrs["members"] : [];
20170
- const memberSet = new Set(members);
20171
- let reason = "";
20172
- for (const id of members) {
20173
- const m = l2.getNode(id);
20174
- if (!m) {
20175
- reason = `member ${id} was removed`;
20176
- break;
20177
- }
20178
- if (m.attrs["revisit"] === true) {
20179
- reason = `member "${m.description}" needs revisit`;
20180
- break;
20181
- }
20182
- const contradictors = [
20183
- ...l2.outEdges(id, ["CONTRADICTS"]).map((e) => e.to),
20184
- ...l2.inEdges(id, ["CONTRADICTS"]).map((e) => e.from)
20185
- ];
20186
- if (contradictors.some((other) => !memberSet.has(other))) {
20187
- reason = `member "${m.description}" is now contradicted by external evidence`;
20188
- break;
20189
- }
20190
- }
20191
- if (!reason) continue;
20192
- l2.updateNode(n.id, {
20193
- attrs: {
20194
- ...n.attrs,
20195
- revisit: true,
20196
- revisitReason: reason,
20197
- revisitSinceTs: ts,
20198
- provisional: true,
20199
- // re-queue for re-distillation (P3 re-name)
20200
- pendingDistillation: true
20201
- },
20202
- lastUpdatedAt: ts
20203
- });
20204
- report.flagged++;
20205
- }
20206
- });
20207
- return report;
20208
- }
20209
- function harvestAbstractionFences(l2, text, ts) {
20210
- let applied = 0;
20211
- let rejected = 0;
20212
- for (const fence of parseAbstractionFences(text)) {
20213
- if (applyAbstractionFence(l2, fence, ts).applied) applied++;
20214
- else rejected++;
20215
- }
20216
- return { applied, rejected };
20217
- }
20218
- var DEFAULT_MIN_CLUSTER_SIZE, DEFAULT_MIN_DISTINCT_CONTEXTS;
20219
- var init_abstraction = __esm({
20220
- "../../packages/local-graph/src/abstraction.ts"() {
20221
- "use strict";
20222
- init_src();
20223
- init_src2();
20224
- init_src3();
20225
- init_percolate();
20226
- DEFAULT_MIN_CLUSTER_SIZE = 3;
20227
- DEFAULT_MIN_DISTINCT_CONTEXTS = 2;
20228
- }
20229
- });
20230
-
20231
- // ../../packages/local-graph/src/community.ts
20232
- function detectLocalCommunities(store, opts = {}) {
20233
- const minSize = opts.minCommunitySize ?? DEFAULT_MIN_COMMUNITY_SIZE;
20234
- const report = { candidates: 0, communities: 0, joint: 0, assigned: 0 };
20235
- const ids = /* @__PURE__ */ new Set();
20236
- for (const label of JOINT_COMMUNITY_LABELS) {
20237
- for (const n of store.findNodesByLabel(label)) {
20238
- if (label === "Problem" && n.attrs["resolvedAs"] === PROBLEM_RESOLUTION.FALSE_POSITIVE) continue;
20239
- ids.add(n.id);
20240
- }
20241
- }
20242
- report.candidates = ids.size;
20243
- if (ids.size === 0) return report;
20244
- const adj = /* @__PURE__ */ new Map();
20245
- const protect = [];
20246
- const add = (a, b, w) => {
20247
- const l = adj.get(a) ?? [];
20248
- l.push({ to: b, weight: w });
20249
- adj.set(a, l);
20250
- };
20251
- for (const id of ids) {
20252
- for (const e of store.outEdges(id, [...JOINT_COMMUNITY_EDGES])) {
20253
- if (!ids.has(e.to)) continue;
20254
- const conf = e.confidence > 0 ? e.confidence : 0.5;
20255
- const w = Math.max(EDGE_WEIGHT[e.type] ?? 1, 0.1) * conf;
20256
- add(id, e.to, w);
20257
- add(e.to, id, w);
20258
- if (isCausalProtected(e.type)) protect.push([id, e.to]);
20259
- }
20260
- }
20261
- const comm = detectCommunitiesLeiden({ nodeIds: [...ids], adj }, { protect });
20262
- const members = /* @__PURE__ */ new Map();
20263
- for (const [id, c] of comm.community) {
20264
- const l = members.get(c) ?? [];
20265
- l.push(id);
20266
- members.set(c, l);
20267
- }
20268
- store.transaction(() => {
20269
- for (const [cid, mem] of members) {
20270
- const qualifies = mem.length >= minSize;
20271
- let hasLocal = false;
20272
- let hasCloud = false;
20273
- for (const id of mem) {
20274
- const n = store.getNode(id);
20275
- if (!n) continue;
20276
- const pulled = n.attrs["source"] === "cloud";
20277
- if (pulled) hasCloud = true;
20278
- else hasLocal = true;
20279
- const nextAttrs = { ...n.attrs };
20280
- if (qualifies) nextAttrs["localCommunity"] = cid;
20281
- else delete nextAttrs["localCommunity"];
20282
- store.updateNode(id, { attrs: nextAttrs });
20283
- if (!pulled) store.setCommunity(id, qualifies ? cid : null);
20284
- }
20285
- if (qualifies) {
20286
- report.communities++;
20287
- report.assigned += mem.length;
20288
- if (hasLocal && hasCloud) report.joint++;
20289
- }
20290
- }
20291
- });
20292
- return report;
20293
- }
20294
- function communitySeeds(store, opts = {}) {
20295
- const maxCommunities = opts.maxCommunities ?? 4;
20296
- const maxSeeds = opts.maxSeeds ?? 32;
20297
- const byCommunity = /* @__PURE__ */ new Map();
20298
- for (const label of JOINT_COMMUNITY_LABELS) {
20299
- for (const n of store.findNodesByLabel(label)) {
20300
- const cid = n.attrs["localCommunity"];
20301
- if (typeof cid !== "string") continue;
20302
- const cloudId = n.attrs["cloudNodeId"];
20303
- const l = byCommunity.get(cid) ?? [];
20304
- l.push({
20305
- id: n.id,
20306
- pulled: n.attrs["source"] === "cloud",
20307
- cloudId: typeof cloudId === "string" && cloudId !== n.id ? cloudId : null,
20308
- ts: n.lastUpdatedAt
20309
- });
20310
- byCommunity.set(cid, l);
20311
- }
20312
- }
20313
- if (byCommunity.size === 0) return [];
20314
- const ranked = [...byCommunity.entries()].map(([cid, mem]) => ({ cid, mem, fresh: Math.max(...mem.map((m) => m.ts)) })).sort((a, b) => b.fresh - a.fresh).slice(0, maxCommunities);
20315
- const seeds = [];
20316
- const seen = /* @__PURE__ */ new Set();
20317
- const push = (id) => {
20318
- if (seeds.length >= maxSeeds || seen.has(id)) return;
20319
- seen.add(id);
20320
- seeds.push(id);
20321
- };
20322
- for (const { mem } of ranked) {
20323
- const ordered = [...mem].sort(
20324
- (a, b) => a.pulled === b.pulled ? b.ts - a.ts : a.pulled ? -1 : 1
20325
- );
20326
- for (const m of ordered) {
20327
- push(m.id);
20328
- if (m.cloudId) push(m.cloudId);
20329
- }
20330
- }
20331
- return seeds;
20332
- }
20333
- function communityInductionRequests(store, opts = {}) {
20334
- const maxCommunities = opts.maxCommunities ?? 2;
20335
- const byCommunity = /* @__PURE__ */ new Map();
20336
- for (const label of JOINT_COMMUNITY_LABELS) {
20337
- for (const n of store.findNodesByLabel(label)) {
20338
- const cid = n.attrs["localCommunity"];
20339
- if (typeof cid !== "string") continue;
20340
- const cloudId = n.attrs["cloudNodeId"];
20341
- const l = byCommunity.get(cid) ?? [];
20342
- l.push({
20343
- id: n.id,
20344
- pulled: n.attrs["source"] === "cloud",
20345
- cloudId: typeof cloudId === "string" && cloudId !== n.id ? cloudId : null,
20346
- ts: n.lastUpdatedAt,
20347
- description: n.description
20348
- });
20349
- byCommunity.set(cid, l);
20350
- }
20351
- }
20352
- if (byCommunity.size === 0) return [];
20353
- return [...byCommunity.entries()].map(([cid, mem]) => ({ cid, mem, fresh: Math.max(...mem.map((m) => m.ts)) })).sort((a, b) => b.fresh - a.fresh).map(({ cid, mem }) => {
20354
- const ids = /* @__PURE__ */ new Set();
20355
- for (const m of mem) {
20356
- ids.add(m.id);
20357
- if (m.cloudId) ids.add(m.cloudId);
20358
- }
20359
- const freshestLocal = [...mem].filter((m) => !m.pulled).sort((a, b) => b.ts - a.ts)[0];
20360
- return {
20361
- communityId: cid,
20362
- members: [...ids].slice(0, 64),
20363
- context: (freshestLocal?.description ?? "recurring workspace problem cluster").slice(0, 200)
20364
- };
20365
- }).filter((r) => r.members.length >= MIN_INDUCTION_MEMBERS).slice(0, maxCommunities);
20366
- }
20367
- var JOINT_COMMUNITY_LABELS, JOINT_COMMUNITY_EDGES, DEFAULT_MIN_COMMUNITY_SIZE, MIN_INDUCTION_MEMBERS;
20368
- var init_community2 = __esm({
20369
- "../../packages/local-graph/src/community.ts"() {
20370
- "use strict";
20371
- init_src2();
20372
- init_src3();
20373
- JOINT_COMMUNITY_LABELS = [
20374
- "Problem",
20375
- "Solution",
20376
- "RootCause",
20377
- "Pattern"
20378
- ];
20379
- JOINT_COMMUNITY_EDGES = [
20380
- "CAUSED_BY",
20381
- "SOLVED_BY",
20382
- "FIXED_BY",
20383
- "CONTRADICTS",
20384
- "INSTANCE_OF",
20385
- "MATCHES",
20386
- "IMPLEMENTS",
20387
- "TRIAGED_BY",
20388
- "INDICATES",
20389
- "CONFIRMS",
20390
- "RELATES_TO"
20391
- ];
20392
- DEFAULT_MIN_COMMUNITY_SIZE = 2;
20393
- MIN_INDUCTION_MEMBERS = 3;
20394
- }
20395
- });
20396
-
20397
19989
  // ../../packages/local-graph/src/triage.ts
20398
19990
  function semNode(id, label, description, ts, attrs) {
20399
19991
  return {
@@ -20431,6 +20023,10 @@ function semEdge(id, from, to, type, ts, attrs) {
20431
20023
  attrs
20432
20024
  };
20433
20025
  }
20026
+ function isUnfilledPlaceholder(value) {
20027
+ const v = value.trim();
20028
+ return v.startsWith("<") && v.endsWith(">");
20029
+ }
20434
20030
  function recordTriageObservation(l2, obs, ts) {
20435
20031
  const statement = obs.presentingStatement.trim();
20436
20032
  const presentingId = obs.presentingId?.trim() || identityId({ kind: "DesignProblem", statement });
@@ -20539,6 +20135,33 @@ function recordCauseChainLink(l2, link, ts, attrs = {}) {
20539
20135
  });
20540
20136
  return created;
20541
20137
  }
20138
+ function mintWorkspaceCausalFact(ws, fact, ts) {
20139
+ if (fact.causeId === fact.presentingId) return false;
20140
+ const problem = ws.getNode(fact.presentingId);
20141
+ if (!problem || problem.label !== "Problem") return false;
20142
+ if (problem.attrs["source"] === "cloud") return false;
20143
+ const existingCause = ws.getNode(fact.causeId);
20144
+ if (existingCause && existingCause.label !== "RootCause") return false;
20145
+ const description = fact.causeDescription?.trim();
20146
+ if (!existingCause && !description) return false;
20147
+ let created = false;
20148
+ ws.transaction(() => {
20149
+ if (!ws.getNode(fact.causeId)) {
20150
+ ws.mergeNode(
20151
+ semNode(fact.causeId, "RootCause", description, ts, {
20152
+ scope: {},
20153
+ ...fact.sessionId ? { sources: [fact.sessionId] } : {}
20154
+ })
20155
+ );
20156
+ }
20157
+ const edgeId2 = `edge_cb_${fact.presentingId}_${fact.causeId}`;
20158
+ if (!ws.getEdge(edgeId2)) {
20159
+ ws.mergeEdge(semEdge(edgeId2, fact.presentingId, fact.causeId, "CAUSED_BY", ts, { witnessed: true }));
20160
+ created = true;
20161
+ }
20162
+ });
20163
+ return created;
20164
+ }
20542
20165
  function recordMisreadPrior(shared, problem, context, contributor, ts) {
20543
20166
  if (problem.attrs["resolvedAs"] !== PROBLEM_RESOLUTION.FALSE_POSITIVE) return false;
20544
20167
  if (Number(problem.attrs["corroborations"] ?? 0) < 1) return false;
@@ -20739,6 +20362,7 @@ function markDiscriminatorAsked(l2, routeIds, ts) {
20739
20362
  function promoteRouteWithDiscriminator(l2, routeId, discriminator, ts, source) {
20740
20363
  const test = discriminator.trim();
20741
20364
  if (!test) return false;
20365
+ if (isUnfilledPlaceholder(test)) return false;
20742
20366
  const existing = l2.getEdge(routeId);
20743
20367
  if (!existing || !ROUTE_EDGE_TYPES.includes(existing.type)) return false;
20744
20368
  const attrs = {
@@ -20896,6 +20520,467 @@ var init_triage2 = __esm({
20896
20520
  }
20897
20521
  });
20898
20522
 
20523
+ // ../../packages/local-graph/src/abstraction.ts
20524
+ function sharedScope(l2, memberIds) {
20525
+ const shared = (field) => {
20526
+ const vals = /* @__PURE__ */ new Set();
20527
+ for (const id of memberIds) {
20528
+ const s = l2.getNode(id)?.attrs["scope"] ?? {};
20529
+ if (typeof s[field] === "string") vals.add(s[field]);
20530
+ }
20531
+ return vals.size === 1 ? [...vals][0] : void 0;
20532
+ };
20533
+ const lang = shared("lang");
20534
+ const versionRange = shared("versionRange");
20535
+ return { ...lang ? { lang } : {}, ...versionRange ? { versionRange } : {} };
20536
+ }
20537
+ function buildCandidate(id, ts, members, contexts, originMachine, scope) {
20538
+ return {
20539
+ id,
20540
+ label: "Claim",
20541
+ description: "",
20542
+ // empty — the harness distills the prose in P3
20543
+ extractionConfidence: 0.4,
20544
+ extractionSource: "agent-observed",
20545
+ embedding: [],
20546
+ cumulativeSurprise: 0,
20547
+ peakSurprise: 0,
20548
+ cumulativeHits: 1,
20549
+ lastUpdatedAt: ts,
20550
+ createdAt: ts,
20551
+ memoryTier: "short-term",
20552
+ pageRank: 0,
20553
+ isLandmark: false,
20554
+ community: null,
20555
+ stability: "unstable",
20556
+ attrs: {
20557
+ abstractionLevel: ABSTRACTION_LEVEL.PRINCIPLE,
20558
+ kind: "abstraction-candidate",
20559
+ provisional: true,
20560
+ pendingDistillation: true,
20561
+ // membership is the unit of truth — the harness validates its fence against
20562
+ // this set (P3 / C3) and may only phrase, never alter, it.
20563
+ members,
20564
+ memberContexts: contexts,
20565
+ distinctContexts: contexts.length,
20566
+ // standard Claim envelope so it crystallizes/syncs unchanged once distilled
20567
+ truthKind: "contextual",
20568
+ // Derived from members (P1): a single-language cluster becomes a lang-scoped
20569
+ // principle; a cross-language one stays universal (`{}`).
20570
+ scope,
20571
+ groundedSupport: 0,
20572
+ sources: [],
20573
+ crystallized: "hypothesis",
20574
+ confidence: 0.4,
20575
+ ...originMachine ? { originMachine } : {}
20576
+ }
20577
+ };
20578
+ }
20579
+ function mergeGeneralizes(store, principleId, memberId, ts) {
20580
+ store.mergeEdge({
20581
+ id: `edge_${digest({ from: principleId, type: "GENERALIZES", to: memberId })}`.slice(0, 24),
20582
+ from: principleId,
20583
+ to: memberId,
20584
+ type: "GENERALIZES",
20585
+ confidence: 0.4,
20586
+ extractionSource: "agent-observed",
20587
+ createdAt: ts,
20588
+ lastSeenAt: ts,
20589
+ navSuccesses: 0,
20590
+ navFailures: 0,
20591
+ attrs: { provisional: true }
20592
+ });
20593
+ }
20594
+ function induceAbstractions(l2, opts) {
20595
+ const K = opts.minClusterSize ?? DEFAULT_MIN_CLUSTER_SIZE;
20596
+ const minCtx = opts.minDistinctContexts ?? DEFAULT_MIN_DISTINCT_CONTEXTS;
20597
+ const report = {
20598
+ communities: 0,
20599
+ candidatesMinted: 0,
20600
+ candidatesExisting: 0,
20601
+ skippedSmall: 0,
20602
+ skippedSingleContext: 0,
20603
+ generalizesEdges: 0
20604
+ };
20605
+ const ids = /* @__PURE__ */ new Set();
20606
+ for (const label of PERCOLATING_LABELS) {
20607
+ for (const n of l2.findNodesByLabel(label)) {
20608
+ if (Number(n.attrs["abstractionLevel"] ?? ABSTRACTION_LEVEL.CONCRETE) >= ABSTRACTION_LEVEL.PRINCIPLE) {
20609
+ continue;
20610
+ }
20611
+ if (n.attrs["resolvedAs"] === PROBLEM_RESOLUTION.FALSE_POSITIVE) continue;
20612
+ ids.add(n.id);
20613
+ }
20614
+ }
20615
+ if (ids.size === 0) return report;
20616
+ const adj = /* @__PURE__ */ new Map();
20617
+ const protect = [];
20618
+ const add = (a, b, w) => {
20619
+ const l = adj.get(a) ?? [];
20620
+ l.push({ to: b, weight: w });
20621
+ adj.set(a, l);
20622
+ };
20623
+ for (const id of ids) {
20624
+ for (const e of l2.outEdges(id, [...PERCOLATING_EDGES])) {
20625
+ if (!ids.has(e.to)) continue;
20626
+ const conf = e.confidence > 0 ? e.confidence : 0.5;
20627
+ const w = Math.max(EDGE_WEIGHT[e.type] ?? 1, 0.1) * conf;
20628
+ add(id, e.to, w);
20629
+ add(e.to, id, w);
20630
+ if (isCausalProtected(e.type)) protect.push([id, e.to]);
20631
+ }
20632
+ }
20633
+ const comm = detectCommunitiesLeiden({ nodeIds: [...ids], adj }, { protect });
20634
+ report.communities = comm.count;
20635
+ const members = /* @__PURE__ */ new Map();
20636
+ for (const [id, c] of comm.community) {
20637
+ const l = members.get(c) ?? [];
20638
+ l.push(id);
20639
+ members.set(c, l);
20640
+ }
20641
+ l2.transaction(() => {
20642
+ for (const mem of members.values()) {
20643
+ if (opts.touched && !mem.some((id) => opts.touched.has(id))) continue;
20644
+ if (mem.length < K) {
20645
+ report.skippedSmall++;
20646
+ continue;
20647
+ }
20648
+ if (mem.some((id) => l2.inEdges(id, ["GENERALIZES"]).length > 0)) {
20649
+ report.candidatesExisting++;
20650
+ continue;
20651
+ }
20652
+ const contexts = /* @__PURE__ */ new Set();
20653
+ for (const id of mem) {
20654
+ const n = l2.getNode(id);
20655
+ const obs = n?.attrs["observedInProjects"];
20656
+ if (Array.isArray(obs)) {
20657
+ for (const ws of obs) contexts.add(opts.contextOf(ws) ?? `project:${ws}`);
20658
+ }
20659
+ }
20660
+ if (contexts.size < minCtx) {
20661
+ report.skippedSingleContext++;
20662
+ continue;
20663
+ }
20664
+ const sorted = [...mem].sort();
20665
+ const principleId = `princ_${digest({ members: sorted })}`.slice(0, 56);
20666
+ l2.mergeNode(
20667
+ buildCandidate(principleId, opts.ts, sorted, [...contexts].sort(), opts.originMachine, sharedScope(l2, sorted))
20668
+ );
20669
+ for (const id of sorted) {
20670
+ mergeGeneralizes(l2, principleId, id, opts.ts);
20671
+ report.generalizesEdges++;
20672
+ }
20673
+ report.candidatesMinted++;
20674
+ }
20675
+ });
20676
+ return report;
20677
+ }
20678
+ function pendingAbstractions(l2) {
20679
+ const out2 = [];
20680
+ for (const n of l2.findNodesByLabel("Claim")) {
20681
+ if (Number(n.attrs["abstractionLevel"] ?? ABSTRACTION_LEVEL.CONCRETE) < ABSTRACTION_LEVEL.PRINCIPLE) continue;
20682
+ if (n.attrs["provisional"] !== true) continue;
20683
+ const memberIds = Array.isArray(n.attrs["members"]) ? n.attrs["members"] : [];
20684
+ const members = memberIds.map((id) => {
20685
+ const m = l2.getNode(id);
20686
+ return m ? { id: m.id, label: m.label, description: m.description } : { id, label: "?", description: "(member missing)" };
20687
+ });
20688
+ out2.push({
20689
+ candidate: n.id,
20690
+ distinctContexts: Number(n.attrs["distinctContexts"] ?? 0),
20691
+ members
20692
+ });
20693
+ }
20694
+ return out2;
20695
+ }
20696
+ function parseAbstractionFences(text) {
20697
+ const out2 = [];
20698
+ const block = /```errata-abstraction[^\n]*\n([\s\S]*?)```/g;
20699
+ let m;
20700
+ while ((m = block.exec(text)) !== null) {
20701
+ const fields = {};
20702
+ for (const line of m[1].split(/\r?\n/)) {
20703
+ const kv = /^\s*(candidate|principle|covers)\s*:\s*(.+?)\s*$/i.exec(line);
20704
+ if (kv) fields[kv[1].toLowerCase()] = kv[2].trim();
20705
+ }
20706
+ if (!fields["candidate"] || !fields["principle"] || !fields["covers"]) continue;
20707
+ const covers = fields["covers"].split(/[,\s]+/).map((s) => s.trim()).filter(Boolean);
20708
+ if (covers.length === 0) continue;
20709
+ out2.push({ candidate: fields["candidate"], principle: fields["principle"], covers });
20710
+ }
20711
+ return out2;
20712
+ }
20713
+ function applyAbstractionFence(l2, fence, ts) {
20714
+ const node2 = l2.getNode(fence.candidate);
20715
+ if (!node2 || node2.label !== "Claim" || Number(node2.attrs["abstractionLevel"] ?? ABSTRACTION_LEVEL.CONCRETE) < ABSTRACTION_LEVEL.PRINCIPLE) {
20716
+ return { applied: false, principleId: fence.candidate, reason: "no such abstraction candidate" };
20717
+ }
20718
+ if (node2.attrs["provisional"] !== true) {
20719
+ return { applied: false, principleId: fence.candidate, reason: "candidate already distilled" };
20720
+ }
20721
+ const members = Array.isArray(node2.attrs["members"]) ? node2.attrs["members"] : [];
20722
+ const want = new Set(members);
20723
+ const got = new Set(fence.covers);
20724
+ const missing = members.filter((id) => !got.has(id));
20725
+ const invented = fence.covers.filter((id) => !want.has(id));
20726
+ if (missing.length > 0 || invented.length > 0) {
20727
+ return {
20728
+ applied: false,
20729
+ principleId: fence.candidate,
20730
+ reason: `coverage mismatch \u2014 ${missing.length} member(s) uncovered, ${invented.length} non-member(s) invented`
20731
+ };
20732
+ }
20733
+ const principle = fence.principle.trim();
20734
+ if (!principle) return { applied: false, principleId: fence.candidate, reason: "empty principle" };
20735
+ if (isUnfilledPlaceholder(principle)) {
20736
+ return { applied: false, principleId: fence.candidate, reason: "unfilled template placeholder" };
20737
+ }
20738
+ const { revisit: _r, revisitReason: _rr, revisitSinceTs: _rs, ...rest2 } = node2.attrs;
20739
+ void _r;
20740
+ void _rr;
20741
+ void _rs;
20742
+ l2.updateNode(fence.candidate, {
20743
+ description: principle,
20744
+ attrs: { ...rest2, provisional: false, pendingDistillation: false, distilledAt: ts },
20745
+ lastUpdatedAt: ts
20746
+ });
20747
+ return { applied: true, principleId: fence.candidate };
20748
+ }
20749
+ function revisitContradictedPrinciples(l2, ts) {
20750
+ const report = { flagged: 0 };
20751
+ l2.transaction(() => {
20752
+ for (const n of l2.findNodesByLabel("Claim")) {
20753
+ if (Number(n.attrs["abstractionLevel"] ?? ABSTRACTION_LEVEL.CONCRETE) < ABSTRACTION_LEVEL.PRINCIPLE) continue;
20754
+ if (n.attrs["revisit"] === true) continue;
20755
+ const members = Array.isArray(n.attrs["members"]) ? n.attrs["members"] : [];
20756
+ const memberSet = new Set(members);
20757
+ let reason = "";
20758
+ for (const id of members) {
20759
+ const m = l2.getNode(id);
20760
+ if (!m) {
20761
+ reason = `member ${id} was removed`;
20762
+ break;
20763
+ }
20764
+ if (m.attrs["revisit"] === true) {
20765
+ reason = `member "${m.description}" needs revisit`;
20766
+ break;
20767
+ }
20768
+ const contradictors = [
20769
+ ...l2.outEdges(id, ["CONTRADICTS"]).map((e) => e.to),
20770
+ ...l2.inEdges(id, ["CONTRADICTS"]).map((e) => e.from)
20771
+ ];
20772
+ if (contradictors.some((other) => !memberSet.has(other))) {
20773
+ reason = `member "${m.description}" is now contradicted by external evidence`;
20774
+ break;
20775
+ }
20776
+ }
20777
+ if (!reason) continue;
20778
+ l2.updateNode(n.id, {
20779
+ attrs: {
20780
+ ...n.attrs,
20781
+ revisit: true,
20782
+ revisitReason: reason,
20783
+ revisitSinceTs: ts,
20784
+ provisional: true,
20785
+ // re-queue for re-distillation (P3 re-name)
20786
+ pendingDistillation: true
20787
+ },
20788
+ lastUpdatedAt: ts
20789
+ });
20790
+ report.flagged++;
20791
+ }
20792
+ });
20793
+ return report;
20794
+ }
20795
+ function harvestAbstractionFences(l2, text, ts) {
20796
+ let applied = 0;
20797
+ let rejected = 0;
20798
+ for (const fence of parseAbstractionFences(text)) {
20799
+ if (applyAbstractionFence(l2, fence, ts).applied) applied++;
20800
+ else rejected++;
20801
+ }
20802
+ return { applied, rejected };
20803
+ }
20804
+ var DEFAULT_MIN_CLUSTER_SIZE, DEFAULT_MIN_DISTINCT_CONTEXTS;
20805
+ var init_abstraction = __esm({
20806
+ "../../packages/local-graph/src/abstraction.ts"() {
20807
+ "use strict";
20808
+ init_src();
20809
+ init_src2();
20810
+ init_src3();
20811
+ init_percolate();
20812
+ init_triage2();
20813
+ DEFAULT_MIN_CLUSTER_SIZE = 3;
20814
+ DEFAULT_MIN_DISTINCT_CONTEXTS = 2;
20815
+ }
20816
+ });
20817
+
20818
+ // ../../packages/local-graph/src/community.ts
20819
+ function detectLocalCommunities(store, opts = {}) {
20820
+ const minSize = opts.minCommunitySize ?? DEFAULT_MIN_COMMUNITY_SIZE;
20821
+ const report = { candidates: 0, communities: 0, joint: 0, assigned: 0 };
20822
+ const ids = /* @__PURE__ */ new Set();
20823
+ for (const label of JOINT_COMMUNITY_LABELS) {
20824
+ for (const n of store.findNodesByLabel(label)) {
20825
+ if (label === "Problem" && n.attrs["resolvedAs"] === PROBLEM_RESOLUTION.FALSE_POSITIVE) continue;
20826
+ ids.add(n.id);
20827
+ }
20828
+ }
20829
+ report.candidates = ids.size;
20830
+ if (ids.size === 0) return report;
20831
+ const adj = /* @__PURE__ */ new Map();
20832
+ const protect = [];
20833
+ const add = (a, b, w) => {
20834
+ const l = adj.get(a) ?? [];
20835
+ l.push({ to: b, weight: w });
20836
+ adj.set(a, l);
20837
+ };
20838
+ for (const id of ids) {
20839
+ for (const e of store.outEdges(id, [...JOINT_COMMUNITY_EDGES])) {
20840
+ if (!ids.has(e.to)) continue;
20841
+ const conf = e.confidence > 0 ? e.confidence : 0.5;
20842
+ const w = Math.max(EDGE_WEIGHT[e.type] ?? 1, 0.1) * conf;
20843
+ add(id, e.to, w);
20844
+ add(e.to, id, w);
20845
+ if (isCausalProtected(e.type)) protect.push([id, e.to]);
20846
+ }
20847
+ }
20848
+ const comm = detectCommunitiesLeiden({ nodeIds: [...ids], adj }, { protect });
20849
+ const members = /* @__PURE__ */ new Map();
20850
+ for (const [id, c] of comm.community) {
20851
+ const l = members.get(c) ?? [];
20852
+ l.push(id);
20853
+ members.set(c, l);
20854
+ }
20855
+ store.transaction(() => {
20856
+ for (const [cid, mem] of members) {
20857
+ const qualifies = mem.length >= minSize;
20858
+ let hasLocal = false;
20859
+ let hasCloud = false;
20860
+ for (const id of mem) {
20861
+ const n = store.getNode(id);
20862
+ if (!n) continue;
20863
+ const pulled = n.attrs["source"] === "cloud";
20864
+ if (pulled) hasCloud = true;
20865
+ else hasLocal = true;
20866
+ const nextAttrs = { ...n.attrs };
20867
+ if (qualifies) nextAttrs["localCommunity"] = cid;
20868
+ else delete nextAttrs["localCommunity"];
20869
+ store.updateNode(id, { attrs: nextAttrs });
20870
+ if (!pulled) store.setCommunity(id, qualifies ? cid : null);
20871
+ }
20872
+ if (qualifies) {
20873
+ report.communities++;
20874
+ report.assigned += mem.length;
20875
+ if (hasLocal && hasCloud) report.joint++;
20876
+ }
20877
+ }
20878
+ });
20879
+ return report;
20880
+ }
20881
+ function communitySeeds(store, opts = {}) {
20882
+ const maxCommunities = opts.maxCommunities ?? 4;
20883
+ const maxSeeds = opts.maxSeeds ?? 32;
20884
+ const byCommunity = /* @__PURE__ */ new Map();
20885
+ for (const label of JOINT_COMMUNITY_LABELS) {
20886
+ for (const n of store.findNodesByLabel(label)) {
20887
+ const cid = n.attrs["localCommunity"];
20888
+ if (typeof cid !== "string") continue;
20889
+ const cloudId = n.attrs["cloudNodeId"];
20890
+ const l = byCommunity.get(cid) ?? [];
20891
+ l.push({
20892
+ id: n.id,
20893
+ pulled: n.attrs["source"] === "cloud",
20894
+ cloudId: typeof cloudId === "string" && cloudId !== n.id ? cloudId : null,
20895
+ ts: n.lastUpdatedAt
20896
+ });
20897
+ byCommunity.set(cid, l);
20898
+ }
20899
+ }
20900
+ if (byCommunity.size === 0) return [];
20901
+ const ranked = [...byCommunity.entries()].map(([cid, mem]) => ({ cid, mem, fresh: Math.max(...mem.map((m) => m.ts)) })).sort((a, b) => b.fresh - a.fresh).slice(0, maxCommunities);
20902
+ const seeds = [];
20903
+ const seen = /* @__PURE__ */ new Set();
20904
+ const push = (id) => {
20905
+ if (seeds.length >= maxSeeds || seen.has(id)) return;
20906
+ seen.add(id);
20907
+ seeds.push(id);
20908
+ };
20909
+ for (const { mem } of ranked) {
20910
+ const ordered = [...mem].sort(
20911
+ (a, b) => a.pulled === b.pulled ? b.ts - a.ts : a.pulled ? -1 : 1
20912
+ );
20913
+ for (const m of ordered) {
20914
+ push(m.id);
20915
+ if (m.cloudId) push(m.cloudId);
20916
+ }
20917
+ }
20918
+ return seeds;
20919
+ }
20920
+ function communityInductionRequests(store, opts = {}) {
20921
+ const maxCommunities = opts.maxCommunities ?? 2;
20922
+ const byCommunity = /* @__PURE__ */ new Map();
20923
+ for (const label of JOINT_COMMUNITY_LABELS) {
20924
+ for (const n of store.findNodesByLabel(label)) {
20925
+ const cid = n.attrs["localCommunity"];
20926
+ if (typeof cid !== "string") continue;
20927
+ const cloudId = n.attrs["cloudNodeId"];
20928
+ const l = byCommunity.get(cid) ?? [];
20929
+ l.push({
20930
+ id: n.id,
20931
+ pulled: n.attrs["source"] === "cloud",
20932
+ cloudId: typeof cloudId === "string" && cloudId !== n.id ? cloudId : null,
20933
+ ts: n.lastUpdatedAt,
20934
+ description: n.description
20935
+ });
20936
+ byCommunity.set(cid, l);
20937
+ }
20938
+ }
20939
+ if (byCommunity.size === 0) return [];
20940
+ return [...byCommunity.entries()].map(([cid, mem]) => ({ cid, mem, fresh: Math.max(...mem.map((m) => m.ts)) })).sort((a, b) => b.fresh - a.fresh).map(({ cid, mem }) => {
20941
+ const ids = /* @__PURE__ */ new Set();
20942
+ for (const m of mem) {
20943
+ ids.add(m.id);
20944
+ if (m.cloudId) ids.add(m.cloudId);
20945
+ }
20946
+ const freshestLocal = [...mem].filter((m) => !m.pulled).sort((a, b) => b.ts - a.ts)[0];
20947
+ return {
20948
+ communityId: cid,
20949
+ members: [...ids].slice(0, 64),
20950
+ context: (freshestLocal?.description ?? "recurring workspace problem cluster").slice(0, 200)
20951
+ };
20952
+ }).filter((r) => r.members.length >= MIN_INDUCTION_MEMBERS).slice(0, maxCommunities);
20953
+ }
20954
+ var JOINT_COMMUNITY_LABELS, JOINT_COMMUNITY_EDGES, DEFAULT_MIN_COMMUNITY_SIZE, MIN_INDUCTION_MEMBERS;
20955
+ var init_community2 = __esm({
20956
+ "../../packages/local-graph/src/community.ts"() {
20957
+ "use strict";
20958
+ init_src2();
20959
+ init_src3();
20960
+ JOINT_COMMUNITY_LABELS = [
20961
+ "Problem",
20962
+ "Solution",
20963
+ "RootCause",
20964
+ "Pattern"
20965
+ ];
20966
+ JOINT_COMMUNITY_EDGES = [
20967
+ "CAUSED_BY",
20968
+ "SOLVED_BY",
20969
+ "FIXED_BY",
20970
+ "CONTRADICTS",
20971
+ "INSTANCE_OF",
20972
+ "MATCHES",
20973
+ "IMPLEMENTS",
20974
+ "TRIAGED_BY",
20975
+ "INDICATES",
20976
+ "CONFIRMS",
20977
+ "RELATES_TO"
20978
+ ];
20979
+ DEFAULT_MIN_COMMUNITY_SIZE = 2;
20980
+ MIN_INDUCTION_MEMBERS = 3;
20981
+ }
20982
+ });
20983
+
20899
20984
  // ../../packages/local-graph/src/problem-dedup.ts
20900
20985
  function corroborations(n) {
20901
20986
  return Number(n.attrs["corroborations"] ?? 0);
@@ -21243,6 +21328,7 @@ __export(src_exports2, {
21243
21328
  ingestDesignProblem: () => ingestDesignProblem,
21244
21329
  isConstraintProblem: () => isConstraintProblem,
21245
21330
  isPlaceholderStatement: () => isPlaceholderStatement,
21331
+ isUnfilledPlaceholder: () => isUnfilledPlaceholder,
21246
21332
  linkProblemToLanguages: () => linkProblemToLanguages,
21247
21333
  linkProblemToPackages: () => linkProblemToPackages,
21248
21334
  linkProblemToSymbols: () => linkProblemToSymbols,
@@ -21256,10 +21342,12 @@ __export(src_exports2, {
21256
21342
  matchSymbolsInText: () => matchSymbolsInText,
21257
21343
  mergeCloudCounts: () => mergeCloudCounts,
21258
21344
  mergeDuplicateProblems: () => mergeDuplicateProblems,
21345
+ migrateProjectAlias: () => migrateProjectAlias,
21259
21346
  mintCitedPackageNode: () => mintCitedPackageNode,
21260
21347
  mintComponentNode: () => mintComponentNode,
21261
21348
  mintDomainNode: () => mintDomainNode,
21262
21349
  mintPatternNode: () => mintPatternNode,
21350
+ mintWorkspaceCausalFact: () => mintWorkspaceCausalFact,
21263
21351
  openGraphStore: () => openGraphStore,
21264
21352
  osNodeId: () => osNodeId,
21265
21353
  parseAbstractionFences: () => parseAbstractionFences,
@@ -21698,10 +21786,32 @@ function renderSnapshot(s) {
21698
21786
  lines.push("");
21699
21787
  }
21700
21788
  lines.push("### Open review queue");
21701
- if (s.reviewCount === 0) {
21789
+ const reviewItems = s.reviewItems ?? [];
21790
+ if (s.reviewCount === 0 && reviewItems.length === 0) {
21702
21791
  lines.push("- (none)");
21703
21792
  } else {
21704
- lines.push(`- ${s.reviewCount} items awaiting your review at ${s.reviewUiUrl}`);
21793
+ if (s.reviewCount > 0) lines.push(`- ${s.reviewCount} items awaiting your review at ${s.reviewUiUrl}`);
21794
+ for (const item of reviewItems) {
21795
+ if (item.kind === "discriminator") {
21796
+ lines.push(
21797
+ `- **A recurring diagnosis is one cheap check from promoting** \u2014 "${item.presenting}" usually turns out to be: ${item.cause}. If (and only if) you KNOW a cheap ante-hoc check that confirms it, answer in your reply:`
21798
+ );
21799
+ lines.push(" ```errata-triage");
21800
+ lines.push(` route: ${item.route}`);
21801
+ lines.push(" test: <the cheap check>");
21802
+ lines.push(" ```");
21803
+ } else {
21804
+ lines.push(
21805
+ `- **Name the principle this cluster of your beliefs generalizes** \u2014 it ships to the collective only once named, and the prose must cover ALL members in one sentence:`
21806
+ );
21807
+ for (const m of item.members) lines.push(` - ${m.description}`);
21808
+ lines.push(" ```errata-abstraction");
21809
+ lines.push(` candidate: ${item.candidate}`);
21810
+ lines.push(" principle: <one sentence covering all members>");
21811
+ lines.push(` covers: ${item.members.map((m) => m.id).join(", ")}`);
21812
+ lines.push(" ```");
21813
+ }
21814
+ }
21705
21815
  }
21706
21816
  return lines.join("\n");
21707
21817
  }
@@ -21711,6 +21821,12 @@ function dropLowestUnit(s) {
21711
21821
  case "skills":
21712
21822
  if (s.skills.length) return s.skills.pop(), true;
21713
21823
  break;
21824
+ case "reviewItemsOverFloor":
21825
+ if (s.reviewItems && s.reviewItems.length > REVIEW_ITEM_FLOOR) return s.reviewItems.pop(), true;
21826
+ break;
21827
+ case "reviewItems":
21828
+ if (s.reviewItems && s.reviewItems.length) return s.reviewItems.pop(), true;
21829
+ break;
21714
21830
  case "motifsOverFloor":
21715
21831
  if (s.motifs.length > MOTIF_FLOOR) return s.motifs.pop(), true;
21716
21832
  break;
@@ -21765,6 +21881,7 @@ function assembleAgentContext(opts) {
21765
21881
  });
21766
21882
  if (opts.edgeElicitation) snapshot.edgeElicitation = opts.edgeElicitation;
21767
21883
  if (opts.pendingUpdate) snapshot.pendingUpdate = opts.pendingUpdate;
21884
+ if (opts.reviewItems?.length) snapshot.reviewItems = [...opts.reviewItems];
21768
21885
  if (opts.remote && opts.remote.length > 0) {
21769
21886
  const seen = /* @__PURE__ */ new Set();
21770
21887
  const remote = opts.remote.filter((n) => {
@@ -21787,7 +21904,7 @@ _${dropped} lower-priority item${dropped === 1 ? "" : "s"} omitted to fit the pa
21787
21904
  }
21788
21905
  return { body: body2, snapshot, dropped };
21789
21906
  }
21790
- var RECALL_FIRST_HEADER, RECALL_FIRST_BODY, RECALL_FIRST_BLOCK, SEARCH_IMPERATIVE_HEADER, SEARCH_IMPERATIVE_BODY, EVICTION_ORDER, MOTIF_FLOOR, REMOTE_FLOOR, PROBLEM_FLOOR, DEFAULT_AGENT_CONTEXT_BUDGET;
21907
+ var RECALL_FIRST_HEADER, RECALL_FIRST_BODY, RECALL_FIRST_BLOCK, SEARCH_IMPERATIVE_HEADER, SEARCH_IMPERATIVE_BODY, EVICTION_ORDER, MOTIF_FLOOR, REMOTE_FLOOR, PROBLEM_FLOOR, REVIEW_ITEM_FLOOR, DEFAULT_AGENT_CONTEXT_BUDGET;
21791
21908
  var init_render = __esm({
21792
21909
  "../../packages/context-writer/src/render.ts"() {
21793
21910
  "use strict";
@@ -21802,6 +21919,9 @@ ${RECALL_FIRST_BODY}`;
21802
21919
  SEARCH_IMPERATIVE_BODY = "Everything below is a budgeted, top-of-head slice of a much larger graph. Any prior id below is a live burst seed: `errata.burst` it (or read `.errata/g/burst/<id>`) to pull its wider neighborhood \u2014 causes, fixes, siblings. When a prior is adjacent-but-not-quite, or none fit, that gap is exactly when to search deeper before solving cold: a stuck search is itself a signal that routes help to you.";
21803
21920
  EVICTION_ORDER = [
21804
21921
  "skills",
21922
+ // Review items beyond the floor drop immediately after skills — one naming
21923
+ // ask per render is the design (the caller rotates), extras are padding.
21924
+ "reviewItemsOverFloor",
21805
21925
  // Collective floors (EE-evidence-live): motifs/remote trim to a FLOOR here and
21806
21926
  // fully drain only near the very end. The unfloored order zeroed them on every
21807
21927
  // render (~44 units dropped per block, live 8-01), which starved §5.4 at the
@@ -21837,11 +21957,22 @@ ${RECALL_FIRST_BODY}`;
21837
21957
  "recentProblemsOverFloor",
21838
21958
  "motifs",
21839
21959
  "remote",
21960
+ // The floored review item outlives even the terminal motif/remote stages ON
21961
+ // PURPOSE: the block's deficit reaches those stages on EVERY render (measured
21962
+ // — non-evictable instruction prose leaves ~2k discretionary, and live
21963
+ // renders land at exactly the problem floor with motifs/remote at zero), so
21964
+ // any earlier slot is structurally invisible — the same starvation that
21965
+ // killed the pull surfaces this band replaces. Cost: one compact ask ≈ one
21966
+ // remote prior. That trade is deliberate: the ask IS a witness elicitation
21967
+ // (a discriminator/principle is §5.4-grade evidence nothing else produces),
21968
+ // and it still yields to the agent's own live problems.
21969
+ "reviewItems",
21840
21970
  "recentProblems"
21841
21971
  ];
21842
21972
  MOTIF_FLOOR = 2;
21843
21973
  REMOTE_FLOOR = 3;
21844
21974
  PROBLEM_FLOOR = 4;
21975
+ REVIEW_ITEM_FLOOR = 1;
21845
21976
  DEFAULT_AGENT_CONTEXT_BUDGET = 9e3;
21846
21977
  }
21847
21978
  });
@@ -26344,7 +26475,8 @@ function mergeDualBurst(local, cloud, opts = {}) {
26344
26475
  provenance: corroborated ? "corroborated" : "local",
26345
26476
  ...twin?.usageCount != null ? { usageCount: twin.usageCount } : {},
26346
26477
  ...twin?.rel ? { rel: twin.rel } : {},
26347
- why: l.components
26478
+ why: l.components,
26479
+ ...l.via ? { via: l.via } : {}
26348
26480
  });
26349
26481
  }
26350
26482
  let collectiveCount = 0;
@@ -26531,7 +26663,8 @@ function formatDualNodes(merged) {
26531
26663
  centrality: Number(n.why.centrality.toFixed(3)),
26532
26664
  trust: n.why.trust
26533
26665
  }
26534
- } : {}
26666
+ } : {},
26667
+ ...n.via ? { via: n.via } : {}
26535
26668
  }));
26536
26669
  }
26537
26670
  var BRIDGE_LABELS, CANONICAL_KNOWLEDGE_ID, UUID_ID;
@@ -28275,14 +28408,19 @@ function localBurst(store, seedId, opts = {}) {
28275
28408
  const seed = store.getNode(seedId);
28276
28409
  if (!seed) return [];
28277
28410
  const hopOf = /* @__PURE__ */ new Map([[seedId, 0]]);
28411
+ const viaOf = /* @__PURE__ */ new Map();
28278
28412
  let frontier = [seedId];
28279
28413
  for (let h = 1; h <= maxHops && hopOf.size < nodeCap; h++) {
28280
28414
  const next = [];
28281
28415
  for (const id of frontier) {
28282
- const neigh = [...store.outEdges(id).map((e) => e.to), ...store.inEdges(id).map((e) => e.from)];
28283
- for (const nb of neigh) {
28416
+ const neigh = [
28417
+ ...store.outEdges(id).map((e) => ({ nb: e.to, via: { edgeType: e.type, parentId: id, forward: true } })),
28418
+ ...store.inEdges(id).map((e) => ({ nb: e.from, via: { edgeType: e.type, parentId: id, forward: false } }))
28419
+ ];
28420
+ for (const { nb, via } of neigh) {
28284
28421
  if (!hopOf.has(nb)) {
28285
28422
  hopOf.set(nb, h);
28423
+ viaOf.set(nb, via);
28286
28424
  next.push(nb);
28287
28425
  if (hopOf.size >= nodeCap) break;
28288
28426
  }
@@ -28304,7 +28442,8 @@ function localBurst(store, seedId, opts = {}) {
28304
28442
  const now = store.currentIngestSeq();
28305
28443
  return nodes.map(({ node: node2, hops }) => {
28306
28444
  const r = scoreCandidate(node2, { ...ctxSeed, hops, decay: nodeFreshness(node2, now) }, weights);
28307
- return { id: node2.id, score: r.score, components: r.components, node: node2, hops };
28445
+ const via = viaOf.get(node2.id);
28446
+ return { id: node2.id, score: r.score, components: r.components, node: node2, hops, ...via ? { via } : {} };
28308
28447
  }).filter((r) => !gated || r.score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
28309
28448
  }
28310
28449
  var init_relevance_rank = __esm({
@@ -38028,7 +38167,7 @@ var init_mcp = __esm({
38028
38167
  },
38029
38168
  {
38030
38169
  name: "errata.burst",
38031
- description: "Burst-grade local retrieval \u2014 the INTELLIGENT, seed-relative neighborhood (not a flat list). Given a SEED (`nodeId` or `qname`), expand its neighborhood and return the RELEVANT scored subset, ranked by burst-style relevance: semantic similarity to the seed \xD7 centrality \xD7 trust, hop-decayed; semantically-unrelated nodes are gated out. Prefer this over errata.neighbors when you want signal-ranked CONTEXT rather than raw edges. When the SEED is a package and the collective is reachable, results BLEND cross-project knowledge \u2014 each carries `provenance` (local | collective | corroborated) and `usageCount` (projects using it); `corroborated` means your graph and the collective agree (trust it most), `collective` is a cross-project prior to verify. Returns {seed, count, results:[{id,label,name,score,hops,provenance,usageCount?,why?}]}.",
38170
+ description: "Burst-grade local retrieval \u2014 the INTELLIGENT, seed-relative neighborhood (not a flat list). Given a SEED (`nodeId` or `qname`), expand its neighborhood and return the RELEVANT scored subset, ranked by burst-style relevance: semantic similarity to the seed \xD7 centrality \xD7 trust, hop-decayed; semantically-unrelated nodes are gated out. Prefer this over errata.neighbors when you want signal-ranked CONTEXT rather than raw edges. When the SEED is a package and the collective is reachable, results BLEND cross-project knowledge \u2014 each carries `provenance` (local | collective | corroborated) and `usageCount` (projects using it); `corroborated` means your graph and the collective agree (trust it most), `collective` is a cross-project prior to verify. Returns {seed, count, results:[{id,label,name,score,hops,provenance,usageCount?,why?,via?}]} \u2014 `via` is the TOPOLOGY: the edge that reached this result in the walk ({edgeType, parentId, forward}; forward: true \u21D2 parent \u2014edgeType\u2192 node), so you can tell 'FIXED_BY your seed' from 'two hops away via a shared package' and choose whether to dig down a causal chain or turn elsewhere.",
38032
38171
  inputSchema: {
38033
38172
  type: "object",
38034
38173
  properties: {
@@ -38072,7 +38211,8 @@ var init_mcp = __esm({
38072
38211
  node: { label: r.node.label, description: r.node.description },
38073
38212
  score: r.score,
38074
38213
  hops: r.hops ?? 0,
38075
- components: r.components
38214
+ components: r.components,
38215
+ ...r.via ? { via: r.via } : {}
38076
38216
  }));
38077
38217
  const seedNode = store.getNode(id);
38078
38218
  let collectiveStatus = "local-only";
@@ -38118,7 +38258,8 @@ var init_mcp = __esm({
38118
38258
  semantic: Number(r.components.semantic.toFixed(3)),
38119
38259
  centrality: Number(r.components.centrality.toFixed(3)),
38120
38260
  trust: r.components.trust
38121
- }
38261
+ },
38262
+ ...r.via ? { via: r.via } : {}
38122
38263
  }))
38123
38264
  };
38124
38265
  }
@@ -52874,6 +53015,12 @@ function sessionOriginKey(sessionId) {
52874
53015
  function refreshRepoLocator(root, profile) {
52875
53016
  const detected = detectRepoLocator(root, profile.repoRemote);
52876
53017
  if (!detected || detected === profile.repoLocator) return false;
53018
+ if (profile.repoLocator) {
53019
+ const aliases = new Set(profile.repoLocatorAliases ?? []);
53020
+ aliases.add(profile.repoLocator);
53021
+ aliases.delete(detected);
53022
+ profile.repoLocatorAliases = [...aliases];
53023
+ }
52877
53024
  profile.repoLocator = detected;
52878
53025
  saveProfile(root, profile);
52879
53026
  return true;
@@ -53264,7 +53411,7 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
53264
53411
  }
53265
53412
 
53266
53413
  // src/engine.ts
53267
- var DAEMON_VERSION = true ? "2.0.2-dev.361" : "2.0.0-alpha.0";
53414
+ var DAEMON_VERSION = true ? "2.0.2-dev.417" : "2.0.0-alpha.0";
53268
53415
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
53269
53416
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
53270
53417
  var GIT_OP_MUTE_MS = 4e3;
@@ -53600,6 +53747,27 @@ function createWorkspaceEngine(opts) {
53600
53747
  }
53601
53748
  }
53602
53749
  };
53750
+ const clip = (s, n) => s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
53751
+ const pickReviewItems = () => {
53752
+ const shared = opts.sharedStore;
53753
+ if (!shared) return [];
53754
+ try {
53755
+ if ((/* @__PURE__ */ new Date()).getHours() % 2 === 0) {
53756
+ const small = pendingAbstractions(shared).filter((a) => a.members.length >= 2 && a.members.length <= 5).sort((a, b) => a.members.length - b.members.length)[0];
53757
+ if (small) {
53758
+ return [{
53759
+ kind: "abstraction",
53760
+ candidate: small.candidate,
53761
+ members: small.members.map((m) => ({ id: m.id, description: clip(m.description, 110) }))
53762
+ }];
53763
+ }
53764
+ }
53765
+ const [d] = pendingDiscriminators(shared, { limit: 1 });
53766
+ return d ? [{ kind: "discriminator", route: d.route, presenting: clip(d.presenting, 140), cause: clip(d.cause, 140) }] : [];
53767
+ } catch {
53768
+ return [];
53769
+ }
53770
+ };
53603
53771
  const refreshContextOnce = async () => {
53604
53772
  const elicit = isEdgeElicitationEnabled();
53605
53773
  const snapOpts = {
@@ -53613,13 +53781,15 @@ function createWorkspaceEngine(opts) {
53613
53781
  const prebuilt = passWorker ? await passWorker.snapshot(snapOpts).catch(() => null) : null;
53614
53782
  const doneRender = prebuilt ? null : markPass(`context-render-inline:${profile.name}`);
53615
53783
  const pendingUpdate = opts.pendingUpdate?.() ?? null;
53784
+ const reviewItems = pickReviewItems();
53616
53785
  const { body: body2, snapshot } = assembleAgentContext({
53617
53786
  store,
53618
53787
  ...snapOpts,
53619
53788
  remote: remotePriors,
53620
53789
  ...elicit ? { edgeElicitation: { instruction: PRIOR_TAG_INSTRUCTION } } : {},
53621
53790
  ...prebuilt ? { snapshot: prebuilt } : {},
53622
- ...pendingUpdate ? { pendingUpdate } : {}
53791
+ ...pendingUpdate ? { pendingUpdate } : {},
53792
+ ...reviewItems.length ? { reviewItems } : {}
53623
53793
  });
53624
53794
  doneRender?.();
53625
53795
  writeContextFile(opts.workspaceRoot, body2);
@@ -54102,6 +54272,22 @@ function createWorkspaceEngine(opts) {
54102
54272
  } catch (err2) {
54103
54273
  console.warn("[errata] inline triage route failed:", err2);
54104
54274
  }
54275
+ if (evidence !== "inferred") {
54276
+ try {
54277
+ mintWorkspaceCausalFact(
54278
+ store,
54279
+ {
54280
+ presentingId,
54281
+ causeId: tr.causeId,
54282
+ ...tr.causeDescription ? { causeDescription: tr.causeDescription } : {},
54283
+ sessionId
54284
+ },
54285
+ t
54286
+ );
54287
+ } catch (err2) {
54288
+ console.warn("[errata] workspace causal-fact mirror failed:", err2);
54289
+ }
54290
+ }
54105
54291
  }
54106
54292
  }
54107
54293
  if (opts.sharedStore) {
@@ -54115,6 +54301,13 @@ function createWorkspaceEngine(opts) {
54115
54301
  }
54116
54302
  }
54117
54303
  }
54304
+ for (const link of plan.causalLinks) {
54305
+ try {
54306
+ recordCauseChainLink(store, link, t, { contributor: sessionId, sources: [sessionId] });
54307
+ } catch (err2) {
54308
+ console.warn("[errata] workspace causal chain mirror failed:", err2);
54309
+ }
54310
+ }
54118
54311
  for (const at of plan.attempts) {
54119
54312
  const pid = at.problemId ?? (at.threadId ? threads.get(at.threadId) : void 0) ?? (at.boundStatement ? designProblemId(at.boundStatement) : void 0);
54120
54313
  const node2 = pid ? store.getNode(pid) : void 0;
@@ -56453,6 +56646,37 @@ async function startMultiDaemon(opts = {}) {
56453
56646
  async percolateAll() {
56454
56647
  const ts = Date.now();
56455
56648
  const out2 = /* @__PURE__ */ new Map();
56649
+ const repoBasename = (locator) => (locator.split("/").filter(Boolean).pop() ?? locator).toLowerCase();
56650
+ for (const r of records) {
56651
+ const profile = r.engine.profile;
56652
+ const aliases = profile.repoLocatorAliases ?? [];
56653
+ const locator = profile.repoLocator;
56654
+ if (!locator || aliases.length === 0) continue;
56655
+ try {
56656
+ for (const alias of aliases) {
56657
+ if (repoBasename(alias) !== repoBasename(locator)) {
56658
+ console.log(
56659
+ `[errata] project alias NOT auto-migrated for ${r.entry.name} (${alias} \u2192 ${locator}: repo name differs \u2014 a fork/repoint, not a rename). Run scripts/migrate-project-alias.ts if this really is the same repo.`
56660
+ );
56661
+ continue;
56662
+ }
56663
+ const m = migrateProjectAlias(sharedStore, { from: alias, to: locator, ts });
56664
+ if (m.nodesRewritten || m.edgesRewritten) {
56665
+ console.log(
56666
+ `[errata] project alias migrated for ${r.entry.name}: ${alias} \u2192 ${locator} (${m.nodesRewritten} nodes, ${m.edgesRewritten} edges)`
56667
+ );
56668
+ }
56669
+ }
56670
+ delete profile.repoLocatorAliases;
56671
+ const onDisk = loadProfile(r.root);
56672
+ if (onDisk) {
56673
+ delete onDisk.repoLocatorAliases;
56674
+ saveProfile(r.root, onDisk);
56675
+ }
56676
+ } catch (err2) {
56677
+ console.warn(`[errata] project-alias migration failed for ${r.entry.name} (kept for next pass): ${err2 instanceof Error ? err2.message : err2}`);
56678
+ }
56679
+ }
56456
56680
  let inlineRecords = records;
56457
56681
  if (consolidateWorker) {
56458
56682
  const fileBacked = records.filter((r) => r.engine.paths.castalia !== ":memory:");