@inerrata-corporation/errata 2.0.0-dev.39 → 2.0.0-dev.41

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
@@ -18445,7 +18445,7 @@ var init_triage = __esm({
18445
18445
  // ../../packages/math/src/relevance.ts
18446
18446
  function scoreRelevance(input, weights) {
18447
18447
  const w = { ...DEFAULT_RELEVANCE_WEIGHTS, ...weights };
18448
- const semantic = input.semantic == null ? 1 : clamp01((input.semantic - w.semanticFloor) / (1 - w.semanticFloor));
18448
+ const semantic = input.semantic != null ? clamp01((input.semantic - w.semanticFloor) / (1 - w.semanticFloor)) : input.semanticUnknown ? clamp01(w.semanticNeutral) : 1;
18449
18449
  const specificity = 1 + w.specificityWeight * clamp01(input.specificity ?? 0);
18450
18450
  const centrality = 1 + w.centralityWeight * Math.tanh((input.centrality ?? 0) / w.centralityScale);
18451
18451
  const quality = 1 + w.qualityWeight * Math.tanh((input.quality ?? 0) / w.qualityScale);
@@ -18462,6 +18462,7 @@ var init_relevance = __esm({
18462
18462
  "use strict";
18463
18463
  DEFAULT_RELEVANCE_WEIGHTS = {
18464
18464
  semanticFloor: 0.5,
18465
+ semanticNeutral: 0.4,
18465
18466
  centralityWeight: 0.5,
18466
18467
  centralityScale: 1,
18467
18468
  qualityWeight: 0.4,
@@ -20054,11 +20055,15 @@ function buildSnapshot(opts) {
20054
20055
  languages: opts.profile.languages.map((name2) => ({ name: name2, node: matchNode(langNodes, name2) })),
20055
20056
  packages: opts.profile.stack.map((name2) => ({ name: name2, node: matchNode(pkgNodes, pkgBase(name2)) }))
20056
20057
  };
20058
+ const causalNudge = recent.length >= 2 && recent.every(
20059
+ (r) => opts.store.outEdges(r.node.id, ["CAUSED_BY"]).length === 0 && opts.store.inEdges(r.node.id, ["CAUSED_BY"]).length === 0
20060
+ );
20057
20061
  return {
20058
20062
  profile: opts.profile,
20059
20063
  profileContext,
20060
20064
  recentProblems: recent,
20061
20065
  recentResolved,
20066
+ ...causalNudge ? { causalNudge } : {},
20062
20067
  motifs,
20063
20068
  reviewCount: opts.reviewCount,
20064
20069
  reviewUiUrl: opts.reviewUiUrl,
@@ -20205,6 +20210,11 @@ function renderSnapshot(s) {
20205
20210
  const surprise = (r.node.cumulativeSurprise ?? 0).toFixed(2);
20206
20211
  lines.push(` - observed ${hits}\xD7 \xB7 cumulativeSurprise ${surprise}`);
20207
20212
  }
20213
+ if (s.causalNudge) {
20214
+ lines.push(
20215
+ "_None of these problems are causally linked yet \u2014 if one caused another (or your report says so in prose), bind them with `(cause:#slug \u2026)` / `(cause:[handle])`. Trace can only follow edges you declare._"
20216
+ );
20217
+ }
20208
20218
  }
20209
20219
  if (s.recentResolved.length > 0) {
20210
20220
  lines.push("_Recently resolved \u2014 solved here; jumping-off points, not live defects:_");
@@ -20857,6 +20867,18 @@ var init_client = __esm({
20857
20867
  async me() {
20858
20868
  return this.json("GET", "/v2/me");
20859
20869
  }
20870
+ // ─── acting-agent switching (gateway control plane, #898) ───────────
20871
+ /** Agents the authenticated user may act as, plus their default agent.
20872
+ * Authenticated with the caller's console-signed gateway JWT. */
20873
+ async actableAgents() {
20874
+ return this.json("GET", "/api/gate/actable-agents");
20875
+ }
20876
+ /** Mint a fresh gateway JWT stamped with `handle` (authz-checked + audited
20877
+ * server-side). The returned `token` bears the acting-agent identity for
20878
+ * subsequent cloud calls. Authenticated with the caller's base JWT. */
20879
+ async actAs(handle2) {
20880
+ return this.json("POST", "/api/gate/act-as", { handle: handle2 });
20881
+ }
20860
20882
  // ─── ingest ────────────────────────────────────────────────────────
20861
20883
  /** Upload one outbox batch (projected onto the reconciled wire) and fold
20862
20884
  * the per-decision response into flush accounting.
@@ -21337,7 +21359,8 @@ function defaultConfig() {
21337
21359
  notifications: true,
21338
21360
  onboardedAt: null,
21339
21361
  machineId: null,
21340
- updateChannel: "dev"
21362
+ updateChannel: "dev",
21363
+ activeAgent: null
21341
21364
  };
21342
21365
  }
21343
21366
  function loadConfig() {
@@ -21348,7 +21371,7 @@ function loadConfig() {
21348
21371
  saveConfig(cfg);
21349
21372
  return cfg;
21350
21373
  }
21351
- const raw2 = readFileSync3(p, "utf8");
21374
+ const raw2 = readFileSync3(p, "utf8").replace(/^\uFEFF/, "");
21352
21375
  const parsed = JSON.parse(raw2);
21353
21376
  const base = defaultConfig();
21354
21377
  return {
@@ -21372,6 +21395,19 @@ function machineId() {
21372
21395
  saveConfig({ ...cfg, machineId: id });
21373
21396
  return id;
21374
21397
  }
21398
+ function getActiveAgent(cfg = loadConfig()) {
21399
+ return cfg.activeAgent ?? null;
21400
+ }
21401
+ function setActiveAgent(agent, cfg = loadConfig()) {
21402
+ const next = { ...cfg, activeAgent: { handle: agent.handle, id: agent.id } };
21403
+ saveConfig(next);
21404
+ return next;
21405
+ }
21406
+ function clearActiveAgent(cfg = loadConfig()) {
21407
+ const next = { ...cfg, activeAgent: null };
21408
+ saveConfig(next);
21409
+ return next;
21410
+ }
21375
21411
  var DEFAULT_CLOUD_URL;
21376
21412
  var init_config = __esm({
21377
21413
  "src/config.ts"() {
@@ -24557,6 +24593,18 @@ import { mkdirSync as mkdirSync4, existsSync as existsSync6 } from "node:fs";
24557
24593
  import { homedir as homedir2 } from "node:os";
24558
24594
  import { dirname as dirname3, join as join4 } from "node:path";
24559
24595
  import { createRequire } from "node:module";
24596
+ function semanticFloorFor(version2) {
24597
+ if (version2 === EMBEDDING_VERSION) return 0.25;
24598
+ return 0.5;
24599
+ }
24600
+ function noteHashFallback() {
24601
+ if (warnedHashFallback) return;
24602
+ if (process.env["ERRATA_EMBED_HASH_ONLY"] === "1") return;
24603
+ warnedHashFallback = true;
24604
+ console.warn(
24605
+ `[errata] embedding model unavailable (${lastError?.message ?? "unknown"}) \u2014 falling back to ${EMBEDDING_VERSION} hash vectors; semantic ranking runs at reduced fidelity`
24606
+ );
24607
+ }
24560
24608
  function getCacheDir() {
24561
24609
  return process.env["ERRATA_MODEL_CACHE"] ?? join4(homedir2(), ".errata", "models");
24562
24610
  }
@@ -24647,6 +24695,7 @@ async function embedBatchWithModelOrHash(texts) {
24647
24695
  }
24648
24696
  } catch {
24649
24697
  }
24698
+ noteHashFallback();
24650
24699
  return texts.map((t) => {
24651
24700
  const v = embed(t);
24652
24701
  return { vector: v, version: EMBEDDING_VERSION, dim: v.length };
@@ -24661,11 +24710,12 @@ async function embedTextWithModelOrHash(text) {
24661
24710
  const v = await embedTextWithModel(text);
24662
24711
  return { vector: v, version: MODEL_EMBEDDING_VERSION, dim: v.length };
24663
24712
  } catch {
24713
+ noteHashFallback();
24664
24714
  const v = embed(text);
24665
24715
  return { vector: v, version: EMBEDDING_VERSION, dim: v.length };
24666
24716
  }
24667
24717
  }
24668
- var MODEL_NAME, MODEL_EMBEDDING_DIM, MODEL_EMBEDDING_VERSION, pipelinePromise, lastError;
24718
+ var MODEL_NAME, MODEL_EMBEDDING_DIM, MODEL_EMBEDDING_VERSION, pipelinePromise, lastError, warnedHashFallback;
24669
24719
  var init_model = __esm({
24670
24720
  "../../packages/embedding/src/model.ts"() {
24671
24721
  "use strict";
@@ -24675,6 +24725,7 @@ var init_model = __esm({
24675
24725
  MODEL_EMBEDDING_VERSION = "minilm-l6-v2";
24676
24726
  pipelinePromise = null;
24677
24727
  lastError = null;
24728
+ warnedHashFallback = false;
24678
24729
  }
24679
24730
  });
24680
24731
 
@@ -25834,11 +25885,11 @@ var init_generalizer = __esm({
25834
25885
  function layerOf(kind) {
25835
25886
  return kind === "Technique" ? "technique" : kind === "AntiPattern" ? "antipattern" : "pattern";
25836
25887
  }
25837
- function motifTitle(members, layer) {
25888
+ function motifTitle(members, layer, instanceCount) {
25838
25889
  const top = [...members].sort(
25839
25890
  (a, b) => b.extractionConfidence - a.extractionConfidence
25840
25891
  )[0];
25841
- return `${layer}: ${top?.description ?? "recurring problem cluster"}`;
25892
+ return `${layer} \xD7${instanceCount} (exemplar): ${top?.description ?? "recurring problem cluster"}`;
25842
25893
  }
25843
25894
  function classifyMotif(store, instances, promoted) {
25844
25895
  if (!promoted || instances.length === 0) return "Pattern";
@@ -25884,7 +25935,9 @@ function promoteMotifs(store, t) {
25884
25935
  const motif = {
25885
25936
  id: motifId,
25886
25937
  label: kind,
25887
- description: existing?.description ?? motifTitle(members, layerOf(kind)),
25938
+ // Refresh the deterministic title every run (exemplar/count/kind drift),
25939
+ // but never clobber a distilled one — a distillation pass owns it then.
25940
+ description: existing?.attrs["titleDistilled"] === true ? existing.description : motifTitle(members, layerOf(kind), instanceCount),
25888
25941
  extractionConfidence: d.motifConfidence,
25889
25942
  extractionSource: "bko-inferred",
25890
25943
  embedding: existing?.embedding ?? [],
@@ -26095,8 +26148,8 @@ async function embedNodesByLabels(store, labels, opts = {}) {
26095
26148
  durationMs: 0,
26096
26149
  versionsSeen: {}
26097
26150
  };
26098
- const probe = await embedBatchWithModelOrHash(["probe"]);
26099
- const currentVersion = probe[0]?.version ?? "unknown";
26151
+ const hashOnly = opts.mode === "hash";
26152
+ const currentVersion = hashOnly ? EMBEDDING_VERSION : (await embedBatchWithModelOrHash(["probe"]))[0]?.version ?? "unknown";
26100
26153
  const todo = [];
26101
26154
  for (const label of labels) {
26102
26155
  for (const node2 of store.findNodesByLabel(label)) {
@@ -26105,6 +26158,10 @@ async function embedNodesByLabels(store, labels, opts = {}) {
26105
26158
  if (existingVersion) {
26106
26159
  report.versionsSeen[existingVersion] = (report.versionsSeen[existingVersion] ?? 0) + 1;
26107
26160
  }
26161
+ if (hashOnly && node2.embedding.length > 0) {
26162
+ report.skippedFresh++;
26163
+ continue;
26164
+ }
26108
26165
  if (!opts.force && existingVersion === currentVersion && node2.embedding.length > 0) {
26109
26166
  report.skippedFresh++;
26110
26167
  continue;
@@ -26118,7 +26175,10 @@ async function embedNodesByLabels(store, labels, opts = {}) {
26118
26175
  const batch = todo.slice(i2, i2 + BATCH_SIZE);
26119
26176
  let results;
26120
26177
  try {
26121
- results = await embedBatchWithModelOrHash(batch.map((b) => b.text));
26178
+ results = hashOnly ? batch.map((b) => {
26179
+ const v = embed(b.text);
26180
+ return { vector: v, version: EMBEDDING_VERSION, dim: v.length };
26181
+ }) : await embedBatchWithModelOrHash(batch.map((b) => b.text));
26122
26182
  } catch {
26123
26183
  report.failed += batch.length;
26124
26184
  continue;
@@ -26184,12 +26244,15 @@ function nodeFreshness(node2, currentIngestSeq) {
26184
26244
  }
26185
26245
  function scoreCandidate(candidate, ctx = {}, weights) {
26186
26246
  const sameVersion = !ctx.seedEmbeddingVersion || !candidate.attrs["embeddingVersion"] || candidate.attrs["embeddingVersion"] === ctx.seedEmbeddingVersion;
26187
- const semantic = ctx.seedEmbedding && candidate.embedding.length === ctx.seedEmbedding.length && sameVersion ? cosine(ctx.seedEmbedding, candidate.embedding) : void 0;
26247
+ const comparable = ctx.seedEmbedding != null && candidate.embedding.length === ctx.seedEmbedding.length && sameVersion;
26248
+ const semantic = comparable ? cosine(ctx.seedEmbedding, candidate.embedding) : void 0;
26249
+ const semanticUnknown = ctx.seedEmbedding != null && !comparable;
26188
26250
  const quality = typeof candidate.attrs["effectivenessScore"] === "number" ? candidate.attrs["effectivenessScore"] : void 0;
26189
26251
  const trustTier = candidate.attrs["trustTier"];
26190
26252
  return scoreRelevance(
26191
26253
  {
26192
26254
  ...semantic !== void 0 ? { semantic } : {},
26255
+ ...semanticUnknown ? { semanticUnknown } : {},
26193
26256
  centrality: candidate.pageRank,
26194
26257
  ...quality !== void 0 ? { quality } : {},
26195
26258
  ...ctx.specificity !== void 0 ? { specificity: ctx.specificity } : {},
@@ -26209,7 +26272,11 @@ function rankByRelevance(store, seedId, candidateIds, opts = {}) {
26209
26272
  };
26210
26273
  const nodes = candidateIds.map((id) => store.getNode(id)).filter((n) => n != null);
26211
26274
  const maxPr = Math.max(1e-9, ...nodes.map((n) => n.pageRank));
26212
- const weights = { centralityScale: maxPr, ...opts.weights };
26275
+ const weights = {
26276
+ centralityScale: maxPr,
26277
+ ...ctx.seedEmbedding ? { semanticFloor: semanticFloorFor(ctx.seedEmbeddingVersion) } : {},
26278
+ ...opts.weights
26279
+ };
26213
26280
  const now = store.currentIngestSeq();
26214
26281
  return nodes.map((node2) => {
26215
26282
  const r = scoreCandidate(node2, { ...ctx, decay: nodeFreshness(node2, now) }, weights);
@@ -26244,7 +26311,11 @@ function localBurst(store, seedId, opts = {}) {
26244
26311
  const gated = seed.embedding.length > 0;
26245
26312
  const nodes = [...hopOf.entries()].map(([id, hops]) => ({ node: store.getNode(id), hops })).filter((x) => x.node != null);
26246
26313
  const maxPr = Math.max(1e-9, ...nodes.map((x) => x.node.pageRank));
26247
- const weights = { centralityScale: maxPr, ...opts.weights };
26314
+ const weights = {
26315
+ centralityScale: maxPr,
26316
+ ...gated ? { semanticFloor: semanticFloorFor(ctxSeed.seedEmbeddingVersion) } : {},
26317
+ ...opts.weights
26318
+ };
26248
26319
  const now = store.currentIngestSeq();
26249
26320
  return nodes.map(({ node: node2, hops }) => {
26250
26321
  const r = scoreCandidate(node2, { ...ctxSeed, hops, decay: nodeFreshness(node2, now) }, weights);
@@ -37436,8 +37507,8 @@ function buildWebUi(deps) {
37436
37507
  const arrText = Array.isArray(respObj["content"]) ? respObj["content"].map(
37437
37508
  (c2) => c2 && typeof c2 === "object" && typeof c2["text"] === "string" ? c2["text"] : ""
37438
37509
  ).join("\n") : "";
37439
- const errText = typeof rawResp === "string" ? rawResp : typeof respObj["error"] === "string" ? respObj["error"] : typeof respObj["message"] === "string" ? respObj["message"] : typeof respObj["stderr"] === "string" ? respObj["stderr"] : typeof respObj["content"] === "string" ? respObj["content"] : arrText;
37440
- const errorTokens = errText.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).slice(0, 20);
37510
+ const errText2 = typeof rawResp === "string" ? rawResp : typeof respObj["error"] === "string" ? respObj["error"] : typeof respObj["message"] === "string" ? respObj["message"] : typeof respObj["stderr"] === "string" ? respObj["stderr"] : typeof respObj["content"] === "string" ? respObj["content"] : arrText;
37511
+ const errorTokens = errText2.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).slice(0, 20);
37441
37512
  if (errorTokens.length > 0) {
37442
37513
  deps.onToolError({
37443
37514
  tool,
@@ -37446,7 +37517,7 @@ function buildWebUi(deps) {
37446
37517
  exitCode: numericExit ?? 1,
37447
37518
  ts: Date.now()
37448
37519
  });
37449
- recallCtx = recallForError(deps.store, errText);
37520
+ recallCtx = recallForError(deps.store, errText2);
37450
37521
  }
37451
37522
  }
37452
37523
  }
@@ -42478,7 +42549,7 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
42478
42549
  }
42479
42550
 
42480
42551
  // src/engine.ts
42481
- var DAEMON_VERSION = true ? "2.0.0-dev.39" : "2.0.0-alpha.0";
42552
+ var DAEMON_VERSION = true ? "2.0.0-dev.41" : "2.0.0-alpha.0";
42482
42553
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
42483
42554
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
42484
42555
  var GIT_OP_MUTE_MS = 4e3;
@@ -43506,6 +43577,16 @@ function createWorkspaceEngine(opts) {
43506
43577
  telemetry.count("problems_resolved", gen.problemsResolved);
43507
43578
  telemetry.count("reviews_triggered", gen.reviewsTriggered);
43508
43579
  telemetry.count("signals_credited", report.signalsCredited);
43580
+ if (gen.problemsWritten > 0) {
43581
+ try {
43582
+ const inline = await embedSemanticNodes(store, { mode: "hash" });
43583
+ if (inline.embedded > 0) {
43584
+ console.log(`[errata] inline-embedded ${inline.embedded} fresh semantic node(s) (hash)`);
43585
+ }
43586
+ } catch (err2) {
43587
+ console.warn("[errata] inline semantic embed failed:", err2 instanceof Error ? err2.message : err2);
43588
+ }
43589
+ }
43509
43590
  const graphChanged = gen.problemsWritten + gen.problemsResolved + gen.problemsReinforced + gen.reviewsTriggered + report.signalsCredited > 0;
43510
43591
  if (!opts.skipContextWriter && (graphChanged || contextDirty)) {
43511
43592
  refreshContextNow();
@@ -45202,6 +45283,114 @@ init_src4();
45202
45283
  init_config();
45203
45284
  init_src6();
45204
45285
  init_cloud_auth();
45286
+
45287
+ // src/agent-switch.ts
45288
+ function parseUseArgs(args2) {
45289
+ const positional = [];
45290
+ let clear = false;
45291
+ for (const arg of args2) {
45292
+ if (arg === "--none" || arg === "--clear") {
45293
+ clear = true;
45294
+ continue;
45295
+ }
45296
+ if (arg.startsWith("--")) continue;
45297
+ positional.push(arg);
45298
+ }
45299
+ if (clear) return { kind: "clear" };
45300
+ const handle2 = positional[0];
45301
+ if (!handle2) return { kind: "show" };
45302
+ return { kind: "set", handle: handle2 };
45303
+ }
45304
+ function normalizeHandle(handle2) {
45305
+ return handle2.trim().replace(/^@/, "").toLowerCase();
45306
+ }
45307
+ function findActable(agents, handle2) {
45308
+ const want = normalizeHandle(handle2);
45309
+ return agents.find((a) => normalizeHandle(a.handle) === want) ?? null;
45310
+ }
45311
+ async function runUse(action, ctx) {
45312
+ switch (action.kind) {
45313
+ case "clear":
45314
+ return runClear(ctx);
45315
+ case "show":
45316
+ return runShow(ctx);
45317
+ case "set":
45318
+ return runSet(action.handle, ctx);
45319
+ }
45320
+ }
45321
+ function runClear(ctx) {
45322
+ const active = ctx.getActiveAgent();
45323
+ ctx.clearActiveAgent();
45324
+ if (active) {
45325
+ ctx.log(`no longer acting as ${active.handle} \u2014 the daemon acts as your default identity`);
45326
+ } else {
45327
+ ctx.log(`already on the default identity \u2014 nothing to clear`);
45328
+ }
45329
+ return 0;
45330
+ }
45331
+ async function runShow(ctx) {
45332
+ const active = ctx.getActiveAgent();
45333
+ ctx.log(active ? `active agent: ${active.handle} (id ${active.id})` : `active agent: (default identity)`);
45334
+ if (!ctx.loggedIn) {
45335
+ ctx.log(` log in to list the agents you can act as: errata login`);
45336
+ return 0;
45337
+ }
45338
+ let resp;
45339
+ try {
45340
+ resp = await ctx.actableAgents();
45341
+ } catch (err2) {
45342
+ ctx.log(` couldn't list actable agents: ${errText(err2)}`);
45343
+ return 1;
45344
+ }
45345
+ printActable(resp, active, ctx);
45346
+ return 0;
45347
+ }
45348
+ async function runSet(handle2, ctx) {
45349
+ if (!ctx.loggedIn) {
45350
+ ctx.log(`not logged in \u2014 run \`errata login\` first, then \`errata use ${handle2}\``);
45351
+ return 1;
45352
+ }
45353
+ let resp;
45354
+ try {
45355
+ resp = await ctx.actableAgents();
45356
+ } catch (err2) {
45357
+ ctx.log(`couldn't verify \`${handle2}\`: ${errText(err2)}`);
45358
+ return 1;
45359
+ }
45360
+ const match2 = findActable(resp.agents, handle2);
45361
+ if (!match2) {
45362
+ ctx.log(`unknown agent: ${handle2}`);
45363
+ if (resp.agents.length === 0) {
45364
+ ctx.log(` you can't act as any agents yet.`);
45365
+ } else {
45366
+ ctx.log(` agents you can act as: ${resp.agents.map((a) => a.handle).join(", ")}`);
45367
+ }
45368
+ return 2;
45369
+ }
45370
+ ctx.setActiveAgent({ handle: match2.handle, id: match2.id });
45371
+ ctx.log(`now acting as ${match2.handle} (id ${match2.id}) \u2014 saved; persists across daemon restarts`);
45372
+ return 0;
45373
+ }
45374
+ function printActable(resp, active, ctx) {
45375
+ if (resp.agents.length === 0) {
45376
+ ctx.log(` (no agents you can act as)`);
45377
+ return;
45378
+ }
45379
+ ctx.log(` agents you can act as:`);
45380
+ for (const a of resp.agents) {
45381
+ const marks = [];
45382
+ if (active && a.id === active.id) marks.push("active");
45383
+ if (a.id === resp.defaultAgentId) marks.push("default");
45384
+ const suffix = marks.length ? ` (${marks.join(", ")})` : "";
45385
+ const scope = a.scope ? ` \xB7 ${a.scope}` : "";
45386
+ ctx.log(` ${a.handle}${scope}${suffix}`);
45387
+ }
45388
+ }
45389
+ function errText(err2) {
45390
+ return err2 instanceof Error ? err2.message : String(err2);
45391
+ }
45392
+
45393
+ // src/cli.ts
45205
45394
  init_cloud_endpoint_policy();
45206
45395
 
45207
45396
  // src/oauth-login.ts
@@ -45446,6 +45635,8 @@ async function main() {
45446
45635
  return cmdLogin();
45447
45636
  case "logout":
45448
45637
  return cmdLogout();
45638
+ case "use":
45639
+ return cmdUse(rest);
45449
45640
  case "review":
45450
45641
  return cmdReview();
45451
45642
  case "tick":
@@ -45570,6 +45761,9 @@ Commands:
45570
45761
  surface (navigation, problems, claims, burst, health)
45571
45762
  login Sign in with the cloud (OAuth by default; --device for legacy device-code)
45572
45763
  logout Clear local cloud credentials
45764
+ use [<handle>] Set the sticky session active agent the daemon acts as.
45765
+ No arg lists the agents you can act as + the current one;
45766
+ --none (or --clear) reverts to your default identity.
45573
45767
  sync now Flush outbox to the cloud once
45574
45768
  privacy Show what is collected/scrubbed + your consent state
45575
45769
  consent <channel> <on|off>
@@ -45772,6 +45966,10 @@ async function cmdStatus() {
45772
45966
  console.log(` cloud:`);
45773
45967
  console.log(` url: ${cfg.cloudUrl}`);
45774
45968
  console.log(` logged in: ${hasCloudCredential(cfg) ? `yes (${cfg.email})` : "no"}`);
45969
+ const active = getActiveAgent(cfg);
45970
+ console.log(
45971
+ ` active agent: ${active ? `${active.handle} (acting-as; \`errata use --none\` to clear)` : "default identity"}`
45972
+ );
45775
45973
  const inspection = await inspectCloudEndpoint(cfg.cloudUrl);
45776
45974
  const decision = evaluateCloudEndpoint(cfg.cloudUrl, inspection);
45777
45975
  const service = inspection.service ? ` \xB7 service ${inspection.service}` : "";
@@ -46048,6 +46246,24 @@ async function cmdLogout() {
46048
46246
  saveConfig(cfg);
46049
46247
  console.log(`logged out`);
46050
46248
  }
46249
+ async function cmdUse(args2) {
46250
+ const cfg = loadConfig();
46251
+ const action = parseUseArgs(args2);
46252
+ const ctx = {
46253
+ loggedIn: hasCloudCredential(cfg),
46254
+ actableAgents: () => authedCloudClient(loadConfig()).actableAgents(),
46255
+ getActiveAgent: () => getActiveAgent(),
46256
+ setActiveAgent: (agent) => {
46257
+ setActiveAgent(agent);
46258
+ },
46259
+ clearActiveAgent: () => {
46260
+ clearActiveAgent();
46261
+ },
46262
+ log: (line) => console.log(line)
46263
+ };
46264
+ const code = await runUse(action, ctx);
46265
+ if (code !== 0) process.exitCode = code;
46266
+ }
46051
46267
  async function cmdReview() {
46052
46268
  const paths = workspacePaths(ROOT);
46053
46269
  if (!existsSync20(paths.reviewQueue)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.0-dev.39",
3
+ "version": "2.0.0-dev.41",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -25393,11 +25393,11 @@ var MEMBER_LABELS = ["Problem", "RootCause", "Solution"];
25393
25393
  function layerOf(kind) {
25394
25394
  return kind === "Technique" ? "technique" : kind === "AntiPattern" ? "antipattern" : "pattern";
25395
25395
  }
25396
- function motifTitle(members, layer) {
25396
+ function motifTitle(members, layer, instanceCount) {
25397
25397
  const top = [...members].sort(
25398
25398
  (a, b) => b.extractionConfidence - a.extractionConfidence
25399
25399
  )[0];
25400
- return `${layer}: ${top?.description ?? "recurring problem cluster"}`;
25400
+ return `${layer} \xD7${instanceCount} (exemplar): ${top?.description ?? "recurring problem cluster"}`;
25401
25401
  }
25402
25402
  function classifyMotif(store2, instances, promoted) {
25403
25403
  if (!promoted || instances.length === 0) return "Pattern";
@@ -25443,7 +25443,9 @@ function promoteMotifs(store2, t) {
25443
25443
  const motif = {
25444
25444
  id: motifId,
25445
25445
  label: kind,
25446
- description: existing?.description ?? motifTitle(members, layerOf(kind)),
25446
+ // Refresh the deterministic title every run (exemplar/count/kind drift),
25447
+ // but never clobber a distilled one — a distillation pass owns it then.
25448
+ description: existing?.attrs["titleDistilled"] === true ? existing.description : motifTitle(members, layerOf(kind), instanceCount),
25447
25449
  extractionConfidence: d.motifConfidence,
25448
25450
  extractionSource: "bko-inferred",
25449
25451
  embedding: existing?.embedding ?? [],
@@ -25681,11 +25683,15 @@ function buildSnapshot(opts) {
25681
25683
  languages: opts.profile.languages.map((name2) => ({ name: name2, node: matchNode(langNodes, name2) })),
25682
25684
  packages: opts.profile.stack.map((name2) => ({ name: name2, node: matchNode(pkgNodes, pkgBase(name2)) }))
25683
25685
  };
25686
+ const causalNudge = recent.length >= 2 && recent.every(
25687
+ (r) => opts.store.outEdges(r.node.id, ["CAUSED_BY"]).length === 0 && opts.store.inEdges(r.node.id, ["CAUSED_BY"]).length === 0
25688
+ );
25684
25689
  return {
25685
25690
  profile: opts.profile,
25686
25691
  profileContext,
25687
25692
  recentProblems: recent,
25688
25693
  recentResolved,
25694
+ ...causalNudge ? { causalNudge } : {},
25689
25695
  motifs,
25690
25696
  reviewCount: opts.reviewCount,
25691
25697
  reviewUiUrl: opts.reviewUiUrl,