gossipcat 0.4.26 → 0.4.27

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.
@@ -10033,7 +10033,7 @@ function coerceStatus(raw) {
10033
10033
  }
10034
10034
  return "pending";
10035
10035
  }
10036
- var import_fs9, import_crypto8, import_path10, SAFE_NAME, __SKILL_ENGINE_TEST_HOOK, VALID_STATUSES, KNOWN_CATEGORIES, CATEGORY_KEYWORDS, REQUIRED_SECTIONS, BUNDLED_TEMPLATE, SkillEngine;
10036
+ var import_fs9, import_crypto8, import_path10, SAFE_NAME, __SKILL_ENGINE_TEST_HOOK, VALID_STATUSES, TECH_STACK_MIN_DEPS, MANIFEST_HINTS, KNOWN_CATEGORIES, CATEGORY_KEYWORDS, REQUIRED_SECTIONS, BUNDLED_TEMPLATE, SkillEngine;
10037
10037
  var init_skill_engine = __esm({
10038
10038
  "packages/orchestrator/src/skill-engine.ts"() {
10039
10039
  "use strict";
@@ -10056,6 +10056,17 @@ var init_skill_engine = __esm({
10056
10056
  "silent_skill",
10057
10057
  "insufficient_evidence"
10058
10058
  ];
10059
+ TECH_STACK_MIN_DEPS = 3;
10060
+ MANIFEST_HINTS = [
10061
+ ["Cargo.toml", "Rust"],
10062
+ ["pyproject.toml", "Python"],
10063
+ ["requirements.txt", "Python"],
10064
+ ["go.mod", "Go"],
10065
+ ["foundry.toml", "Solidity/Foundry"],
10066
+ ["Move.toml", "Move/Aptos/Sui"],
10067
+ ["Gemfile", "Ruby"],
10068
+ ["composer.json", "PHP"]
10069
+ ];
10059
10070
  KNOWN_CATEGORIES = /* @__PURE__ */ new Set([
10060
10071
  "trust_boundaries",
10061
10072
  "injection_vectors",
@@ -10356,7 +10367,7 @@ NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST.
10356
10367
  projectContext = (0, import_fs9.readFileSync)(bootstrapPath, "utf-8").slice(0, 1500);
10357
10368
  }
10358
10369
  if (this.techStackCache === void 0) {
10359
- this.techStackCache = await this.detectTechStack();
10370
+ this.techStackCache = this.readTechStackOverride() ?? await this.detectTechStack();
10360
10371
  }
10361
10372
  const techStack = this.techStackCache;
10362
10373
  if (techStack) {
@@ -10531,6 +10542,7 @@ ${fm}
10531
10542
  */
10532
10543
  async detectTechStack() {
10533
10544
  const inputs = [];
10545
+ let totalDepCount = 0;
10534
10546
  const pkgPaths = [(0, import_path10.join)(this.projectRoot, "package.json")];
10535
10547
  try {
10536
10548
  const packagesDir = (0, import_path10.join)(this.projectRoot, "packages");
@@ -10546,6 +10558,7 @@ ${fm}
10546
10558
  try {
10547
10559
  const pkg = JSON.parse((0, import_fs9.readFileSync)(p, "utf-8"));
10548
10560
  const deps = Object.keys({ ...pkg.dependencies, ...pkg.devDependencies });
10561
+ totalDepCount += deps.length;
10549
10562
  if (deps.length > 0) {
10550
10563
  inputs.push(`${p.replace(this.projectRoot + "/", "")}: ${deps.join(", ")}`);
10551
10564
  }
@@ -10557,7 +10570,59 @@ ${fm}
10557
10570
  inputs.push(`Source dirs: ${srcDirs.join(", ") || "root"}`);
10558
10571
  } catch {
10559
10572
  }
10560
- if (inputs.length === 0) return null;
10573
+ let manifestCount = 0;
10574
+ for (const [filename, language] of MANIFEST_HINTS) {
10575
+ if ((0, import_fs9.existsSync)((0, import_path10.join)(this.projectRoot, filename))) {
10576
+ inputs.push(`Manifest: ${filename} (${language})`);
10577
+ manifestCount++;
10578
+ }
10579
+ }
10580
+ const READMES = ["README.md", "README", "readme.md"];
10581
+ let readmeFound = false;
10582
+ for (const name of READMES) {
10583
+ const p = (0, import_path10.join)(this.projectRoot, name);
10584
+ if ((0, import_fs9.existsSync)(p)) {
10585
+ try {
10586
+ const content = (0, import_fs9.readFileSync)(p, "utf-8");
10587
+ const lines = content.split("\n").slice(0, 30).join("\n");
10588
+ const clamped = lines.slice(0, 2e3);
10589
+ if (clamped.trim()) {
10590
+ inputs.push(`README (${name}, first ${Math.min(30, content.split("\n").length)} lines):
10591
+ ${clamped}`);
10592
+ readmeFound = true;
10593
+ break;
10594
+ }
10595
+ } catch {
10596
+ }
10597
+ }
10598
+ }
10599
+ const EXCLUDED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".gossip", "dist", "build", "out", "coverage"]);
10600
+ const EXCLUDED_EXTS = /* @__PURE__ */ new Set([".json", ".md", ".yaml", ".yml", ".toml", ".lock", ".txt", ".xml", ".ini", ".cfg", ".env", ".gitignore", ".gitattributes", ".editorconfig", ".npmrc", ".nvmrc"]);
10601
+ let extensionSignal = false;
10602
+ try {
10603
+ const entries = (0, import_fs9.readdirSync)(this.projectRoot, { withFileTypes: true });
10604
+ const extCounts = /* @__PURE__ */ new Map();
10605
+ for (const entry of entries) {
10606
+ if (EXCLUDED_DIRS.has(entry.name)) continue;
10607
+ if (entry.isFile()) {
10608
+ const dotIdx = entry.name.lastIndexOf(".");
10609
+ if (dotIdx > 0) {
10610
+ const ext = entry.name.slice(dotIdx).toLowerCase();
10611
+ if (!EXCLUDED_EXTS.has(ext)) {
10612
+ extCounts.set(ext, (extCounts.get(ext) ?? 0) + 1);
10613
+ }
10614
+ }
10615
+ }
10616
+ }
10617
+ if (extCounts.size > 0) {
10618
+ const sorted = Array.from(extCounts.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([ext, count]) => `${ext}(${count})`).join(", ");
10619
+ inputs.push(`Root file extensions: ${sorted}`);
10620
+ extensionSignal = true;
10621
+ }
10622
+ } catch {
10623
+ }
10624
+ const hasNonNodeSignal = manifestCount > 0 || readmeFound || extensionSignal;
10625
+ if (totalDepCount < TECH_STACK_MIN_DEPS && !hasNonNodeSignal) return null;
10561
10626
  try {
10562
10627
  const messages = [{
10563
10628
  role: "user",
@@ -10579,6 +10644,35 @@ ${inputs.join("\n")}
10579
10644
  return inputs.join("\n").slice(0, 500);
10580
10645
  }
10581
10646
  }
10647
+ /**
10648
+ * Reads `.gossip/tech-stack.md` as a user-authored override for tech-stack
10649
+ * detection. Returns the file content (clamped to 2000 chars) if present and
10650
+ * non-empty, or null to fall through to auto-detect.
10651
+ *
10652
+ * Operator escape hatch for non-Node host projects (Solidity, Rust, Move, etc.)
10653
+ * where the LLM hallucinates a Node.js stack from thin npm dep signal.
10654
+ * Cache is session-stable via techStackCache — restart the MCP server to pick
10655
+ * up edits to this file.
10656
+ */
10657
+ readTechStackOverride() {
10658
+ const overridePath = (0, import_path10.join)(this.projectRoot, ".gossip", "tech-stack.md");
10659
+ if (!(0, import_fs9.existsSync)(overridePath)) return null;
10660
+ try {
10661
+ const { size } = (0, import_fs9.statSync)(overridePath);
10662
+ if (size > 2048) {
10663
+ process.stderr.write(`[skill-engine] tech-stack.md override is ${size} bytes, clamping to 2000 chars
10664
+ `);
10665
+ }
10666
+ const content = (0, import_fs9.readFileSync)(overridePath, "utf-8").slice(0, 2e3).trim();
10667
+ if (!content) return null;
10668
+ return content;
10669
+ } catch (err) {
10670
+ const msg = err instanceof Error ? err.message : String(err);
10671
+ process.stderr.write(`[skill-engine] tech-stack.md override read failed: ${msg}
10672
+ `);
10673
+ return null;
10674
+ }
10675
+ }
10582
10676
  loadCategoryFindings(category) {
10583
10677
  const filePath = (0, import_path10.join)(this.projectRoot, ".gossip", "agent-performance.jsonl");
10584
10678
  if (!(0, import_fs9.existsSync)(filePath)) return [];
@@ -31022,11 +31116,11 @@ var init_routes = __esm({
31022
31116
  }
31023
31117
  if (!existsSync68(reportsDir)) return { reports: [], totalReports: 0, page, pageSize, retractedConsensusIds, roundRetractions };
31024
31118
  try {
31025
- const { statSync: statSync32 } = require("fs");
31119
+ const { statSync: statSync33 } = require("fs");
31026
31120
  const allFiles = readdirSync22(reportsDir).filter((f) => f.endsWith(".json")).sort((a, b) => {
31027
31121
  try {
31028
- const aTime = statSync32((0, import_path68.join)(reportsDir, a)).mtimeMs;
31029
- const bTime = statSync32((0, import_path68.join)(reportsDir, b)).mtimeMs;
31122
+ const aTime = statSync33((0, import_path68.join)(reportsDir, a)).mtimeMs;
31123
+ const bTime = statSync33((0, import_path68.join)(reportsDir, b)).mtimeMs;
31030
31124
  return bTime - aTime;
31031
31125
  } catch {
31032
31126
  return 0;
@@ -52109,7 +52203,7 @@ Note: write-mode classification unavailable on this native-only install \u2014 a
52109
52203
  } catch {
52110
52204
  }
52111
52205
  try {
52112
- const { readdirSync: readdirSync22, statSync: statSync32 } = await import("fs");
52206
+ const { readdirSync: readdirSync22, statSync: statSync33 } = await import("fs");
52113
52207
  const { readJsonlWithRotated: readJsonlRotated1506 } = await Promise.resolve().then(() => (init_src4(), src_exports3));
52114
52208
  const reportsDir = (0, import_path84.join)(process.cwd(), ".gossip", "consensus-reports");
52115
52209
  const perfPath = (0, import_path84.join)(process.cwd(), ".gossip", "agent-performance.jsonl");
@@ -52120,7 +52214,7 @@ Note: write-mode classification unavailable on this native-only install \u2014 a
52120
52214
  for (const fname of readdirSync22(reportsDir)) {
52121
52215
  if (!fname.endsWith(".json")) continue;
52122
52216
  const fpath = (0, import_path84.join)(reportsDir, fname);
52123
- const st = statSync32(fpath);
52217
+ const st = statSync33(fpath);
52124
52218
  if (now - st.mtimeMs > WINDOW_MS2) continue;
52125
52219
  recentReports.push({ id: fname.replace(/\.json$/, ""), mtimeMs: st.mtimeMs });
52126
52220
  }
package/docs/HANDBOOK.md CHANGED
@@ -228,6 +228,12 @@ If `format_compliance` signals a sudden drop for an agent that was fine last rou
228
228
 
229
229
  When blocked, the error message shows age + remaining cooldown + override instruction. Pass `force: true` to bypass; every override is appended to `.gossip/forced-skill-develops.jsonl` for auditability. Chronic override patterns on an agent+category pair are a signal that the skill prompt is ineffective or `MIN_EVIDENCE` is miscalibrated for that category — investigate before reflexively forcing.
230
230
 
231
+ ### Tech-stack override
232
+
233
+ Drop a `.gossip/tech-stack.md` at the project root to bypass auto-detection on `gossip_skills(action: "develop")`. Content (max 2000 chars after trim) is injected verbatim into the skill-develop prompt's `<tech_stack>` block, replacing the auto-detected description. Useful for non-Node host projects (Solidity, Rust, Move, audit workspaces) where the LLM hallucinates a Node.js stack from thin npm dep signal (issue #410, PR #411 floor + this override). Cache is session-stable — restart the MCP server to pick up edits. Empty file or read errors fall through to auto-detect (with stderr warning on errors). Files over 2 KB are clamped with a stderr warning.
234
+
235
+ **Tech-stack auto-detection.** When no override is present and the npm dep count is below `TECH_STACK_MIN_DEPS=3` OR the project is non-Node, `detectTechStack` scans the project root for known manifests (Cargo.toml, pyproject.toml, requirements.txt, go.mod, foundry.toml, Move.toml, Gemfile, composer.json), the README first 30 lines / 2 KB, and a shallow file-extension census (root only, excluding `node_modules`/`.git`/`.gossip`/`dist`/`build`/`out`/`coverage`, capped at 10 extension types; config/docs extensions such as `.json`, `.md`, `.yaml`, `.toml`, `.lock` are excluded from the census since they are already captured by other signals). Any non-Node signal — manifest match, README content, or extension census — bypasses the `MIN_DEPS=3` floor so polyglot projects don't need the override file. Workspace-level manifests (e.g., `packages/contracts/foundry.toml`) are NOT scanned in this MVP; place the manifest at root or use `.gossip/tech-stack.md` for those cases.
236
+
231
237
  ### Verifying UNVERIFIED findings
232
238
 
233
239
  When a consensus report has `UNVERIFIED` findings (cross-reviewer couldn't check), **you must verify them yourself before presenting results**. UNVERIFIED means "the peer didn't have the tools or context to check" — you do. Read the cited files, grep for the identifiers, confirm or reject. Do not show raw consensus output with unexamined UNVERIFIED findings.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gossipcat",
3
- "version": "0.4.26",
3
+ "version": "0.4.27",
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": {