@inerrata-corporation/errata 2.0.2-dev.362 → 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
  });
@@ -52884,6 +53015,12 @@ function sessionOriginKey(sessionId) {
52884
53015
  function refreshRepoLocator(root, profile) {
52885
53016
  const detected = detectRepoLocator(root, profile.repoRemote);
52886
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
+ }
52887
53024
  profile.repoLocator = detected;
52888
53025
  saveProfile(root, profile);
52889
53026
  return true;
@@ -53274,7 +53411,7 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
53274
53411
  }
53275
53412
 
53276
53413
  // src/engine.ts
53277
- var DAEMON_VERSION = true ? "2.0.2-dev.362" : "2.0.0-alpha.0";
53414
+ var DAEMON_VERSION = true ? "2.0.2-dev.417" : "2.0.0-alpha.0";
53278
53415
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
53279
53416
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
53280
53417
  var GIT_OP_MUTE_MS = 4e3;
@@ -53610,6 +53747,27 @@ function createWorkspaceEngine(opts) {
53610
53747
  }
53611
53748
  }
53612
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
+ };
53613
53771
  const refreshContextOnce = async () => {
53614
53772
  const elicit = isEdgeElicitationEnabled();
53615
53773
  const snapOpts = {
@@ -53623,13 +53781,15 @@ function createWorkspaceEngine(opts) {
53623
53781
  const prebuilt = passWorker ? await passWorker.snapshot(snapOpts).catch(() => null) : null;
53624
53782
  const doneRender = prebuilt ? null : markPass(`context-render-inline:${profile.name}`);
53625
53783
  const pendingUpdate = opts.pendingUpdate?.() ?? null;
53784
+ const reviewItems = pickReviewItems();
53626
53785
  const { body: body2, snapshot } = assembleAgentContext({
53627
53786
  store,
53628
53787
  ...snapOpts,
53629
53788
  remote: remotePriors,
53630
53789
  ...elicit ? { edgeElicitation: { instruction: PRIOR_TAG_INSTRUCTION } } : {},
53631
53790
  ...prebuilt ? { snapshot: prebuilt } : {},
53632
- ...pendingUpdate ? { pendingUpdate } : {}
53791
+ ...pendingUpdate ? { pendingUpdate } : {},
53792
+ ...reviewItems.length ? { reviewItems } : {}
53633
53793
  });
53634
53794
  doneRender?.();
53635
53795
  writeContextFile(opts.workspaceRoot, body2);
@@ -54112,6 +54272,22 @@ function createWorkspaceEngine(opts) {
54112
54272
  } catch (err2) {
54113
54273
  console.warn("[errata] inline triage route failed:", err2);
54114
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
+ }
54115
54291
  }
54116
54292
  }
54117
54293
  if (opts.sharedStore) {
@@ -54125,6 +54301,13 @@ function createWorkspaceEngine(opts) {
54125
54301
  }
54126
54302
  }
54127
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
+ }
54128
54311
  for (const at of plan.attempts) {
54129
54312
  const pid = at.problemId ?? (at.threadId ? threads.get(at.threadId) : void 0) ?? (at.boundStatement ? designProblemId(at.boundStatement) : void 0);
54130
54313
  const node2 = pid ? store.getNode(pid) : void 0;
@@ -56463,6 +56646,37 @@ async function startMultiDaemon(opts = {}) {
56463
56646
  async percolateAll() {
56464
56647
  const ts = Date.now();
56465
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
+ }
56466
56680
  let inlineRecords = records;
56467
56681
  if (consolidateWorker) {
56468
56682
  const fileBacked = records.filter((r) => r.engine.paths.castalia !== ":memory:");