gossipcat 0.6.8 → 0.6.9

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.
@@ -24760,7 +24760,7 @@ This section will be used for cross-review with peer agents.
24760
24760
 
24761
24761
  ${OUTPUT_DELIVERY_PROTOCOL}` });
24762
24762
  } else {
24763
- 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);
24763
+ 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 || parts.agentCorrections && parts.agentCorrections.length > 0);
24764
24764
  if (hasAnyMeaningfulPart) {
24765
24765
  suffix.push({ priority: 0, text: `
24766
24766
 
@@ -24786,13 +24786,18 @@ ${parts.lens}
24786
24786
  ${parts.specReviewContext}
24787
24787
  --- END SPEC REVIEW ---` });
24788
24788
  }
24789
- if (parts.memory || parts.consensusFindings && parts.consensusFindings.length > 0) {
24789
+ if (parts.memory || parts.consensusFindings && parts.consensusFindings.length > 0 || parts.agentCorrections && parts.agentCorrections.length > 0) {
24790
24790
  const memParts = [];
24791
24791
  if (parts.memory) memParts.push(parts.memory);
24792
24792
  if (parts.consensusFindings && parts.consensusFindings.length > 0) {
24793
24793
  const findingsBlock = "### Recent Consensus Findings\n" + parts.consensusFindings.map((f, i) => `${i + 1}. ${f}`).join("\n");
24794
24794
  memParts.push(findingsBlock);
24795
24795
  }
24796
+ if (parts.agentCorrections && parts.agentCorrections.length > 0) {
24797
+ memParts.push(
24798
+ "### Your Prior Corrections\n" + parts.agentCorrections.map((f, i) => `${i + 1}. ${f}`).join("\n")
24799
+ );
24800
+ }
24796
24801
  suffix.push({ priority: 3, text: `
24797
24802
 
24798
24803
  --- MEMORY ---
@@ -24911,7 +24916,7 @@ var init_prompt_assembler = __esm({
24911
24916
  });
24912
24917
 
24913
24918
  // packages/orchestrator/src/agent-memory.ts
24914
- var import_fs20, import_path21, FINDINGS_MAX_RESULTS, FINDINGS_MAX_CHARS, FINDINGS_STALE_DAYS, FINDINGS_MIN_SCORE, AgentMemoryReader;
24919
+ var import_fs20, import_path21, FINDINGS_MAX_RESULTS, FINDINGS_MAX_CHARS, FINDINGS_STALE_DAYS, FINDINGS_MIN_SCORE, CORRECTIONS_MAX_RESULTS, AgentMemoryReader;
24915
24920
  var init_agent_memory = __esm({
24916
24921
  "packages/orchestrator/src/agent-memory.ts"() {
24917
24922
  "use strict";
@@ -24921,6 +24926,7 @@ var init_agent_memory = __esm({
24921
24926
  FINDINGS_MAX_CHARS = 150;
24922
24927
  FINDINGS_STALE_DAYS = 30;
24923
24928
  FINDINGS_MIN_SCORE = 1;
24929
+ CORRECTIONS_MAX_RESULTS = 2;
24924
24930
  AgentMemoryReader = class {
24925
24931
  constructor(projectRoot) {
24926
24932
  this.projectRoot = projectRoot;
@@ -25021,6 +25027,46 @@ ${content}
25021
25027
  }
25022
25028
  return scored.sort((a, b) => b.score - a.score).slice(0, FINDINGS_MAX_RESULTS).map((s) => s.text);
25023
25029
  }
25030
+ /**
25031
+ * Pre-fetch this agent's own prior lesson cards relevant to the task (issue #642 B-delta).
25032
+ * Selects by `lesson-*.md` filename (not frontmatter `type`, which parsers don't read),
25033
+ * ranks by body keyword overlap, drops cards older than FINDINGS_STALE_DAYS, and strips
25034
+ * prompt-injection delimiters before returning. Returns top CORRECTIONS_MAX_RESULTS snippets.
25035
+ */
25036
+ prefetchAgentCorrectionsText(agentId, taskText) {
25037
+ if (!agentId || /[/\\.\0]/.test(agentId)) return [];
25038
+ const knowledgeDir = (0, import_path21.join)(this.projectRoot, ".gossip", "agents", agentId, "memory", "knowledge");
25039
+ if (!(0, import_fs20.existsSync)(knowledgeDir)) return [];
25040
+ let files;
25041
+ try {
25042
+ files = (0, import_fs20.readdirSync)(knowledgeDir).filter((f) => f.startsWith("lesson-") && f.endsWith(".md"));
25043
+ } catch {
25044
+ return [];
25045
+ }
25046
+ if (files.length === 0) return [];
25047
+ const keywords = this.extractKeywords(taskText);
25048
+ if (keywords.length === 0) return [];
25049
+ const cutoffMs = Date.now() - FINDINGS_STALE_DAYS * 864e5;
25050
+ const scored = [];
25051
+ for (const file2 of files) {
25052
+ const filePath = (0, import_path21.join)(knowledgeDir, file2);
25053
+ let content;
25054
+ try {
25055
+ const mtime = (0, import_fs20.statSync)(filePath).mtimeMs;
25056
+ if (mtime < cutoffMs) continue;
25057
+ content = (0, import_fs20.readFileSync)(filePath, "utf-8");
25058
+ } catch {
25059
+ continue;
25060
+ }
25061
+ const body = content.replace(/^---\n[\s\S]*?\n---\n*/, "").replace(/<\/?(?:agent-memory|system|instructions)>/gi, "").replace(/\s+/g, " ").trim();
25062
+ if (!body) continue;
25063
+ const score = this.scoreKeywords(keywords, body);
25064
+ if (score >= FINDINGS_MIN_SCORE) {
25065
+ scored.push({ text: body.slice(0, FINDINGS_MAX_CHARS).trim(), score });
25066
+ }
25067
+ }
25068
+ return scored.sort((a, b) => b.score - a.score).slice(0, CORRECTIONS_MAX_RESULTS).map((s) => s.text);
25069
+ }
25024
25070
  /** Simple word-boundary keyword overlap scoring (no LLM). */
25025
25071
  extractKeywords(taskText) {
25026
25072
  const words = taskText.toLowerCase().split(/[\s,/.;:!?()\[\]{}*_~`"'<>|]+/);
@@ -25505,7 +25551,21 @@ function validateAgentId(agentId) {
25505
25551
  function sanitizeTaskId(taskId) {
25506
25552
  return taskId.replace(/[/\\:*?"<>|\0]/g, "_");
25507
25553
  }
25508
- var import_fs23, import_path24, import_crypto11, MemoryWriter;
25554
+ function writeLessonCardsForSignals(projectRoot, signals) {
25555
+ if (!Array.isArray(signals) || signals.length === 0) return;
25556
+ const writer = new MemoryWriter(projectRoot);
25557
+ for (const s of signals) {
25558
+ if (!s || !s.finding_id || !TERMINAL_LESSON_SIGNALS.has(s.signal)) continue;
25559
+ writer.writeLessonCard(s.agent_id, {
25560
+ signal: s.signal,
25561
+ findingId: s.finding_id,
25562
+ finding: s.finding ?? "",
25563
+ lesson: s.lesson,
25564
+ taskTokens: s.finding
25565
+ });
25566
+ }
25567
+ }
25568
+ var import_fs23, import_path24, import_crypto11, TERMINAL_LESSON_SIGNALS, LESSON_CARDS_MAX_PER_AGENT, LESSON_RESERVED_AGENT_IDS, MemoryWriter;
25509
25569
  var init_memory_writer = __esm({
25510
25570
  "packages/orchestrator/src/memory-writer.ts"() {
25511
25571
  "use strict";
@@ -25516,6 +25576,13 @@ var init_memory_writer = __esm({
25516
25576
  init_project_structure();
25517
25577
  init_log();
25518
25578
  init_completion_signals();
25579
+ TERMINAL_LESSON_SIGNALS = /* @__PURE__ */ new Set([
25580
+ "hallucination_caught",
25581
+ "impl_test_fail",
25582
+ "impl_peer_rejected"
25583
+ ]);
25584
+ LESSON_CARDS_MAX_PER_AGENT = 200;
25585
+ LESSON_RESERVED_AGENT_IDS = /* @__PURE__ */ new Set(["_project", "_system", "_utility"]);
25519
25586
  MemoryWriter = class {
25520
25587
  constructor(projectRoot) {
25521
25588
  this.projectRoot = projectRoot;
@@ -26088,7 +26155,7 @@ ${truncateAtWord(summaryBody, NEXT_SESSION_MAX_CHARS)}
26088
26155
  /** Shared warmth-aware pruning — evicts lowest-warmth files, respects pinned */
26089
26156
  pruneKnowledgeDir(knowledgeDir, maxFiles) {
26090
26157
  try {
26091
- const existing = (0, import_fs23.readdirSync)(knowledgeDir).filter((f) => f.endsWith(".md") && !f.endsWith("-session.md")).sort();
26158
+ const existing = (0, import_fs23.readdirSync)(knowledgeDir).filter((f) => f.endsWith(".md") && !f.endsWith("-session.md") && !f.startsWith("lesson-")).sort();
26092
26159
  if (existing.length >= maxFiles) {
26093
26160
  const scored = existing.map((f) => {
26094
26161
  const content = (0, import_fs23.readFileSync)((0, import_path24.join)(knowledgeDir, f), "utf-8");
@@ -26392,6 +26459,76 @@ ${result}`;
26392
26459
  this.pruneKnowledgeDir(knowledgeDir, 25);
26393
26460
  (0, import_fs23.writeFileSync)((0, import_path24.join)(knowledgeDir, filename), content);
26394
26461
  }
26462
+ /**
26463
+ * Write a recallable "lesson card" for a terminal correction signal (issue #642 A).
26464
+ * Best-effort: any failure is swallowed — this MUST NOT fail signal recording.
26465
+ * Idempotent: keyed by finding_id via sanitizeTaskId, no timestamp in the filename.
26466
+ */
26467
+ writeLessonCard(agentId, card) {
26468
+ try {
26469
+ if (LESSON_RESERVED_AGENT_IDS.has(agentId)) return;
26470
+ validateAgentId(agentId);
26471
+ const slug = sanitizeTaskId(card.findingId).slice(0, 96);
26472
+ if (!slug) return;
26473
+ const memDir = this.ensureDirs(agentId);
26474
+ const knowledgeDir = (0, import_path24.join)(memDir, "knowledge");
26475
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
26476
+ const generated = (/* @__PURE__ */ new Date()).toISOString();
26477
+ const shortId = slug.slice(0, 24);
26478
+ const finding = truncateAtWord(sanitizeYamlValue(card.finding ?? ""), 400);
26479
+ const lessonBody = card.lesson ? truncateAtWord(sanitizeYamlValue(card.lesson), 400) : "(no root-cause supplied)";
26480
+ const desc = sanitizeYamlValue(card.lesson || card.finding || "lesson").slice(0, 80);
26481
+ const taskTokens = (card.taskTokens ?? card.finding ?? "").replace(/[\n\r]/g, " ").slice(0, 300);
26482
+ const content = [
26483
+ "---",
26484
+ `name: lesson-${sanitizeYamlValue(card.signal)}-${shortId}`,
26485
+ `description: ${desc}`,
26486
+ "type: lesson",
26487
+ `agent: ${sanitizeYamlValue(agentId)}`,
26488
+ `signal: ${sanitizeYamlValue(card.signal)}`,
26489
+ `finding_id: ${sanitizeYamlValue(card.findingId)}`,
26490
+ "importance: 0.7",
26491
+ `lastAccessed: ${today}`,
26492
+ "accessCount: 0",
26493
+ `generated: ${generated}`,
26494
+ "---",
26495
+ "",
26496
+ `**Why it failed:** ${lessonBody}`,
26497
+ "",
26498
+ `**What happened:** ${finding}`,
26499
+ "",
26500
+ `**Task context:** ${taskTokens}`
26501
+ ].join("\n");
26502
+ const cardPath = (0, import_path24.join)(knowledgeDir, `lesson-${slug}.md`);
26503
+ if (!(0, import_fs23.existsSync)(cardPath)) this.pruneLessonCards(knowledgeDir, LESSON_CARDS_MAX_PER_AGENT);
26504
+ (0, import_fs23.writeFileSync)(cardPath, content);
26505
+ } catch {
26506
+ }
26507
+ }
26508
+ /** Count-based prune of lesson-*.md only (leaves consensus/session knowledge untouched). */
26509
+ pruneLessonCards(knowledgeDir, maxFiles) {
26510
+ try {
26511
+ const cards = (0, import_fs23.readdirSync)(knowledgeDir).filter((f) => f.startsWith("lesson-") && f.endsWith(".md"));
26512
+ if (cards.length < maxFiles) return;
26513
+ const withMtime = cards.map((f) => {
26514
+ let mtime = 0;
26515
+ try {
26516
+ mtime = (0, import_fs23.statSync)((0, import_path24.join)(knowledgeDir, f)).mtimeMs;
26517
+ } catch {
26518
+ }
26519
+ return { f, mtime };
26520
+ });
26521
+ withMtime.sort((a, b) => a.mtime - b.mtime);
26522
+ const toEvict = withMtime.slice(0, cards.length - (maxFiles - 1));
26523
+ for (const item of toEvict) {
26524
+ try {
26525
+ (0, import_fs23.unlinkSync)((0, import_path24.join)(knowledgeDir, item.f));
26526
+ } catch {
26527
+ }
26528
+ }
26529
+ } catch {
26530
+ }
26531
+ }
26395
26532
  /**
26396
26533
  * Update task memory importance based on consensus signals.
26397
26534
  * High-quality findings get boosted, hallucinations get reduced.
@@ -32298,6 +32435,10 @@ var init_dispatch_pipeline = __esm({
32298
32435
  if (consensusFindings.length > 0) {
32299
32436
  gossipLog(`\u{1F4CB} pre-fetched ${consensusFindings.length} consensus findings for ${agentId}`);
32300
32437
  }
32438
+ const agentCorrections = this.memReader.prefetchAgentCorrectionsText(agentId, task);
32439
+ if (agentCorrections.length > 0) {
32440
+ gossipLog(`\u{1F9E0} pre-fetched ${agentCorrections.length} prior corrections for ${agentId}`);
32441
+ }
32301
32442
  const effectiveSkills = resolveEffectiveSkills(agentId, agentSkills, this.skillIndex ?? void 0);
32302
32443
  const skillWarnings = this.catalog ? this.catalog.checkCoverage(effectiveSkills, task) : [];
32303
32444
  const sessionGossip = this.sessionContext.getSessionGossip();
@@ -32348,7 +32489,8 @@ var init_dispatch_pipeline = __esm({
32348
32489
  consensusSummary: options?.consensus,
32349
32490
  specReviewContext,
32350
32491
  projectStructure: this.getProjectStructure(),
32351
- consensusFindings: consensusFindings.length > 0 ? consensusFindings : void 0
32492
+ consensusFindings: consensusFindings.length > 0 ? consensusFindings : void 0,
32493
+ agentCorrections: agentCorrections.length > 0 ? agentCorrections : void 0
32352
32494
  });
32353
32495
  this.taskGraph.recordCreated(taskId, agentId, task, agentSkills);
32354
32496
  const entry = {
@@ -40598,6 +40740,7 @@ __export(src_exports3, {
40598
40740
  JACCARD_THRESHOLD: () => JACCARD_THRESHOLD,
40599
40741
  KEY_REQUIRING_PROVIDERS: () => KEY_REQUIRING_PROVIDERS,
40600
40742
  LEDGER_INDEX_FILENAME: () => LEDGER_INDEX_FILENAME,
40743
+ LESSON_CARDS_MAX_PER_AGENT: () => LESSON_CARDS_MAX_PER_AGENT,
40601
40744
  LensGenerator: () => LensGenerator,
40602
40745
  MAX_ASSEMBLED_PROMPT_CHARS: () => MAX_ASSEMBLED_PROMPT_CHARS,
40603
40746
  MAX_CLAIMS_PER_BLOCK: () => MAX_CLAIMS_PER_BLOCK,
@@ -40641,6 +40784,7 @@ __export(src_exports3, {
40641
40784
  SkillEngine: () => SkillEngine,
40642
40785
  SkillGapTracker: () => SkillGapTracker,
40643
40786
  SkillIndex: () => SkillIndex,
40787
+ TERMINAL_LESSON_SIGNALS: () => TERMINAL_LESSON_SIGNALS,
40644
40788
  TIMEOUT_DAYS: () => TIMEOUT_DAYS,
40645
40789
  TIMEOUT_MS: () => TIMEOUT_MS,
40646
40790
  TOOL_SCHEMAS: () => TOOL_SCHEMAS2,
@@ -40808,6 +40952,7 @@ __export(src_exports3, {
40808
40952
  verifyClaims: () => verifyClaims,
40809
40953
  withResolverLock: () => withResolverLock,
40810
40954
  writeLedgerIndex: () => writeLedgerIndex,
40955
+ writeLessonCardsForSignals: () => writeLessonCardsForSignals,
40811
40956
  writeOrchestratorRoleMarker: () => writeOrchestratorRoleMarker
40812
40957
  });
40813
40958
  var init_src4 = __esm({
@@ -46531,11 +46676,11 @@ var init_routes = __esm({
46531
46676
  }
46532
46677
  if (!existsSync74(reportsDir)) return { reports: [], totalReports: 0, page, pageSize, retractedConsensusIds, roundRetractions };
46533
46678
  try {
46534
- const { statSync: statSync36 } = require("fs");
46679
+ const { statSync: statSync37 } = require("fs");
46535
46680
  const allFiles = readdirSync22(reportsDir).filter((f) => f.endsWith(".json")).sort((a, b) => {
46536
46681
  try {
46537
- const aTime = statSync36((0, import_path80.join)(reportsDir, a)).mtimeMs;
46538
- const bTime = statSync36((0, import_path80.join)(reportsDir, b)).mtimeMs;
46682
+ const aTime = statSync37((0, import_path80.join)(reportsDir, a)).mtimeMs;
46683
+ const bTime = statSync37((0, import_path80.join)(reportsDir, b)).mtimeMs;
46539
46684
  return bTime - aTime;
46540
46685
  } catch {
46541
46686
  return 0;
@@ -49403,7 +49548,7 @@ function appendRelayWarning(projectRoot, entry) {
49403
49548
  }
49404
49549
  function checkWorktreeEngaged(startedAt, projectRoot = process.cwd()) {
49405
49550
  try {
49406
- const { readdirSync: readdirSync22, statSync: statSync36 } = require("fs");
49551
+ const { readdirSync: readdirSync22, statSync: statSync37 } = require("fs");
49407
49552
  const { join: join93 } = require("path");
49408
49553
  const wtDir = join93(projectRoot, ".claude", "worktrees");
49409
49554
  let entries;
@@ -49416,7 +49561,7 @@ function checkWorktreeEngaged(startedAt, projectRoot = process.cwd()) {
49416
49561
  for (const entry of entries) {
49417
49562
  if (!entry.startsWith("agent-")) continue;
49418
49563
  try {
49419
- const st = statSync36(join93(wtDir, entry));
49564
+ const st = statSync37(join93(wtDir, entry));
49420
49565
  if (st.isDirectory() && st.mtimeMs >= startedAt - 2e3) return true;
49421
49566
  } catch {
49422
49567
  }
@@ -81759,7 +81904,7 @@ Ledger: ${ledgerPath2}`
81759
81904
  } catch {
81760
81905
  }
81761
81906
  try {
81762
- const { readdirSync: readdirSync22, statSync: statSync36, readFileSync: readFileSync70 } = await import("fs");
81907
+ const { readdirSync: readdirSync22, statSync: statSync37, readFileSync: readFileSync70 } = await import("fs");
81763
81908
  const { readJsonlWithRotated: readJsonlRotated1506 } = await Promise.resolve().then(() => (init_src4(), src_exports3));
81764
81909
  const reportsDir = (0, import_path97.join)(process.cwd(), ".gossip", "consensus-reports");
81765
81910
  const perfPath = (0, import_path97.join)(process.cwd(), ".gossip", "agent-performance.jsonl");
@@ -81770,7 +81915,7 @@ Ledger: ${ledgerPath2}`
81770
81915
  for (const fname of readdirSync22(reportsDir)) {
81771
81916
  if (!fname.endsWith(".json")) continue;
81772
81917
  const fpath = (0, import_path97.join)(reportsDir, fname);
81773
- const st = statSync36(fpath);
81918
+ const st = statSync37(fpath);
81774
81919
  if (now - st.mtimeMs > WINDOW_MS3) continue;
81775
81920
  try {
81776
81921
  const report = JSON.parse(readFileSync70(fpath, "utf8"));
@@ -82595,6 +82740,7 @@ ${output}` }]
82595
82740
  agent_id: external_exports.string().describe("Agent being evaluated"),
82596
82741
  counterpart_id: external_exports.string().optional().describe("The other agent involved (e.g., who won the disagreement)"),
82597
82742
  finding: external_exports.string().describe("Brief description of the finding"),
82743
+ lesson: external_exports.string().optional().describe("Root-cause narrative for a terminal correction signal \u2014 1-2 sentences on why it failed and the check that would have caught it. Written verbatim into the discovering agent's lesson card (issue #642 A)."),
82598
82744
  finding_id: external_exports.string().optional().describe("Consensus finding ID \u2014 links this signal to a specific finding in a consensus report. Enables dashboard to resolve UNVERIFIED findings."),
82599
82745
  severity: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("Finding severity for impact scoring. If omitted, defaults to medium."),
82600
82746
  category: external_exports.string().optional().describe("Finding category for ATI competency profiles (e.g., concurrency, trust_boundaries, injection_vectors, resource_exhaustion, type_safety, error_handling, data_integrity, input_validation)"),
@@ -83083,6 +83229,11 @@ Uncategorized finding_ids: ${uncategorized.join(", ")}`;
83083
83229
  if (dedupedConsensus.length > 0) emitRecordConsensusSignals(process.cwd(), dedupedConsensus);
83084
83230
  if (dedupedImpl.length > 0) emitRecordImplSignals(process.cwd(), dedupedImpl);
83085
83231
  }
83232
+ try {
83233
+ const { writeLessonCardsForSignals: writeLessonCardsForSignals2 } = await Promise.resolve().then(() => (init_src4(), src_exports3));
83234
+ writeLessonCardsForSignals2(process.cwd(), signals ?? []);
83235
+ } catch {
83236
+ }
83086
83237
  const hallucinationSignals = deduped.filter(
83087
83238
  (s) => s.type === "consensus" && s.signal === "hallucination_caught"
83088
83239
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gossipcat",
3
- "version": "0.6.8",
3
+ "version": "0.6.9",
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": {