@antonbabenko/deliberation-mcp 3.5.4 → 3.6.1

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.
Files changed (2) hide show
  1. package/dist/index.js +155 -35
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -231,18 +231,71 @@ var require_provider = __commonJS({
231
231
  var VERDICT_RE = /\bverdict\b[^A-Za-z0-9]*\b(APPROVE|REJECT|REQUEST[_\s]CHANGES)\b/i;
232
232
  var BULLET_RE = /^([-*+•]|`?\[)/;
233
233
  var BRACKET_CAT_RE = /\[\s*([A-Za-z_]+)\s*\]/g;
234
+ var SENTINEL_RE = /^[#>*_`\s]*verdict\s*[:=]\s*[*_`\s]*(APPROVE|REJECT|REQUEST[_\s]CHANGES)\b/i;
235
+ var VERDICT_WORD_RE = /^[#>*_`\s]*verdict[*_`:\s]*$/i;
236
+ var TOKEN_LINE_RE = /^(APPROVE|REJECT|REQUEST[_\s]CHANGES)$/i;
237
+ var MD_EMPHASIS = /[*_`~]/g;
238
+ var FENCE_RE = /^\s*(```|~~~)/;
239
+ var ARTIFACT_RE = /^[*_`~\s:.\-]*$/;
240
+ var STRIP_LEAD = /^[*_`~\s:.\-]+/;
241
+ var STRIP_TRAIL = /[*_`~\s]+$/;
242
+ var HEADING_RE = /^#{1,6}\s/;
243
+ function normVerdict(tok) {
244
+ return tok.replace(/\s+/g, "_").toUpperCase();
245
+ }
234
246
  function parseReview(text) {
235
247
  const raw = safeString(text);
236
- const lines = raw.split(/\r?\n/);
237
- let verdict = null;
238
- const criticalIssues = [];
239
- for (const line of lines) {
240
- if (verdict === null) {
241
- const vm = line.match(VERDICT_RE);
242
- if (vm) verdict = /** @type {ParsedReview["verdict"]} */
243
- vm[1].replace(/\s+/g, "_").toUpperCase();
248
+ const lines = [];
249
+ let inFence = false;
250
+ for (const ln of raw.split(/\r?\n/)) {
251
+ if (FENCE_RE.test(ln)) {
252
+ inFence = !inFence;
253
+ continue;
244
254
  }
245
- const trimmed = line.trim();
255
+ if (!inFence) lines.push(ln);
256
+ }
257
+ return { verdict: resolveVerdict(lines), criticalIssues: resolveIssues(lines) };
258
+ }
259
+ function resolveVerdict(lines) {
260
+ for (const ln of lines) {
261
+ const m = ln.match(SENTINEL_RE);
262
+ if (m) return (
263
+ /** @type {any} */
264
+ normVerdict(m[1])
265
+ );
266
+ }
267
+ for (const ln of lines) {
268
+ const m = ln.match(VERDICT_RE);
269
+ if (m) return (
270
+ /** @type {any} */
271
+ normVerdict(m[1])
272
+ );
273
+ }
274
+ for (let i = 0; i < lines.length; i++) {
275
+ if (!VERDICT_WORD_RE.test(lines[i].trim())) continue;
276
+ for (let j = i + 1; j < lines.length && j <= i + 3; j++) {
277
+ const t = lines[j].replace(MD_EMPHASIS, "").trim();
278
+ if (!t) continue;
279
+ if (TOKEN_LINE_RE.test(t)) return (
280
+ /** @type {any} */
281
+ normVerdict(t)
282
+ );
283
+ break;
284
+ }
285
+ }
286
+ for (const ln of lines) {
287
+ const t = ln.replace(MD_EMPHASIS, "").trim();
288
+ if (TOKEN_LINE_RE.test(t)) return (
289
+ /** @type {any} */
290
+ normVerdict(t)
291
+ );
292
+ }
293
+ return null;
294
+ }
295
+ function resolveIssues(lines) {
296
+ const out = [];
297
+ for (let i = 0; i < lines.length; i++) {
298
+ const trimmed = lines[i].trim();
246
299
  if (!BULLET_RE.test(trimmed)) continue;
247
300
  let chosen = null;
248
301
  for (const mm of trimmed.matchAll(BRACKET_CAT_RE)) {
@@ -258,10 +311,20 @@ var require_provider = __commonJS({
258
311
  /** @type {ReviewCriticalIssue["category"]} */
259
312
  REVIEW_CATEGORIES.includes(cat) ? cat : REVIEW_FALLBACK_CATEGORY
260
313
  );
261
- const description = trimmed.slice((chosen.index || 0) + chosen[0].length).replace(/^[`\s:.\-]+/, "").trim();
262
- if (description) criticalIssues.push({ category, description });
314
+ let description = trimmed.slice(chosen.index + chosen[0].length).replace(STRIP_LEAD, "").replace(STRIP_TRAIL, "").trim();
315
+ if (!description || ARTIFACT_RE.test(description)) {
316
+ for (let j = i + 1; j < lines.length; j++) {
317
+ const nt = lines[j].trim();
318
+ if (!nt) break;
319
+ if (BULLET_RE.test(nt) || HEADING_RE.test(nt)) break;
320
+ if (SENTINEL_RE.test(nt) || VERDICT_WORD_RE.test(nt) || VERDICT_RE.test(nt)) break;
321
+ description = nt.replace(STRIP_LEAD, "").replace(STRIP_TRAIL, "").trim();
322
+ break;
323
+ }
324
+ }
325
+ if (description && !ARTIFACT_RE.test(description)) out.push({ category, description });
263
326
  }
264
- return { verdict, criticalIssues };
327
+ return out;
265
328
  }
266
329
  module2.exports = {
267
330
  toErrorResult,
@@ -324,12 +387,12 @@ ${state.currentPlan}`
324
387
  const peerPrompt = [
325
388
  body,
326
389
  "Review the plan for correctness, security, scope, ambiguity, performance, and ops gaps.",
327
- "End with: **Verdict**: APPROVE | REQUEST_CHANGES | REJECT, then categorized critical issues."
390
+ "End with a line by itself in exactly this form (no markdown, token on the SAME line): VERDICT: APPROVE (or VERDICT: REQUEST_CHANGES, or VERDICT: REJECT). Then list any critical issues, one per line as: - [category] description where category is one of security, correctness, scope, ambiguity, performance, ops."
328
391
  ].join("\n\n");
329
392
  const blindPrompt = [
330
393
  body,
331
394
  "Give your own independent verdict BEFORE seeing peer opinions.",
332
- "End with: **Verdict**: APPROVE | REQUEST_CHANGES | REJECT, then categorized critical issues."
395
+ "End with a line by itself in exactly this form (no markdown, token on the SAME line): VERDICT: APPROVE (or VERDICT: REQUEST_CHANGES, or VERDICT: REJECT). Then list any critical issues, one per line as: - [category] description where category is one of security, correctness, scope, ambiguity, performance, ops."
333
396
  ].join("\n\n");
334
397
  return { peerPrompt, blindPrompt };
335
398
  }
@@ -520,7 +583,14 @@ var require_orchestrate = __commonJS({
520
583
  } catch {
521
584
  }
522
585
  }
523
- async function callProvider(provider, req, logger, tool, cache) {
586
+ function withOrientation(provider, req, orientationFiles) {
587
+ if (Array.isArray(orientationFiles) && orientationFiles.length && !(Array.isArray(req.files) && req.files.length) && provider.capabilities && provider.capabilities.walksFilesystem === false) {
588
+ return { ...req, files: orientationFiles };
589
+ }
590
+ return req;
591
+ }
592
+ async function callProvider(provider, req, logger, tool, cache, orientationFiles) {
593
+ req = withOrientation(provider, req, orientationFiles);
524
594
  const useCache = cache && !(Array.isArray(req.files) && req.files.length);
525
595
  if (useCache) {
526
596
  const hit = cache.get(provider.name, req);
@@ -553,7 +623,7 @@ var require_orchestrate = __commonJS({
553
623
  const logger = opts.logger || NULL_LOGGER;
554
624
  const tool = opts.tool || "ask-all";
555
625
  const settled = await Promise.allSettled(
556
- providers.map((p) => callProvider(p, req, logger, tool, opts.cache))
626
+ providers.map((p) => callProvider(p, req, logger, tool, opts.cache, opts.orientationFiles))
557
627
  );
558
628
  return settled.map(
559
629
  (s, i) => s.status === "fulfilled" ? s.value : {
@@ -568,7 +638,7 @@ var require_orchestrate = __commonJS({
568
638
  );
569
639
  }
570
640
  async function askOne2(provider, req, opts = {}) {
571
- return callProvider(provider, req, opts.logger || NULL_LOGGER, opts.tool || "ask-one", opts.cache);
641
+ return callProvider(provider, req, opts.logger || NULL_LOGGER, opts.tool || "ask-one", opts.cache, opts.orientationFiles);
572
642
  }
573
643
  function buildArbiterPrompt(question, opinions) {
574
644
  const blocks = opinions.map((o, i) => `### Opinion ${i + 1}
@@ -593,17 +663,17 @@ ${blocks}`,
593
663
  // Promise.resolve().then(...) so even a SYNCHRONOUS throw in ask() is caught
594
664
  // by the rejection handler (a bare arbiter.ask() could throw before awaiting).
595
665
  Promise.resolve().then(
596
- () => arbiter.ask({
666
+ () => arbiter.ask(withOrientation(arbiter, {
597
667
  ...req,
598
668
  files: req.files ? req.files.map((f) => ({ ...f })) : void 0,
599
669
  developerInstructions: opts.arbiterInstructions || req.developerInstructions
600
- })
670
+ }, opts.orientationFiles))
601
671
  ).then((v) => v, () => null)
602
672
  ) : Promise.resolve(
603
673
  /** @type {DelegationResult|null} */
604
674
  null
605
675
  );
606
- const [opinions, blindVerdict] = await Promise.all([askAll2(providers, req, { logger: opts.logger, tool: "consensus" }), blindPromise]);
676
+ const [opinions, blindVerdict] = await Promise.all([askAll2(providers, req, { logger: opts.logger, tool: "consensus", orientationFiles: opts.orientationFiles }), blindPromise]);
607
677
  const ok = (
608
678
  /** @type {DelegationSuccess[]} */
609
679
  opinions.filter((o) => !o.isError)
@@ -666,8 +736,8 @@ ${feedback || "(reviewers gave no specific issues; tighten the weakest part)"}`
666
736
  const { peerPrompt, blindPrompt } = loop.prepareRound(state);
667
737
  const roundNo = state.round;
668
738
  const [blindRes, peerResults] = await Promise.all([
669
- Promise.resolve().then(() => arbiter.ask({ ...req, prompt: blindPrompt })).then((r) => r, () => null),
670
- askAll2(providers, { ...req, prompt: peerPrompt }, { logger, tool: "consensus" })
739
+ Promise.resolve().then(() => arbiter.ask(withOrientation(arbiter, { ...req, prompt: blindPrompt }, opts.orientationFiles))).then((r) => r, () => null),
740
+ askAll2(providers, { ...req, prompt: peerPrompt }, { logger, tool: "consensus", orientationFiles: opts.orientationFiles })
671
741
  ]);
672
742
  state = loop.recordBlindVerdict(state, okText(blindRes) || "(blind pass unavailable)");
673
743
  lastResults = peerResults.map(
@@ -732,6 +802,52 @@ ${feedback || "(reviewers gave no specific issues; tighten the weakest part)"}`
732
802
  }
733
803
  });
734
804
 
805
+ // ../../core/orientation.js
806
+ var require_orientation = __commonJS({
807
+ "../../core/orientation.js"(exports2, module2) {
808
+ "use strict";
809
+ var fs = require("node:fs");
810
+ var path = require("node:path");
811
+ var ORIENTATION_CANDIDATES = [
812
+ "CLAUDE.md",
813
+ "AGENTS.md",
814
+ "README.md",
815
+ "package.json",
816
+ "pyproject.toml",
817
+ "Cargo.toml",
818
+ "go.mod",
819
+ "tsconfig.json",
820
+ "main.tf"
821
+ ];
822
+ var DEFAULT_MAX_FILES = 6;
823
+ function resolveOrientationFiles(cwd, opts = {}) {
824
+ const base = cwd || process.cwd();
825
+ const max = Number.isInteger(opts.maxFiles) && /** @type {number} */
826
+ opts.maxFiles > 0 ? (
827
+ /** @type {number} */
828
+ opts.maxFiles
829
+ ) : DEFAULT_MAX_FILES;
830
+ const candidates = opts.candidates || ORIENTATION_CANDIDATES;
831
+ const out = [];
832
+ for (const name of candidates) {
833
+ if (out.length >= max) break;
834
+ const abs = path.join(base, name);
835
+ try {
836
+ if (fs.statSync(abs).isFile()) out.push({ path: abs });
837
+ } catch {
838
+ }
839
+ }
840
+ return out;
841
+ }
842
+ function orientationFilesFor2(config, cwd) {
843
+ const o = config && config.orientation;
844
+ if (!o || o.enabled !== true) return void 0;
845
+ return resolveOrientationFiles(cwd, { maxFiles: o.maxFiles });
846
+ }
847
+ module2.exports = { resolveOrientationFiles, orientationFilesFor: orientationFilesFor2, ORIENTATION_CANDIDATES, DEFAULT_MAX_FILES };
848
+ }
849
+ });
850
+
735
851
  // ../../core/prompts/index.js
736
852
  var require_prompts = __commonJS({
737
853
  "../../core/prompts/index.js"(exports2, module2) {
@@ -1443,7 +1559,7 @@ var require_openai_compatible = __commonJS({
1443
1559
  /** @type {any} */
1444
1560
  {
1445
1561
  name,
1446
- capabilities: { canImplement: false, fileUpload: false, multiTurn: true },
1562
+ capabilities: { canImplement: false, fileUpload: false, multiTurn: true, walksFilesystem: false },
1447
1563
  /** Test-only: current number of cached sessions. */
1448
1564
  get __sessionCount() {
1449
1565
  return sessions.size;
@@ -1525,7 +1641,7 @@ var require_grok = __commonJS({
1525
1641
  name: "grok",
1526
1642
  // multiTurn is not wired through Core (runGrok/runWithFiles return no threadId),
1527
1643
  // so report false to match reality.
1528
- capabilities: { canImplement: false, fileUpload: true, multiTurn: false },
1644
+ capabilities: { canImplement: false, fileUpload: true, multiTurn: false, walksFilesystem: false },
1529
1645
  async health() {
1530
1646
  return process.env.XAI_API_KEY ? { ok: true } : { ok: false, reason: "XAI_API_KEY unset" };
1531
1647
  },
@@ -1585,7 +1701,7 @@ var require_antigravity = __commonJS({
1585
1701
  const model = opts.model || process.env.GEMINI_DEFAULT_MODEL || "auto-gemini-3";
1586
1702
  return {
1587
1703
  name: "gemini",
1588
- capabilities: { canImplement: true, fileUpload: false, multiTurn: true },
1704
+ capabilities: { canImplement: true, fileUpload: false, multiTurn: true, walksFilesystem: true },
1589
1705
  async health() {
1590
1706
  return typeof bridge.runGemini === "function" ? { ok: true } : { ok: false, reason: "agy bridge unavailable" };
1591
1707
  },
@@ -1666,7 +1782,7 @@ var require_codex = __commonJS({
1666
1782
  const model = opts.model || "default";
1667
1783
  return {
1668
1784
  name: "codex",
1669
- capabilities: { canImplement: true, fileUpload: false, multiTurn: false },
1785
+ capabilities: { canImplement: true, fileUpload: false, multiTurn: false, walksFilesystem: true },
1670
1786
  // Option A: no threadId continuity
1671
1787
  async health() {
1672
1788
  return { ok: true };
@@ -4525,6 +4641,7 @@ var require_openrouter = __commonJS({
4525
4641
  // index.js
4526
4642
  var { makeRegistry, pinAlias } = require_registry();
4527
4643
  var { askAll, askOne, consensus, runToConvergence } = require_orchestrate();
4644
+ var { orientationFilesFor } = require_orientation();
4528
4645
  var { PROMPTS } = require_prompts();
4529
4646
  var analyzeCore = require_analyze();
4530
4647
  var ADVISORY = { readOnlyHint: true };
@@ -4801,6 +4918,9 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir, notify
4801
4918
  if (!persona) return request;
4802
4919
  return { ...request, developerInstructions: persona };
4803
4920
  }
4921
+ function orient(req) {
4922
+ return orientationFilesFor(getConfig(), req && req.cwd);
4923
+ }
4804
4924
  function sessionsCfg() {
4805
4925
  const c = getConfig() || {};
4806
4926
  const s = c.sessions || {};
@@ -4898,7 +5018,7 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir, notify
4898
5018
  lg.logEvent({ event: "dispatch_start", at: Date.now(), tool: "ask-all", voices: selected.length });
4899
5019
  } catch {
4900
5020
  }
4901
- const results = await askAll(selected, withPersona(req, expert), { logger: lg, tool: "ask-all", cache: opts.noCache ? void 0 : resultCache });
5021
+ const results = await askAll(selected, withPersona(req, expert), { logger: lg, tool: "ask-all", cache: opts.noCache ? void 0 : resultCache, orientationFiles: orient(req) });
4902
5022
  return {
4903
5023
  payload: { results, omitted },
4904
5024
  parts: { opinions: results, blindVerdict: null, verdict: null, arbiter: null, warnings: [] }
@@ -4916,13 +5036,13 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir, notify
4916
5036
  const resolved = await resolveArbiter(arbiterSpec, selected, registry, getConfig);
4917
5037
  if (resolved.warning) warnings.push(resolved.warning);
4918
5038
  if (resolved.mode === "host") {
4919
- const opinions = await askAll(selected, withPersona(req, expert), { logger: currentLogger(), tool: "consensus" });
5039
+ const opinions = await askAll(selected, withPersona(req, expert), { logger: currentLogger(), tool: "consensus", orientationFiles: orient(req) });
4920
5040
  const arbiter2 = { mode: "host" };
4921
5041
  const body = { opinions, blindVerdict: null, verdict: null, arbiter: arbiter2, warnings };
4922
5042
  return { payload: body, parts: body };
4923
5043
  }
4924
5044
  if (!resolved.provider) {
4925
- const out2 = await consensus(selected, withPersona(req, expert), { arbiterInstructions: PROMPTS.arbiter, logger: currentLogger() });
5045
+ const out2 = await consensus(selected, withPersona(req, expert), { arbiterInstructions: PROMPTS.arbiter, logger: currentLogger(), orientationFiles: orient(req) });
4926
5046
  const arbiter2 = { mode: "server", provider: null };
4927
5047
  return {
4928
5048
  payload: { opinions: out2.opinions, blindVerdict: out2.blindVerdict, verdict: out2.verdict, error: out2.error, arbiter: arbiter2, warnings },
@@ -4935,7 +5055,7 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir, notify
4935
5055
  peers = selected;
4936
5056
  warnings.push(`panel too small to exclude arbiter '${arbiterP.name}'; kept it in the peer panel (floor of 2)`);
4937
5057
  }
4938
- const out = await consensus(peers, withPersona(req, expert), { arbiter: arbiterP, arbiterInstructions: PROMPTS.arbiter, blindVote, logger: currentLogger() });
5058
+ const out = await consensus(peers, withPersona(req, expert), { arbiter: arbiterP, arbiterInstructions: PROMPTS.arbiter, blindVote, logger: currentLogger(), orientationFiles: orient(req) });
4939
5059
  const arbiter = { mode: "server", provider: arbiterP.name };
4940
5060
  return {
4941
5061
  payload: { opinions: out.opinions, blindVerdict: out.blindVerdict, verdict: out.verdict, error: out.error, arbiter, warnings },
@@ -4965,7 +5085,7 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir, notify
4965
5085
  }
4966
5086
  const maxRounds = Number.isInteger(maxRoundsOverride) && /** @type {number} */
4967
5087
  maxRoundsOverride > 0 ? maxRoundsOverride : Number.isInteger(cc.maxRounds) && cc.maxRounds > 0 ? cc.maxRounds : void 0;
4968
- const out = await runToConvergence(peers, withPersona(req, expert), { arbiter: arbiterP, maxRounds, logger: currentLogger() });
5088
+ const out = await runToConvergence(peers, withPersona(req, expert), { arbiter: arbiterP, maxRounds, logger: currentLogger(), orientationFiles: orient(req) });
4969
5089
  const allWarnings = out.error ? warnings.concat([`loop: ${out.error}`]) : warnings;
4970
5090
  const rounds = Array.isArray(out.rounds) ? out.rounds.length : 0;
4971
5091
  const arbiter = { mode: "server", provider: arbiterP.name };
@@ -5106,7 +5226,7 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir, notify
5106
5226
  lg.logEvent({ event: "dispatch_start", at: Date.now(), tool: "consensus", round: cur.round, voices: selected.length });
5107
5227
  } catch {
5108
5228
  }
5109
- const peerResults = await askAll(selected, withPersona(peerReq, ex), { logger: lg, tool: "consensus" });
5229
+ const peerResults = await askAll(selected, withPersona(peerReq, ex), { logger: lg, tool: "consensus", orientationFiles: orient(peerReq) });
5110
5230
  const results = peerResults.map(
5111
5231
  (r) => r.isError ? { source: r.provider, isError: true, errorKind: r.errorKind, verdict: null, criticalIssues: [], model: r.model, reasoningEffort: r.reasoningEffort ?? null, ms: r.ms } : { ...parseReview(typeof r.text === "string" ? r.text : ""), source: r.provider, isError: false, model: r.model, reasoningEffort: r.reasoningEffort ?? null, ms: r.ms }
5112
5232
  );
@@ -5230,7 +5350,7 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir, notify
5230
5350
  if (!p) {
5231
5351
  return jsonResult({ error: `provider "${want}" is not in the active panel`, panel: selected.map((x) => x.name) });
5232
5352
  }
5233
- const result = await askOne(p, withPersona(req, expert), { logger: currentLogger(), tool: "ask-one", cache: resultCache });
5353
+ const result = await askOne(p, withPersona(req, expert), { logger: currentLogger(), tool: "ask-one", cache: resultCache, orientationFiles: orient(req) });
5234
5354
  return jsonResult({ result });
5235
5355
  }
5236
5356
  if (name === "analyze") {
@@ -5299,12 +5419,12 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir, notify
5299
5419
  if (Object.prototype.hasOwnProperty.call(ASK_PROVIDER, name)) {
5300
5420
  const p = registry.get(ASK_PROVIDER[name]);
5301
5421
  if (!p) return { content: [{ type: "text", text: JSON.stringify({ error: `provider ${ASK_PROVIDER[name]} not registered` }) }] };
5302
- const result = await askOne(p, withPersona(req, expert), { logger: currentLogger(), tool: "ask-one", cache: resultCache });
5422
+ const result = await askOne(p, withPersona(req, expert), { logger: currentLogger(), tool: "ask-one", cache: resultCache, orientationFiles: orient(req) });
5303
5423
  return { content: [{ type: "text", text: JSON.stringify({ result }) }] };
5304
5424
  }
5305
5425
  if (EXPERTS.includes(name)) {
5306
5426
  const { providers: selected } = registry.selectForAskAll({ config: getConfig(), expert: name });
5307
- const results = await askAll(selected, withPersona({ ...req, expert: name }, expert), { logger: currentLogger(), tool: name, cache: resultCache });
5427
+ const results = await askAll(selected, withPersona({ ...req, expert: name }, expert), { logger: currentLogger(), tool: name, cache: resultCache, orientationFiles: orient(req) });
5308
5428
  return { content: [{ type: "text", text: JSON.stringify({ results }) }] };
5309
5429
  }
5310
5430
  throw new Error(`unknown tool: ${name}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antonbabenko/deliberation-mcp",
3
- "version": "3.5.4",
3
+ "version": "3.6.1",
4
4
  "description": "Deliberation for Claude Code and any MCP host - GPT, Gemini, Grok, and OpenRouter expert subagents.",
5
5
  "mcpName": "io.github.antonbabenko/deliberation",
6
6
  "repository": { "type": "git", "url": "git+https://github.com/antonbabenko/deliberation.git", "directory": "server/mcp" },