@mrrlin-dev/mcp 0.4.1 → 0.4.2

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/bin.cjs +91 -10
  2. package/package.json +4 -4
package/dist/bin.cjs CHANGED
@@ -38187,6 +38187,9 @@ var mrrlinInboxQuestionOptionsSchema = external_exports.array(mrrlinInboxQuestio
38187
38187
  });
38188
38188
  }
38189
38189
  });
38190
+ var REVIEW_AGAIN_ANSWER = "Review the current spec again without changing it.";
38191
+ var EDIT_SPEC_ANSWER = "Hold the task until the spec is edited by hand.";
38192
+ var PROCEED_ANYWAY_ANSWER = "Proceed without agreement; record the unsettled points on the spec.";
38190
38193
  var mrrlinInboxItemSchema = external_exports.object({
38191
38194
  actionPayload: mrrlinInboxItemActionPayloadSchema.nullable(),
38192
38195
  answer: external_exports.string().nullable(),
@@ -40187,7 +40190,7 @@ function startCodexProbe(deps = {}) {
40187
40190
  }
40188
40191
 
40189
40192
  // src/_generated/version.ts
40190
- var PKG_VERSION = "0.4.1";
40193
+ var PKG_VERSION = "0.4.2";
40191
40194
 
40192
40195
  // src/api-base-url-migrate.ts
40193
40196
  var import_node_fs2 = __toESM(require("node:fs"), 1);
@@ -41312,7 +41315,7 @@ async function runConsensus(input) {
41312
41315
  let decision;
41313
41316
  for (let round = 1; round <= effectiveRounds; round++) {
41314
41317
  if (calls + input.heads.length > input.maxHeadCalls) {
41315
- return { outcome: "CONSENSUS_BLOCKED", rounds: round - 1, finalArtifact: artifact, history, ...decision ? { escalation: decision } : {} };
41318
+ return { outcome: "CONSENSUS_BLOCKED", rounds: round - 1, finalArtifact: artifact, history, unresolved: history[history.length - 1]?.accepted ?? [], ...decision ? { escalation: decision } : {} };
41316
41319
  }
41317
41320
  const dismissedBlock = dismissedFwd.length ? `PREVIOUSLY DISMISSED (do not re-raise unless new information):
41318
41321
  ${dismissedFwd.slice(-20).join("\n")}` : "";
@@ -41334,7 +41337,7 @@ ${dismissedFwd.slice(-20).join("\n")}` : "";
41334
41337
  (r) => r.res.ok
41335
41338
  );
41336
41339
  if (responding.length === 0) {
41337
- return { outcome: "CONSENSUS_UNAVAILABLE", rounds: round, finalArtifact: artifact, history };
41340
+ return { outcome: "CONSENSUS_UNAVAILABLE", rounds: round, finalArtifact: artifact, history, unresolved: [] };
41338
41341
  }
41339
41342
  const headOutcomes = input.heads.map((persona) => ({
41340
41343
  persona,
@@ -41360,7 +41363,7 @@ ${dismissedFwd.slice(-20).join("\n")}` : "";
41360
41363
  let adj;
41361
41364
  if (issues.length > 0) {
41362
41365
  if (calls + 1 > input.maxHeadCalls) {
41363
- return { outcome: "CONSENSUS_BLOCKED", rounds: round, finalArtifact: artifact, history, ...decision ? { escalation: decision } : {} };
41366
+ return { outcome: "CONSENSUS_BLOCKED", rounds: round, finalArtifact: artifact, history, unresolved: history[history.length - 1]?.accepted ?? [], ...decision ? { escalation: decision } : {} };
41364
41367
  }
41365
41368
  calls++;
41366
41369
  try {
@@ -41374,7 +41377,7 @@ ${dismissedFwd.slice(-20).join("\n")}` : "";
41374
41377
  history.push({ round, accepted: adj.accepted, dismissed: adj.dismissed, headOutcomes });
41375
41378
  adj.dismissed.forEach((d) => dismissedFwd.push(`- ${d.id}: ${d.reason}`));
41376
41379
  if (adj.accepted.length === 0) {
41377
- return { outcome: "CONVERGED", rounds: round, finalArtifact: artifact, history, ...decision ? { escalation: decision } : {} };
41380
+ return { outcome: "CONVERGED", rounds: round, finalArtifact: artifact, history, unresolved: [], ...decision ? { escalation: decision } : {} };
41378
41381
  }
41379
41382
  if (round === effectiveRounds) {
41380
41383
  if (!decision) {
@@ -41384,12 +41387,12 @@ ${dismissedFwd.slice(-20).join("\n")}` : "";
41384
41387
  escalated = true;
41385
41388
  effectiveRounds = Math.min(effectiveRounds + decision.grantedRounds, input.rounds * 2);
41386
41389
  } else {
41387
- return { outcome: "CONSENSUS_BLOCKED", rounds: round, finalArtifact: artifact, history, ...decision ? { escalation: decision } : {} };
41390
+ return { outcome: "CONSENSUS_BLOCKED", rounds: round, finalArtifact: artifact, history, unresolved: history[history.length - 1]?.accepted ?? [], ...decision ? { escalation: decision } : {} };
41388
41391
  }
41389
41392
  }
41390
41393
  artifact = await input.revise(artifact, adj.accepted);
41391
41394
  }
41392
- return { outcome: "CONSENSUS_BLOCKED", rounds: effectiveRounds, finalArtifact: artifact, history };
41395
+ return { outcome: "CONSENSUS_BLOCKED", rounds: effectiveRounds, finalArtifact: artifact, history, unresolved: history[history.length - 1]?.accepted ?? [] };
41393
41396
  }
41394
41397
 
41395
41398
  // src/consensus/spec-identity.ts
@@ -41688,7 +41691,8 @@ async function runSpecGate(_target, deps, opts) {
41688
41691
  outcome: "CONSENSUS_BLOCKED",
41689
41692
  ...keptClarifications.length > 0 ? { operatorClarifications: keptClarifications } : {},
41690
41693
  ...droppedByCap.length > 0 ? { droppedByCap } : {},
41691
- ...result.escalation ? { escalation: result.escalation } : {}
41694
+ ...result.escalation ? { escalation: result.escalation } : {},
41695
+ unresolved: result.unresolved
41692
41696
  };
41693
41697
  }
41694
41698
  return { outcome: result.outcome };
@@ -57470,6 +57474,44 @@ function humanizeClarificationBody(clarification) {
57470
57474
  const context = clarification.rawQuestion.replace(/\b(?:parseable|SPEC_IDENTITY_MISMATCH|machine-usable)\b/gi, "").replace(/\s+/g, " ").trim();
57471
57475
  return context ? `${prefix} ${context}` : prefix;
57472
57476
  }
57477
+ var BLOCK_DECISION_OPENER = "The reviewers could not agree on this spec";
57478
+ var BLOCK_BODY_TRAILER = " How should I proceed?";
57479
+ function renderUnresolvedPoints(unresolved) {
57480
+ return unresolved.map(
57481
+ (issue2) => issue2.text.replace(/\b(?:parseable|SPEC_IDENTITY_MISMATCH|machine-usable)\b/gi, "").replace(/\s+/g, " ").trim()
57482
+ ).join("; ");
57483
+ }
57484
+ function renderBlockDecisionBody(taskRef, unresolved) {
57485
+ if (unresolved.length === 0) {
57486
+ return `${BLOCK_DECISION_OPENER} (task ${taskRef}), and their objections could not be read back.${BLOCK_BODY_TRAILER}`;
57487
+ }
57488
+ return `${BLOCK_DECISION_OPENER} (task ${taskRef}). Still unsettled: ${renderUnresolvedPoints(unresolved)}.${BLOCK_BODY_TRAILER}`;
57489
+ }
57490
+ function buildBlockDecisionOptions(diagnosis, reviewAgainAvailable) {
57491
+ const recommendReviewAgain = reviewAgainAvailable && (diagnosis === "INFRA" || diagnosis === "SUBSTANCE");
57492
+ const recommendEditSpec = !reviewAgainAvailable || diagnosis === "AMBIGUITY";
57493
+ const options = [];
57494
+ if (reviewAgainAvailable) {
57495
+ options.push({
57496
+ label: "Review again",
57497
+ description: "Send the current spec back for another review.",
57498
+ answer: REVIEW_AGAIN_ANSWER,
57499
+ ...recommendReviewAgain ? { recommended: true } : {}
57500
+ });
57501
+ }
57502
+ options.push({
57503
+ label: "Edit spec",
57504
+ description: "Open the spec and change it by hand; the review restarts on save.",
57505
+ answer: EDIT_SPEC_ANSWER,
57506
+ ...recommendEditSpec ? { recommended: true } : {}
57507
+ });
57508
+ options.push({
57509
+ label: "Proceed anyway",
57510
+ description: "Start the work with these points unsettled and recorded.",
57511
+ answer: PROCEED_ANYWAY_ANSWER
57512
+ });
57513
+ return options;
57514
+ }
57473
57515
  var defaultNativeSpecGateBuilders = { buildSpecGateDeps, runSpecGate };
57474
57516
  async function runNativeSpecGate(ctx, input, builders = defaultNativeSpecGateBuilders) {
57475
57517
  const deps = builders.buildSpecGateDeps({
@@ -57516,6 +57558,7 @@ function stripHumanizeOpener(body) {
57516
57558
  }
57517
57559
  return body;
57518
57560
  }
57561
+ var DID_NOT_CONVERGE_ERROR = "Consensus blocked: reviewers did not converge within budget.";
57519
57562
  async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
57520
57563
  const operatorClarifications = result.operatorClarifications ?? [];
57521
57564
  const diagnostics = result.diagnostics ?? [];
@@ -57523,9 +57566,10 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
57523
57566
  const taskId = page?.taskId ?? null;
57524
57567
  if (!resolvedTaskId && taskId) resolvedTaskId = taskId;
57525
57568
  const adjudicated = [];
57569
+ let taskInboxItems = [];
57526
57570
  if (taskId) {
57527
57571
  try {
57528
- const taskInboxItems = await ctx.client.listInboxItems(input.projectSlug, { taskId });
57572
+ taskInboxItems = await ctx.client.listInboxItems(input.projectSlug, { taskId });
57529
57573
  for (const item of taskInboxItems) {
57530
57574
  if (item.origin === "gate" && (item.status === "acknowledged" || item.status === "rejected")) {
57531
57575
  adjudicated.push({ body: stripHumanizeOpener(item.body), answer: item.answer, status: item.status });
@@ -57594,6 +57638,35 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
57594
57638
  filedQuestionCount = toFile.length;
57595
57639
  }
57596
57640
  }
57641
+ if (filedQuestionCount === 0 && diagnostics.length === 0 && taskId && page) {
57642
+ const domain2 = await resolveSpecDomain(ctx.client, input.projectSlug, {
57643
+ id: page.id,
57644
+ taskId: page.taskId ?? null
57645
+ });
57646
+ const reviewHash = domainScopedReviewHash2(page.contentHash, domain2);
57647
+ const baseBlockItemId = `II-${taskId}-gateblock-${(0, import_node_crypto9.createHash)("sha256").update(reviewHash).digest("hex").slice(0, 10)}`;
57648
+ let taskRef = taskId;
57649
+ try {
57650
+ const t = await ctx.client.getTask(input.projectSlug, taskId);
57651
+ if (t?.title) taskRef = `${taskId} \u2014 ${t.title}`;
57652
+ } catch {
57653
+ }
57654
+ const reviewAgainAvailable = !taskInboxItems.some(
57655
+ (item) => item.id === baseBlockItemId && item.status !== "pending" && item.answer === REVIEW_AGAIN_ANSWER
57656
+ );
57657
+ const blockItemId = reviewAgainAvailable ? baseBlockItemId : `${baseBlockItemId}-2`;
57658
+ try {
57659
+ await ctx.client.createInboxItem(input.projectSlug, {
57660
+ id: blockItemId,
57661
+ kind: "question",
57662
+ taskId,
57663
+ body: renderBlockDecisionBody(taskRef, result.unresolved ?? []),
57664
+ origin: "gate_block",
57665
+ questionOptions: buildBlockDecisionOptions(result.escalation?.diagnosis, reviewAgainAvailable)
57666
+ });
57667
+ } catch {
57668
+ }
57669
+ }
57597
57670
  if (operatorClarifications.length > 0) {
57598
57671
  try {
57599
57672
  if (page) {
@@ -57634,7 +57707,7 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
57634
57707
  } else if (operatorClarifications.length > 0) {
57635
57708
  error51 = `Consensus blocked on ${operatorClarifications.length} clarification(s), but the spec page is not linked to a task or plan, so none could be filed as questions.`;
57636
57709
  } else {
57637
- error51 = "Consensus blocked: reviewers did not converge within budget.";
57710
+ error51 = DID_NOT_CONVERGE_ERROR;
57638
57711
  }
57639
57712
  if (result.escalation) {
57640
57713
  error51 += ` (diagnosis: ${result.escalation.diagnosis})`;
@@ -57776,6 +57849,14 @@ function onComplete2(outcome, input) {
57776
57849
  body: taskRef ? `Consensus blocked on ${taskRef} (Specs/${input.pageId}); filed ${n} clarifying question${n === 1 ? "" : "s"} \u2014 answer to unblock the run.` : `Consensus blocked on Specs/${input.pageId}; filed ${n} clarifying question${n === 1 ? "" : "s"} \u2014 answer to unblock the run.`
57777
57850
  };
57778
57851
  }
57852
+ if (outcome.kind === "failed" && outcome.error.startsWith(DID_NOT_CONVERGE_ERROR) && taskId) {
57853
+ return {
57854
+ kind: "info",
57855
+ suppressInbox: true,
57856
+ ...taskId ? { taskId } : {},
57857
+ body: taskRef ? `Consensus blocked on ${taskRef} (Specs/${input.pageId}); the reviewers did not agree \u2014 answer the decision question to unblock the run.` : `Consensus blocked on Specs/${input.pageId}; the reviewers did not agree \u2014 answer the decision question to unblock the run.`
57858
+ };
57859
+ }
57779
57860
  const handoffTaskRef = taskRef ? ` (${taskRef})` : "";
57780
57861
  return {
57781
57862
  kind: "handoff",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrlin-dev/mcp",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mrrlin-mcp": "dist/bin.cjs"
@@ -23,10 +23,10 @@
23
23
  "tsx": "^4.22.3",
24
24
  "@mrrlin/client": "0.0.0",
25
25
  "@mrrlin/codex-client": "0.0.0",
26
- "@mrrlin/director-e2e": "0.0.0",
27
- "@mrrlin/schemas": "0.0.0",
28
26
  "@mrrlin/tsconfig": "0.0.0",
29
- "@mrrlin/wiki": "0.0.0"
27
+ "@mrrlin/wiki": "0.0.0",
28
+ "@mrrlin/schemas": "0.0.0",
29
+ "@mrrlin/director-e2e": "0.0.0"
30
30
  },
31
31
  "dependencies": {
32
32
  "@iarna/toml": "^2.2.5",