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.
- package/dist-mcp/mcp-server.js +102 -8
- package/docs/HANDBOOK.md +6 -0
- package/package.json +1 -1
package/dist-mcp/mcp-server.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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 =
|
|
31029
|
-
const bTime =
|
|
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:
|
|
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 =
|
|
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