gossipcat 0.4.16 → 0.4.17

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.
@@ -22,7 +22,7 @@
22
22
  <link rel="icon" type="image/png" href="/dashboard/favicon.png" />
23
23
  <link rel="preconnect" href="https://fonts.googleapis.com" />
24
24
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet" />
25
- <script type="module" crossorigin src="/dashboard/assets/index-BFOANmol.js"></script>
25
+ <script type="module" crossorigin src="/dashboard/assets/index-1D2tBkyY.js"></script>
26
26
  <link rel="stylesheet" crossorigin href="/dashboard/assets/index-B2_6u4lC.css">
27
27
  </head>
28
28
  <body>
@@ -5618,7 +5618,7 @@ var init_gossip_agent = __esm({
5618
5618
  }
5619
5619
  // ─── Public API ─────────────────────────────────────────────────────────────
5620
5620
  connect() {
5621
- return new Promise((resolve25, reject) => {
5621
+ return new Promise((resolve26, reject) => {
5622
5622
  const ws = new import_ws2.default(this.config.relayUrl);
5623
5623
  const timeout = setTimeout(() => {
5624
5624
  ws.removeAllListeners();
@@ -5651,7 +5651,7 @@ var init_gossip_agent = __esm({
5651
5651
  ws.on("error", (err) => this.emit("error", err));
5652
5652
  this.startKeepAlive();
5653
5653
  this.emit("connect", msg.sessionId);
5654
- resolve25();
5654
+ resolve26();
5655
5655
  } else if (msg.type === "error") {
5656
5656
  clearTimeout(timeout);
5657
5657
  ws.removeAllListeners();
@@ -5677,7 +5677,7 @@ var init_gossip_agent = __esm({
5677
5677
  this.reconnectTimer = null;
5678
5678
  }
5679
5679
  if (!this.ws) return;
5680
- return new Promise((resolve25) => {
5680
+ return new Promise((resolve26) => {
5681
5681
  this.intentionalDisconnect = true;
5682
5682
  this._connected = false;
5683
5683
  const ws = this.ws;
@@ -5688,7 +5688,7 @@ var init_gossip_agent = __esm({
5688
5688
  settled = true;
5689
5689
  this.intentionalDisconnect = false;
5690
5690
  this.emit("disconnect", code);
5691
- resolve25();
5691
+ resolve26();
5692
5692
  };
5693
5693
  const timer = setTimeout(() => done(1e3), 2e3);
5694
5694
  ws.once("close", (code) => {
@@ -5727,8 +5727,8 @@ var init_gossip_agent = __esm({
5727
5727
  throw new Error("Not connected to relay");
5728
5728
  }
5729
5729
  const encoded = Buffer.from(this.codec.encode(envelope));
5730
- return new Promise((resolve25, reject) => {
5731
- this.ws.send(encoded, (err) => err ? reject(err) : resolve25());
5730
+ return new Promise((resolve26, reject) => {
5731
+ this.ws.send(encoded, (err) => err ? reject(err) : resolve26());
5732
5732
  });
5733
5733
  }
5734
5734
  // ─── Internal ────────────────────────────────────────────────────────────────
@@ -6178,7 +6178,7 @@ ${context}` : ""}
6178
6178
  this.memoryQueryCalled = true;
6179
6179
  }
6180
6180
  const requestId = (0, import_crypto6.randomUUID)();
6181
- const resultPromise = new Promise((resolve25, reject) => {
6181
+ const resultPromise = new Promise((resolve26, reject) => {
6182
6182
  const timer = setTimeout(() => {
6183
6183
  if (this.pendingToolCalls.has(requestId)) {
6184
6184
  this.pendingToolCalls.delete(requestId);
@@ -6189,7 +6189,7 @@ ${context}` : ""}
6189
6189
  this.pendingToolCalls.set(requestId, {
6190
6190
  resolve: (r) => {
6191
6191
  clearTimeout(timer);
6192
- resolve25(r);
6192
+ resolve26(r);
6193
6193
  },
6194
6194
  reject: (e) => {
6195
6195
  clearTimeout(timer);
@@ -11526,7 +11526,7 @@ ${truncateAtLine(fullDiff, 3e3)}`;
11526
11526
  }
11527
11527
  async requestPeerReview(callerId, diff, testResult) {
11528
11528
  const requestId = (0, import_crypto8.randomUUID)();
11529
- const reviewPromise = new Promise((resolve25, reject) => {
11529
+ const reviewPromise = new Promise((resolve26, reject) => {
11530
11530
  const timer = setTimeout(() => {
11531
11531
  this.pendingReviews.delete(requestId);
11532
11532
  reject(new Error("Review timed out"));
@@ -11535,7 +11535,7 @@ ${truncateAtLine(fullDiff, 3e3)}`;
11535
11535
  this.pendingReviews.set(requestId, {
11536
11536
  resolve: (r) => {
11537
11537
  clearTimeout(timer);
11538
- resolve25(r);
11538
+ resolve26(r);
11539
11539
  },
11540
11540
  reject: (e) => {
11541
11541
  clearTimeout(timer);
@@ -12714,7 +12714,7 @@ var init_skill_loader = __esm({
12714
12714
  });
12715
12715
 
12716
12716
  // packages/orchestrator/src/finding-tag-schema.ts
12717
- var FINDING_TAG_SCHEMA, CONSENSUS_OUTPUT_FORMAT;
12717
+ var FINDING_TAG_SCHEMA, OUTPUT_DELIVERY_PROTOCOL, CONSENSUS_OUTPUT_FORMAT;
12718
12718
  var init_finding_tag_schema = __esm({
12719
12719
  "packages/orchestrator/src/finding-tag-schema.ts"() {
12720
12720
  "use strict";
@@ -12724,6 +12724,7 @@ var init_finding_tag_schema = __esm({
12724
12724
  - severity (findings only): critical | high | medium | low
12725
12725
  - Do NOT invent new types (e.g., "approval", "concern", "risk", "recommendation", "confirmed", "issue", "bug", "warning") \u2014 they will not appear in any dashboard, score, or signal.
12726
12726
  - Cite source files inline: <cite tag="file">path:line</cite>`;
12727
+ OUTPUT_DELIVERY_PROTOCOL = `OUTPUT DELIVERY \u2014 emit findings as TEXT in your response. The orchestrator captures your response and calls gossip_relay on your behalf. Do NOT call gossip_relay, gossip_relay_cross_review, or any gossip_* tool yourself \u2014 these are orchestrator-only. Stop when your findings are written; do not try to "submit" or "finalize".`;
12727
12728
  CONSENSUS_OUTPUT_FORMAT = `\u26A0 UNKNOWN TYPES ARE SILENTLY DROPPED \u2014 only type="finding", type="suggestion", type="insight" are accepted. Any other type value (e.g. approval, concern, risk, recommendation, confirmed) will NOT appear in the dashboard, scores, or signals.
12728
12729
 
12729
12730
  ${FINDING_TAG_SCHEMA}
@@ -12878,6 +12879,9 @@ ${CONSENSUS_OUTPUT_FORMAT}
12878
12879
 
12879
12880
  This section will be used for cross-review with peer agents.
12880
12881
  --- END CONSENSUS OUTPUT FORMAT ---` });
12882
+ suffix.push({ priority: 0, text: `
12883
+
12884
+ ${OUTPUT_DELIVERY_PROTOCOL}` });
12881
12885
  } else {
12882
12886
  const hasAnyMeaningfulPart = !!(parts.identity || parts.instructions || parts.memory || parts.memoryDir || parts.lens || parts.skills || parts.context || parts.sessionContext || parts.chainContext || parts.specReviewContext || parts.projectStructure || parts.task || parts.consensusFindings && parts.consensusFindings.length > 0);
12883
12887
  if (hasAnyMeaningfulPart) {
@@ -12886,6 +12890,9 @@ This section will be used for cross-review with peer agents.
12886
12890
  --- FINDING TAG SCHEMA ---
12887
12891
  ${FINDING_TAG_SCHEMA}
12888
12892
  --- END FINDING TAG SCHEMA ---` });
12893
+ suffix.push({ priority: 0, text: `
12894
+
12895
+ ${OUTPUT_DELIVERY_PROTOCOL}` });
12889
12896
  }
12890
12897
  }
12891
12898
  if (parts.lens) {
@@ -13304,7 +13311,9 @@ var init_memory_compactor = __esm({
13304
13311
  (0, import_fs22.writeFileSync)(archivePath, archiveLines.slice(-MAX_ARCHIVE_LINES).join("\n") + "\n");
13305
13312
  }
13306
13313
  }
13307
- } catch {
13314
+ } catch (err) {
13315
+ process.stderr.write(`[memory-compactor] archive truncation failed for ${agentId}: ${err.message}
13316
+ `);
13308
13317
  }
13309
13318
  (0, import_fs22.writeFileSync)(tasksPath, toKeep.map((e) => e.line).join("\n") + "\n");
13310
13319
  return { archived: toArchive.length, dropped: dropped || void 0, message: `Compacted ${toArchive.length} memories for ${agentId}${dropped ? ` (${dropped} malformed lines dropped)` : ""}` };
@@ -13489,6 +13498,14 @@ ${cleanSummary}` : cleanSummary;
13489
13498
  body = facts.body;
13490
13499
  }
13491
13500
  const accuracyLine = data.agentAccuracy !== void 0 ? `agentAccuracy: ${data.agentAccuracy.toFixed(2)}` : null;
13501
+ const citations = this.validateCitations(body, data.resolutionRoots);
13502
+ const citationLines = [`citationsVerified: ${citations.verified}/${citations.total}`];
13503
+ if (citations.unverified.length > 0) {
13504
+ citationLines.push("citationsFabricated:");
13505
+ for (const u of citations.unverified) {
13506
+ citationLines.push(` - "${u}"`);
13507
+ }
13508
+ }
13492
13509
  const content = [
13493
13510
  "---",
13494
13511
  `name: ${truncateAtWord(data.task, 80).replace(/\n/g, " ")}`,
@@ -13497,6 +13514,7 @@ ${cleanSummary}` : cleanSummary;
13497
13514
  ...accuracyLine ? [accuracyLine] : [],
13498
13515
  `lastAccessed: ${today}`,
13499
13516
  `accessCount: 0`,
13517
+ ...citationLines,
13500
13518
  "---",
13501
13519
  "",
13502
13520
  ...data.agentAccuracy !== void 0 && data.agentAccuracy < 0.4 ? ["> \u26A0 This agent has low accuracy (" + (data.agentAccuracy * 100).toFixed(0) + "%). Treat factual claims as unverified.\n"] : [],
@@ -13504,6 +13522,60 @@ ${cleanSummary}` : cleanSummary;
13504
13522
  ].join("\n");
13505
13523
  (0, import_fs24.writeFileSync)((0, import_path25.join)(knowledgeDir, filename), content);
13506
13524
  }
13525
+ /**
13526
+ * Annotation-only citation check: scans `body` for file-like tokens (optionally
13527
+ * with a `:NN` line-number suffix), resolves each against `projectRoot` plus any
13528
+ * supplied `resolutionRoots`, and reports how many are real. Never drops or
13529
+ * mutates content — the caller records the counts in frontmatter for later
13530
+ * analysis.
13531
+ */
13532
+ validateCitations(body, resolutionRoots) {
13533
+ const fileRegex = /(?:^|[\s`"'(\[<])([a-zA-Z0-9_/.-]+\.[a-z]{1,7})(?::(\d+))?(?=[\s`"'):,\]>]|$)/gm;
13534
+ const seen = /* @__PURE__ */ new Set();
13535
+ const citations = [];
13536
+ let m;
13537
+ while ((m = fileRegex.exec(body)) !== null) {
13538
+ const path2 = m[1];
13539
+ const before = body.slice(Math.max(0, m.index - 8), m.index + 1);
13540
+ if (/https?:\/\//.test(before) || /https?:\/\//.test(path2)) continue;
13541
+ const line = m[2] ? parseInt(m[2], 10) : void 0;
13542
+ const key = line !== void 0 ? `${path2}:${line}` : path2;
13543
+ if (seen.has(key)) continue;
13544
+ seen.add(key);
13545
+ citations.push({ path: path2, line, key });
13546
+ }
13547
+ const roots = [this.projectRoot, ...resolutionRoots ?? []];
13548
+ const unverified = [];
13549
+ let verified = 0;
13550
+ for (const c of citations) {
13551
+ let hit = null;
13552
+ for (const root of roots) {
13553
+ const abs = (0, import_path25.resolve)(root, c.path);
13554
+ if ((0, import_fs24.existsSync)(abs)) {
13555
+ hit = abs;
13556
+ break;
13557
+ }
13558
+ }
13559
+ if (!hit) {
13560
+ unverified.push(c.key);
13561
+ continue;
13562
+ }
13563
+ if (c.line !== void 0) {
13564
+ try {
13565
+ const lineCount = (0, import_fs24.readFileSync)(hit, "utf8").split("\n").length;
13566
+ if (c.line > lineCount || c.line < 1) {
13567
+ unverified.push(c.key);
13568
+ continue;
13569
+ }
13570
+ } catch {
13571
+ unverified.push(c.key);
13572
+ continue;
13573
+ }
13574
+ }
13575
+ verified++;
13576
+ }
13577
+ return { total: citations.length, verified, unverified };
13578
+ }
13507
13579
  /**
13508
13580
  * Generate a cognitive summary — what the agent learned, not just what it saw.
13509
13581
  * Uses the utility LLM (cheapest available model).
@@ -25193,7 +25265,7 @@ function resolveDashboardRoot(projectRoot) {
25193
25265
  return null;
25194
25266
  }
25195
25267
  function readBody(req) {
25196
- return new Promise((resolve25, reject) => {
25268
+ return new Promise((resolve26, reject) => {
25197
25269
  const chunks = [];
25198
25270
  let size = 0;
25199
25271
  let tooLarge = false;
@@ -25208,7 +25280,7 @@ function readBody(req) {
25208
25280
  chunks.push(chunk);
25209
25281
  });
25210
25282
  req.on("end", () => {
25211
- if (!tooLarge) resolve25(Buffer.concat(chunks).toString("utf-8"));
25283
+ if (!tooLarge) resolve26(Buffer.concat(chunks).toString("utf-8"));
25212
25284
  });
25213
25285
  req.on("error", (err) => {
25214
25286
  if (!tooLarge) reject(err);
@@ -25837,7 +25909,7 @@ var init_server = __esm({
25837
25909
  return this.heartbeatRunning;
25838
25910
  }
25839
25911
  async start() {
25840
- return new Promise((resolve25) => {
25912
+ return new Promise((resolve26) => {
25841
25913
  this.httpServer = (0, import_http.createServer)(this.handleHttp.bind(this));
25842
25914
  if (this.config.dashboard) {
25843
25915
  this.dashboardAuth = new DashboardAuth();
@@ -25883,7 +25955,7 @@ var init_server = __esm({
25883
25955
  this.httpServer.listen(this.config.port, this.config.host ?? "127.0.0.1", () => {
25884
25956
  const addr = this.httpServer.address();
25885
25957
  this._port = addr.port;
25886
- resolve25();
25958
+ resolve26();
25887
25959
  });
25888
25960
  });
25889
25961
  }
@@ -25903,9 +25975,9 @@ var init_server = __esm({
25903
25975
  for (const client of this.wss.clients) {
25904
25976
  client.close(1001, "Server shutting down");
25905
25977
  }
25906
- return new Promise((resolve25) => {
25978
+ return new Promise((resolve26) => {
25907
25979
  this.wss.close(() => {
25908
- this.httpServer.close(() => resolve25());
25980
+ this.httpServer.close(() => resolve26());
25909
25981
  });
25910
25982
  });
25911
25983
  }
@@ -42902,8 +42974,8 @@ async function handleDispatchConsensus(taskDefs, _utility_task_id, dispatchResol
42902
42974
  });
42903
42975
  const lenses = await Promise.race([
42904
42976
  lensPromise,
42905
- new Promise((resolve25) => {
42906
- timerId = setTimeout(() => resolve25(null), LENS_TIMEOUT_MS);
42977
+ new Promise((resolve26) => {
42978
+ timerId = setTimeout(() => resolve26(null), LENS_TIMEOUT_MS);
42907
42979
  })
42908
42980
  ]);
42909
42981
  if (timerId) clearTimeout(timerId);
@@ -43115,7 +43187,7 @@ Relay may be down. Check gossip_status() for connection state.` }] };
43115
43187
  process.stderr.write(`[gossipcat] \u23F3 Consensus: ${doneCount}/${pendingNativeIds.length} agents complete (${agentStatus})
43116
43188
  `);
43117
43189
  }
43118
- await new Promise((resolve25) => setTimeout(resolve25, POLL_INTERVAL));
43190
+ await new Promise((resolve26) => setTimeout(resolve26, POLL_INTERVAL));
43119
43191
  }
43120
43192
  const arrived = pendingNativeIds.filter((id) => ctx.nativeResultMap.has(id)).length;
43121
43193
  const timedOutCount = pendingNativeIds.filter((id) => {
@@ -43786,7 +43858,7 @@ function writeStickyPort(filename, port) {
43786
43858
  }
43787
43859
  }
43788
43860
  function probePort(port, host = "127.0.0.1") {
43789
- return new Promise((resolve25) => {
43861
+ return new Promise((resolve26) => {
43790
43862
  const srv = (0, import_net.createServer)();
43791
43863
  let settled = false;
43792
43864
  const done = (ok) => {
@@ -43796,7 +43868,7 @@ function probePort(port, host = "127.0.0.1") {
43796
43868
  srv.close();
43797
43869
  } catch {
43798
43870
  }
43799
- resolve25(ok);
43871
+ resolve26(ok);
43800
43872
  };
43801
43873
  srv.once("error", () => done(false));
43802
43874
  try {
@@ -47691,10 +47763,10 @@ async function startHttpMcpTransport() {
47691
47763
  }
47692
47764
  let body;
47693
47765
  if (req.method === "POST") {
47694
- const raw = await new Promise((resolve25, reject) => {
47766
+ const raw = await new Promise((resolve26, reject) => {
47695
47767
  const chunks = [];
47696
47768
  req.on("data", (c) => chunks.push(c));
47697
- req.on("end", () => resolve25(Buffer.concat(chunks).toString("utf-8")));
47769
+ req.on("end", () => resolve26(Buffer.concat(chunks).toString("utf-8")));
47698
47770
  req.on("error", reject);
47699
47771
  });
47700
47772
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gossipcat",
3
- "version": "0.4.16",
3
+ "version": "0.4.17",
4
4
  "description": "Multi-agent orchestration for Claude Code — parallel review, consensus, adaptive dispatch",
5
5
  "mcpName": "io.github.ataberk-xyz/gossipcat",
6
6
  "repository": {