@mrrlin-dev/mcp 0.3.57 → 0.3.60

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/bin.cjs CHANGED
@@ -20665,10 +20665,7 @@ function normalizeUrl(value) {
20665
20665
  var RETIRED_API_BASE_URLS = {
20666
20666
  // Retired 2026-07-28 when staging moved to the mrrlin2 Vercel team; serves a decommissioned
20667
20667
  // Supabase project and now answers `GET /health` with 402 DEPLOYMENT_DISABLED. Added in 0.3.50.
20668
- "mrrlin-api-fnnzzz-fnnzzzs-projects.vercel.app": STAGING_API_BASE_URL,
20669
- // Retired 2026-08-11 when staging moved to the mrrlin3 Vercel team after mrrlin2 hit usage
20670
- // limits. Added in 0.3.57.
20671
- "mrrlin-api-mrrlin2.vercel.app": STAGING_API_BASE_URL
20668
+ "mrrlin-api-fnnzzz-fnnzzzs-projects.vercel.app": STAGING_API_BASE_URL
20672
20669
  };
20673
20670
  function isSameApiHost(a, b) {
20674
20671
  const origin = (value) => {
@@ -21836,136 +21833,6 @@ async function acquireThread(codex, opts) {
21836
21833
  return { threadId: newId, resumed: false };
21837
21834
  }
21838
21835
 
21839
- // ../../packages/codex-client/dist/render-context-bundle.js
21840
- var DEFAULT_SPEC_BUDGET_CHARS = 6e3;
21841
- var DEFAULT_ARTIFACT_PAYLOAD_BUDGET_CHARS = 400;
21842
- function renderContextBundle(input) {
21843
- const sections = [];
21844
- sections.push(renderTaskSummary(input.task));
21845
- sections.push(renderSpec(input.specMarkdown, input.compact));
21846
- sections.push(renderRunHistory(input.runs, input.openRunId));
21847
- sections.push(renderOpenRun(input.openRunId));
21848
- sections.push(renderArtifacts(input.artifacts, input.omittedArtifactCount ?? 0, input.compact));
21849
- sections.push(renderAutonomyEvents(input.autonomyEvents));
21850
- return sections.join("\n\n");
21851
- }
21852
- function renderTaskSummary(task) {
21853
- const lines2 = [
21854
- "## Task",
21855
- `id: ${task.id}`,
21856
- `title: ${task.title}`,
21857
- `autonomy: ${task.autonomyLevel} (${task.autonomyReason})`
21858
- ];
21859
- if (task.notes)
21860
- lines2.push(`description: ${task.notes}`);
21861
- if (task.latestJudgement) {
21862
- lines2.push(`latest judgement (${task.latestJudgementKind ?? "unknown"}): ${task.latestJudgement}`);
21863
- }
21864
- return lines2.join("\n");
21865
- }
21866
- function renderSpec(specMarkdown, compact) {
21867
- if (!specMarkdown)
21868
- return "## Spec\n(none)";
21869
- if (!compact)
21870
- return `## Spec
21871
- ${specMarkdown}`;
21872
- const budget = compact.specBudgetChars ?? DEFAULT_SPEC_BUDGET_CHARS;
21873
- if (specMarkdown.length <= budget)
21874
- return `## Spec
21875
- ${specMarkdown}`;
21876
- return `## Spec
21877
- ${compactSpecMarkdown(specMarkdown, budget, compact.projectSlug ?? null, compact.specWikiPageId ?? null)}`;
21878
- }
21879
- function compactSpecMarkdown(markdown, totalBudget, projectSlug, specWikiPageId) {
21880
- const lines2 = markdown.split(/\r?\n/);
21881
- const sections = [{ heading: "", body: [] }];
21882
- for (const line of lines2) {
21883
- if (/^#{1,6}\s/.test(line)) {
21884
- sections.push({ heading: line, body: [] });
21885
- } else {
21886
- sections[sections.length - 1]?.body.push(line);
21887
- }
21888
- }
21889
- const sectionCount = Math.max(1, sections.length);
21890
- const perSectionBudget = Math.max(120, Math.floor(totalBudget / sectionCount));
21891
- const out = [];
21892
- for (const section of sections) {
21893
- const bodyText = section.body.join("\n").replace(/^\s+|\s+$/g, "");
21894
- if (section.heading)
21895
- out.push(section.heading);
21896
- if (!bodyText)
21897
- continue;
21898
- if (bodyText.length <= perSectionBudget) {
21899
- out.push(bodyText);
21900
- } else {
21901
- const kept = bodyText.slice(0, perSectionBudget);
21902
- const omitted = bodyText.length - perSectionBudget;
21903
- out.push(`${kept}
21904
- \u2026(${omitted} chars omitted; retrieve full spec for the rest)`);
21905
- }
21906
- }
21907
- if (projectSlug && specWikiPageId) {
21908
- out.push("");
21909
- out.push(`> Compact spec. Source: mrrlin://wiki/${projectSlug}/${specWikiPageId} \u2014 call retrieve_context with this handle before any irreversible decision.`);
21910
- } else {
21911
- out.push("");
21912
- out.push("> Compact spec. Retrieve the Specs/{id} wiki page via get_wiki_page before any irreversible decision.");
21913
- }
21914
- return out.join("\n");
21915
- }
21916
- function renderRunHistory(runs, openRunId) {
21917
- if (runs.length === 0)
21918
- return "## Execution-run history\n(none)";
21919
- const lines2 = ["## Execution-run history"];
21920
- for (const run3 of runs) {
21921
- const marker = run3.id === openRunId ? " (open)" : "";
21922
- lines2.push(`- ${run3.id} [${run3.status}] created ${run3.createdAt}${marker}`);
21923
- }
21924
- return lines2.join("\n");
21925
- }
21926
- function renderOpenRun(openRunId) {
21927
- return `## Open run
21928
- ${openRunId ?? "(none)"}`;
21929
- }
21930
- function renderArtifacts(artifacts, omittedCount, compact) {
21931
- if (artifacts.length === 0 && omittedCount === 0)
21932
- return "## Artifacts\n(none)";
21933
- const lines2 = ["## Artifacts"];
21934
- if (omittedCount > 0) {
21935
- lines2.push(`(+${omittedCount} older artifact${omittedCount === 1 ? "" : "s"} omitted)`);
21936
- }
21937
- let truncatedAny = false;
21938
- for (const artifact of artifacts) {
21939
- if (artifact.kind === "file") {
21940
- const p = artifact.payload;
21941
- if (p.filename && p.stableUrl) {
21942
- lines2.push(`- ${artifact.createdAt} [file] ${p.filename} (${p.class ?? "?"}, ${p.sizeBytes ?? "?"} bytes) ${p.stableUrl}`);
21943
- continue;
21944
- }
21945
- }
21946
- const payloadText = JSON.stringify(artifact.payload);
21947
- const budget = compact?.artifactPayloadBudgetChars ?? DEFAULT_ARTIFACT_PAYLOAD_BUDGET_CHARS;
21948
- const willTruncate = !!compact && payloadText.length > budget;
21949
- const rendered = willTruncate ? `${payloadText.slice(0, budget)}\u2026(${payloadText.length - budget} chars omitted)` : payloadText;
21950
- if (willTruncate)
21951
- truncatedAny = true;
21952
- lines2.push(`- ${artifact.createdAt} [${artifact.kind}] run=${artifact.runId} ${rendered}`);
21953
- }
21954
- if (compact && (omittedCount > 0 || truncatedAny)) {
21955
- lines2.push("> Compact artifacts. Use retrieve_context with a `mrrlin://run/{projectSlug}/{runId}/artifacts` handle (substitute the slug and run id from the lines above) to load a run's full artifact history.");
21956
- }
21957
- return lines2.join("\n");
21958
- }
21959
- function renderAutonomyEvents(events) {
21960
- if (events.length === 0)
21961
- return "## Autonomy history\n(none)";
21962
- const lines2 = ["## Autonomy history"];
21963
- for (const event of events) {
21964
- lines2.push(`- ${event.createdAt} ${event.autonomyLevel ?? "?"} (${event.autonomyReason ?? ""})`);
21965
- }
21966
- return lines2.join("\n");
21967
- }
21968
-
21969
21836
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
21970
21837
  var external_exports = {};
21971
21838
  __export(external_exports, {
@@ -36665,19 +36532,22 @@ var agentPoolResponseSchema = external_exports.object({
36665
36532
  });
36666
36533
 
36667
36534
  // ../../packages/schemas/dist/specConsensusReview.js
36535
+ var mrrlinSpecConsensusSuppressionEvidenceSchema = external_exports.record(external_exports.string(), external_exports.unknown()).nullable();
36668
36536
  var mrrlinSpecConsensusReviewSchema = external_exports.object({
36669
36537
  projectSlug: external_exports.string().min(1),
36670
36538
  specPageId: external_exports.string().min(1),
36671
36539
  contentHash: external_exports.string().min(1),
36672
36540
  outcome: external_exports.string().min(1),
36673
36541
  rounds: external_exports.number().int().nonnegative(),
36542
+ suppressionEvidence: mrrlinSpecConsensusSuppressionEvidenceSchema.default(null),
36674
36543
  createdAt: external_exports.string().datetime()
36675
36544
  });
36676
36545
  var mrrlinSpecConsensusReviewCreateSchema = external_exports.object({
36677
36546
  specPageId: external_exports.string().min(1),
36678
36547
  contentHash: external_exports.string().min(1),
36679
36548
  outcome: external_exports.string().min(1),
36680
- rounds: external_exports.number().int().nonnegative()
36549
+ rounds: external_exports.number().int().nonnegative(),
36550
+ suppressionEvidence: mrrlinSpecConsensusSuppressionEvidenceSchema.optional()
36681
36551
  }).strict();
36682
36552
  var specConsensusReviewResponseSchema = external_exports.object({ data: mrrlinSpecConsensusReviewSchema });
36683
36553
  var specConsensusReviewMaybeResponseSchema = external_exports.object({ data: mrrlinSpecConsensusReviewSchema.nullable() });
@@ -36753,6 +36623,97 @@ var mrrlinAsyncJobRequeueSchema = external_exports.object({
36753
36623
  staleSeconds: external_exports.number().int().min(60).max(86400).default(300)
36754
36624
  });
36755
36625
 
36626
+ // ../../packages/schemas/dist/memory-budget.js
36627
+ function evictionKey(item, idx) {
36628
+ const memClass = item.memoryClass === "historical" ? 0 : 1;
36629
+ const prec = item.precedence === null ? -4 : -item.precedence;
36630
+ const score = item.score ?? 0;
36631
+ return { memClass, prec, score, idx };
36632
+ }
36633
+ function compareEvictionKeys(a, b) {
36634
+ if (a.memClass !== b.memClass)
36635
+ return a.memClass - b.memClass;
36636
+ if (a.prec !== b.prec)
36637
+ return a.prec - b.prec;
36638
+ if (a.score !== b.score)
36639
+ return a.score - b.score;
36640
+ return a.idx - b.idx;
36641
+ }
36642
+ function sortByEvictionOrder(items) {
36643
+ const indexed = items.map((item, idx) => ({ item, key: evictionKey(item, idx) }));
36644
+ indexed.sort((a, b) => compareEvictionKeys(a.key, b.key));
36645
+ return indexed.map((x) => x.item);
36646
+ }
36647
+ function keepUnderCap(sorted, cap) {
36648
+ const kept = [];
36649
+ let used = 0;
36650
+ let i = sorted.length - 1;
36651
+ for (; i >= 0; i--) {
36652
+ const item = sorted[i];
36653
+ if (item === void 0)
36654
+ break;
36655
+ if (used + item.size <= cap) {
36656
+ kept.unshift(item);
36657
+ used += item.size;
36658
+ } else {
36659
+ break;
36660
+ }
36661
+ }
36662
+ const dropped = sorted.slice(0, i + 1);
36663
+ return { kept, dropped };
36664
+ }
36665
+ function applyBudget(items, spec) {
36666
+ const prec0Items = [];
36667
+ const restItems = [];
36668
+ for (const item of items) {
36669
+ if (item.precedence === 0) {
36670
+ prec0Items.push(item);
36671
+ } else {
36672
+ restItems.push(item);
36673
+ }
36674
+ }
36675
+ const sourceCaps = spec.sourceCaps ?? {};
36676
+ const sourceMap = /* @__PURE__ */ new Map();
36677
+ for (const item of restItems) {
36678
+ const list = sourceMap.get(item.source);
36679
+ if (list)
36680
+ list.push(item);
36681
+ else
36682
+ sourceMap.set(item.source, [item]);
36683
+ }
36684
+ const keptAfterSourceCap = [];
36685
+ const droppedAfterSourceCap = [];
36686
+ for (const [source, sourceItems] of sourceMap) {
36687
+ const cap = sourceCaps[source];
36688
+ if (cap === void 0) {
36689
+ keptAfterSourceCap.push(...sourceItems);
36690
+ } else {
36691
+ const sorted = sortByEvictionOrder(sourceItems);
36692
+ const { kept, dropped } = keepUnderCap(sorted, cap);
36693
+ keptAfterSourceCap.push(...kept);
36694
+ droppedAfterSourceCap.push(...dropped);
36695
+ }
36696
+ }
36697
+ const allEligible = keptAfterSourceCap;
36698
+ const sortedGlobal = sortByEvictionOrder(allEligible);
36699
+ const { kept: keptGlobal, dropped: droppedGlobal } = keepUnderCap(sortedGlobal, spec.totalBytes - totalSize(prec0Items));
36700
+ const finalKept = [...prec0Items, ...keptGlobal];
36701
+ const finalDropped = [...droppedAfterSourceCap, ...droppedGlobal];
36702
+ const trimmed = finalDropped.length > 0;
36703
+ const keptSize = totalSize(finalKept);
36704
+ const overflowBy = Math.max(0, keptSize - spec.totalBytes);
36705
+ const handles = [];
36706
+ for (const item of finalDropped) {
36707
+ if (item.handle && item.handle.trim() !== "") {
36708
+ handles.push(item.handle);
36709
+ }
36710
+ }
36711
+ return { kept: finalKept, dropped: finalDropped, handles, trimmed, overflowBy };
36712
+ }
36713
+ function totalSize(items) {
36714
+ return items.reduce((sum, i) => sum + i.size, 0);
36715
+ }
36716
+
36756
36717
  // ../../packages/schemas/dist/index.js
36757
36718
  var mrrlinTaskStatusSchema = external_exports.enum([
36758
36719
  "todo",
@@ -36984,6 +36945,7 @@ var mrrlinTaskEventSchema = external_exports.object({
36984
36945
  });
36985
36946
  var mrrlinWikiPageSchema = external_exports.object({
36986
36947
  contentHash: external_exports.string().default(""),
36948
+ createdAt: external_exports.string().datetime().nullable().default(null),
36987
36949
  folder: external_exports.string().min(1),
36988
36950
  id: mrrlinWikiPageIdSchema,
36989
36951
  markdownBody: external_exports.string().min(1),
@@ -36991,7 +36953,8 @@ var mrrlinWikiPageSchema = external_exports.object({
36991
36953
  preview: external_exports.string(),
36992
36954
  specReady: external_exports.boolean().default(false),
36993
36955
  taskId: mrrlinTaskIdSchema.nullable().default(null),
36994
- title: external_exports.string().min(1)
36956
+ title: external_exports.string().min(1),
36957
+ updatedAt: external_exports.string().datetime().nullable().default(null)
36995
36958
  });
36996
36959
  var mrrlinMarkSpecReadyRequestSchema = external_exports.object({
36997
36960
  ready: external_exports.boolean()
@@ -37115,14 +37078,12 @@ var mrrlinProjectSnapshotSchema = external_exports.object({
37115
37078
  taxonomy: external_exports.array(mrrlinTaxonomyEntrySchema).default([]),
37116
37079
  wikiPages: external_exports.array(mrrlinWikiPageSchema).default([])
37117
37080
  });
37118
- var mrrlinTaskCreateSchema = external_exports.object({
37081
+ var mrrlinTaskCreateShapeSchema = external_exports.object({
37119
37082
  // Auto-deploy design Part B: input-optional (NOT .default("off")) so createTask can tell
37120
37083
  // "caller omitted this" apart from "caller explicitly chose off" -- omitted falls through to
37121
37084
  // the project's default_auto_deploy; the tasks column itself still defaults to 'off' at the
37122
37085
  // SQL level for any direct-insert path that bypasses createTask entirely.
37123
37086
  autoDeploy: mrrlinAutoDeploySchema.optional(),
37124
- // Input-optional so existing createTask call sites need no churn; createTask fills 'code' for the
37125
- // INSERT (the pg column is NOT NULL, so a concrete value is required, not undefined→NULL).
37126
37087
  domain: mrrlinTaskDomainSchema.optional(),
37127
37088
  autonomyLevel: mrrlinAutonomyLevelSchema.default("human_review"),
37128
37089
  autonomyReason: external_exports.string().min(1).max(240).default("Needs human review before automation."),
@@ -37144,6 +37105,17 @@ var mrrlinTaskCreateSchema = external_exports.object({
37144
37105
  type: mrrlinTaskTypeSchema.default("task"),
37145
37106
  verificationDueAt: external_exports.string().datetime().nullable().optional()
37146
37107
  });
37108
+ function refineTaskCreateDomain(data, ctx) {
37109
+ if (data.domain === void 0 && data.parentTaskId == null) {
37110
+ ctx.addIssue({
37111
+ code: external_exports.ZodIssueCode.custom,
37112
+ path: ["domain"],
37113
+ message: "domain is required unless parentTaskId is set (a follow-up inherits its parent's domain)"
37114
+ });
37115
+ }
37116
+ }
37117
+ var mrrlinTaskCreateSchema = mrrlinTaskCreateShapeSchema.superRefine(refineTaskCreateDomain);
37118
+ var mrrlinTaskCreateWithoutIdSchema = mrrlinTaskCreateShapeSchema.omit({ id: true }).superRefine(refineTaskCreateDomain);
37147
37119
  var mrrlinTaskUpdateSchema = external_exports.object({
37148
37120
  actor: external_exports.string().min(1).optional(),
37149
37121
  assignee: mrrlinTaskAssigneeSchema.optional(),
@@ -37261,13 +37233,19 @@ var mrrlinWikiPageSearchQuerySchema = external_exports.object({
37261
37233
  q: external_exports.string().min(2)
37262
37234
  });
37263
37235
  var mrrlinWikiPageSearchResultSchema = external_exports.object({
37236
+ ageDays: external_exports.number().nullable(),
37264
37237
  folder: external_exports.string().min(1),
37265
37238
  id: mrrlinWikiPageIdSchema,
37239
+ // Task-relative; always null from a generic (non-task-scoped) search — resolved only where
37240
+ // a caller has task context (director-loop retrieve()), never at this layer or in the API (D14).
37241
+ memoryClass: external_exports.enum(["normative", "historical"]),
37266
37242
  path: external_exports.string().min(1),
37243
+ precedence: external_exports.union([external_exports.literal(0), external_exports.literal(1), external_exports.literal(2), external_exports.literal(3)]).nullable(),
37267
37244
  preview: external_exports.string(),
37268
37245
  rank: external_exports.number(),
37269
37246
  snippet: external_exports.string(),
37270
- title: external_exports.string().min(1)
37247
+ title: external_exports.string().min(1),
37248
+ updatedAt: external_exports.string().datetime().nullable()
37271
37249
  });
37272
37250
  var wikiKnowledgeChunkSchema = external_exports.object({
37273
37251
  chunk_text: external_exports.string().min(1),
@@ -38093,6 +38071,210 @@ var inboxItemsMarkReadResponseSchema = external_exports.object({
38093
38071
  var inboxItemResponseSchema = external_exports.object({ data: mrrlinInboxItemSchema });
38094
38072
  var inboxItemListResponseSchema = external_exports.object({ data: external_exports.array(mrrlinInboxItemSchema) });
38095
38073
 
38074
+ // ../../packages/codex-client/dist/render-context-bundle.js
38075
+ var DEFAULT_SPEC_BUDGET_CHARS = 6e3;
38076
+ var DEFAULT_ARTIFACT_PAYLOAD_BUDGET_CHARS = 400;
38077
+ function renderContextBundle(input) {
38078
+ const sections = [];
38079
+ sections.push(renderTaskSummary(input.task));
38080
+ sections.push(renderSpec(input.specMarkdown, input.compact));
38081
+ const { keptRuns, keptArtifacts, keptAutonomy, anyHistoricalDropped, droppedHandles } = applyHistoricalBudgetIfNeeded(input);
38082
+ sections.push(renderRunHistory(keptRuns, input.openRunId, input.compact, anyHistoricalDropped));
38083
+ sections.push(renderOpenRun(input.openRunId));
38084
+ sections.push(renderArtifacts(keptArtifacts, input.omittedArtifactCount ?? 0, input.compact, anyHistoricalDropped, droppedHandles));
38085
+ sections.push(renderAutonomyEvents(keptAutonomy, input.compact, anyHistoricalDropped));
38086
+ return sections.join("\n\n");
38087
+ }
38088
+ function renderTaskSummary(task) {
38089
+ const lines2 = [
38090
+ "## Task (normative)",
38091
+ `id: ${task.id}`,
38092
+ `title: ${task.title}`,
38093
+ `autonomy: ${task.autonomyLevel} (${task.autonomyReason})`
38094
+ ];
38095
+ if (task.notes)
38096
+ lines2.push(`description: ${task.notes}`);
38097
+ if (task.latestJudgement) {
38098
+ lines2.push(`latest judgement (${task.latestJudgementKind ?? "unknown"}): ${task.latestJudgement}`);
38099
+ }
38100
+ return lines2.join("\n");
38101
+ }
38102
+ function renderSpec(specMarkdown, compact) {
38103
+ if (!specMarkdown)
38104
+ return "## Spec (normative)\n(none)";
38105
+ if (!compact)
38106
+ return `## Spec (normative)
38107
+ ${specMarkdown}`;
38108
+ const budget = compact.specBudgetChars ?? DEFAULT_SPEC_BUDGET_CHARS;
38109
+ if (specMarkdown.length <= budget)
38110
+ return `## Spec (normative)
38111
+ ${specMarkdown}`;
38112
+ return `## Spec (normative)
38113
+ ${compactSpecMarkdown(specMarkdown, budget, compact.projectSlug ?? null, compact.specWikiPageId ?? null)}`;
38114
+ }
38115
+ function compactSpecMarkdown(markdown, totalBudget, projectSlug, specWikiPageId) {
38116
+ const lines2 = markdown.split(/\r?\n/);
38117
+ const sections = [{ heading: "", body: [] }];
38118
+ for (const line of lines2) {
38119
+ if (/^#{1,6}\s/.test(line)) {
38120
+ sections.push({ heading: line, body: [] });
38121
+ } else {
38122
+ sections[sections.length - 1]?.body.push(line);
38123
+ }
38124
+ }
38125
+ const sectionCount = Math.max(1, sections.length);
38126
+ const perSectionBudget = Math.max(120, Math.floor(totalBudget / sectionCount));
38127
+ const out = [];
38128
+ for (const section of sections) {
38129
+ const bodyText = section.body.join("\n").replace(/^\s+|\s+$/g, "");
38130
+ if (section.heading)
38131
+ out.push(section.heading);
38132
+ if (!bodyText)
38133
+ continue;
38134
+ if (bodyText.length <= perSectionBudget) {
38135
+ out.push(bodyText);
38136
+ } else {
38137
+ const kept = bodyText.slice(0, perSectionBudget);
38138
+ const omitted = bodyText.length - perSectionBudget;
38139
+ out.push(`${kept}
38140
+ \u2026(${omitted} chars omitted; retrieve full spec for the rest)`);
38141
+ }
38142
+ }
38143
+ if (projectSlug && specWikiPageId) {
38144
+ out.push("");
38145
+ out.push(`> Compact spec. Source: mrrlin://wiki/${projectSlug}/${specWikiPageId} \u2014 call retrieve_context with this handle before any irreversible decision.`);
38146
+ } else {
38147
+ out.push("");
38148
+ out.push("> Compact spec. Retrieve the Specs/{id} wiki page via get_wiki_page before any irreversible decision.");
38149
+ }
38150
+ return out.join("\n");
38151
+ }
38152
+ function renderRunHistory(runs, openRunId, compact, anyHistoricalDropped) {
38153
+ if (runs.length === 0)
38154
+ return "## Execution-run history (historical)\n(none)";
38155
+ const lines2 = ["## Execution-run history (historical)"];
38156
+ for (const run3 of runs) {
38157
+ const marker = run3.id === openRunId ? " (open)" : "";
38158
+ lines2.push(`- ${run3.id} [${run3.status}] created ${run3.createdAt}${marker}`);
38159
+ }
38160
+ if (compact && anyHistoricalDropped) {
38161
+ lines2.push("> Compact execution-run history. Use retrieve_context with a `mrrlin://run/{projectSlug}/{runId}/artifacts` handle (substitute the slug and run id from the lines above) to load a run's full artifact history.");
38162
+ }
38163
+ return lines2.join("\n");
38164
+ }
38165
+ function renderOpenRun(openRunId) {
38166
+ return `## Open run (normative)
38167
+ ${openRunId ?? "(none)"}`;
38168
+ }
38169
+ function renderArtifacts(artifacts, omittedCount, compact, anyHistoricalDropped, droppedHandles) {
38170
+ if (artifacts.length === 0 && omittedCount === 0)
38171
+ return "## Artifacts (historical)\n(none)";
38172
+ const lines2 = ["## Artifacts (historical)"];
38173
+ if (omittedCount > 0) {
38174
+ lines2.push(`(+${omittedCount} older artifact${omittedCount === 1 ? "" : "s"} omitted)`);
38175
+ }
38176
+ let truncatedAny = false;
38177
+ for (const artifact of artifacts) {
38178
+ if (artifact.kind === "file") {
38179
+ const p = artifact.payload;
38180
+ if (p.filename && p.stableUrl) {
38181
+ lines2.push(`- ${artifact.createdAt} [file] ${p.filename} (${p.class ?? "?"}, ${p.sizeBytes ?? "?"} bytes) ${p.stableUrl}`);
38182
+ continue;
38183
+ }
38184
+ }
38185
+ const payloadText = JSON.stringify(artifact.payload);
38186
+ const budget = compact?.artifactPayloadBudgetChars ?? DEFAULT_ARTIFACT_PAYLOAD_BUDGET_CHARS;
38187
+ const willTruncate = !!compact && payloadText.length > budget;
38188
+ const rendered = willTruncate ? `${payloadText.slice(0, budget)}\u2026(${payloadText.length - budget} chars omitted)` : payloadText;
38189
+ if (willTruncate)
38190
+ truncatedAny = true;
38191
+ lines2.push(`- ${artifact.createdAt} [${artifact.kind}] run=${artifact.runId} ${rendered}`);
38192
+ }
38193
+ if (compact && (omittedCount > 0 || truncatedAny || anyHistoricalDropped)) {
38194
+ lines2.push("> Compact artifacts. Use retrieve_context with a `mrrlin://run/{projectSlug}/{runId}/artifacts` handle (substitute the slug and run id from the lines above) to load a run's full artifact history.");
38195
+ }
38196
+ if (compact && droppedHandles.length > 0) {
38197
+ lines2.push(`> Dropped by budget, retrievable via: ${droppedHandles.map((h) => `\`${h}\``).join(", ")}`);
38198
+ }
38199
+ return lines2.join("\n");
38200
+ }
38201
+ function renderAutonomyEvents(events, compact, anyHistoricalDropped) {
38202
+ if (events.length === 0)
38203
+ return "## Autonomy history (historical)\n(none)";
38204
+ const lines2 = ["## Autonomy history (historical)"];
38205
+ for (const event of events) {
38206
+ lines2.push(`- ${event.createdAt} ${event.autonomyLevel ?? "?"} (${event.autonomyReason ?? ""})`);
38207
+ }
38208
+ if (compact && anyHistoricalDropped) {
38209
+ lines2.push("> Compact autonomy history. Use retrieve_context with a `mrrlin://run/{projectSlug}/{runId}/artifacts` handle (substitute the slug and run id from the lines above) to load a run's full artifact history.");
38210
+ }
38211
+ return lines2.join("\n");
38212
+ }
38213
+ function applyHistoricalBudgetIfNeeded(input) {
38214
+ if (!input.compact?.budget) {
38215
+ return {
38216
+ keptRuns: input.runs,
38217
+ keptArtifacts: input.artifacts,
38218
+ keptAutonomy: input.autonomyEvents,
38219
+ anyHistoricalDropped: false,
38220
+ droppedHandles: []
38221
+ };
38222
+ }
38223
+ const candidates = [];
38224
+ for (const run3 of input.runs) {
38225
+ const line = `- ${run3.id} [${run3.status}] created ${run3.createdAt}`;
38226
+ candidates.push({
38227
+ kind: "run",
38228
+ runId: run3.id,
38229
+ size: line.length,
38230
+ memoryClass: "historical",
38231
+ precedence: 3,
38232
+ handle: input.compact.projectSlug ? `mrrlin://run/${input.compact.projectSlug}/${run3.id}/artifacts` : null,
38233
+ source: "run_history"
38234
+ });
38235
+ }
38236
+ for (const artifact of input.artifacts) {
38237
+ const line = `- ${artifact.createdAt} [${artifact.kind}] run=${artifact.runId} ${JSON.stringify(artifact.payload)}`;
38238
+ candidates.push({
38239
+ kind: "artifact",
38240
+ artifactId: artifact.id,
38241
+ size: line.length,
38242
+ memoryClass: "historical",
38243
+ precedence: 3,
38244
+ handle: input.compact.projectSlug ? `mrrlin://run/${input.compact.projectSlug}/${artifact.runId}/artifacts` : null,
38245
+ source: "artifacts"
38246
+ });
38247
+ }
38248
+ for (const event of input.autonomyEvents) {
38249
+ const line = `- ${event.createdAt} ${event.autonomyLevel ?? "?"} (${event.autonomyReason ?? ""})`;
38250
+ candidates.push({
38251
+ kind: "autonomy",
38252
+ autonomyEventId: event.id,
38253
+ size: line.length,
38254
+ memoryClass: "historical",
38255
+ precedence: 3,
38256
+ handle: null,
38257
+ source: "autonomy"
38258
+ });
38259
+ }
38260
+ const { kept, dropped } = applyBudget(candidates, input.compact.budget);
38261
+ const keptRunIds = new Set(kept.filter((c) => c.kind === "run").map((c) => c.runId));
38262
+ const keptArtifactIds = new Set(kept.filter((c) => c.kind === "artifact").map((c) => c.artifactId));
38263
+ const keptAutonomyIds = new Set(kept.filter((c) => c.kind === "autonomy").map((c) => c.autonomyEventId));
38264
+ const droppedHandles = [];
38265
+ for (const item of dropped) {
38266
+ if (item.handle)
38267
+ droppedHandles.push(item.handle);
38268
+ }
38269
+ return {
38270
+ keptRuns: input.runs.filter((r) => keptRunIds.has(r.id)),
38271
+ keptArtifacts: input.artifacts.filter((a) => keptArtifactIds.has(a.id)),
38272
+ keptAutonomy: input.autonomyEvents.filter((e) => keptAutonomyIds.has(e.id)),
38273
+ anyHistoricalDropped: dropped.length > 0,
38274
+ droppedHandles
38275
+ };
38276
+ }
38277
+
38096
38278
  // ../../packages/client/dist/agent-token-provider.js
38097
38279
  var AgentTokenProviderError = class extends Error {
38098
38280
  code;
@@ -39460,7 +39642,7 @@ function startCodexProbe(deps = {}) {
39460
39642
  }
39461
39643
 
39462
39644
  // src/_generated/version.ts
39463
- var PKG_VERSION = "0.3.57";
39645
+ var PKG_VERSION = "0.3.60";
39464
39646
 
39465
39647
  // src/api-base-url-migrate.ts
39466
39648
  var import_node_fs2 = __toESM(require("node:fs"), 1);
@@ -40276,13 +40458,26 @@ var HEADS_BY_DOMAIN = {
40276
40458
  content: ["plan-reviewer", "scope-analyst", "content-editor", "compliance-reviewer"]
40277
40459
  };
40278
40460
  var SPEC_HEADS = HEADS_BY_DOMAIN.code;
40461
+ var CODE_HEADS_BY_IMPACT = {
40462
+ high: HEADS_BY_DOMAIN.code,
40463
+ medium: ["plan-reviewer", "security-analyst", "scope-analyst"],
40464
+ low: ["plan-reviewer", "scope-analyst"]
40465
+ };
40279
40466
  function normalizeDomain(value) {
40280
40467
  if (typeof value !== "string") return DEFAULT_DOMAIN;
40281
40468
  const v = value.trim().toLowerCase();
40282
40469
  return DOMAINS.includes(v) ? v : DEFAULT_DOMAIN;
40283
40470
  }
40284
- function resolveHeads(domain2) {
40285
- return [...HEADS_BY_DOMAIN[normalizeDomain(domain2)]];
40471
+ function normalizeImpact(value) {
40472
+ if (typeof value !== "string") return null;
40473
+ const v = value.trim().toLowerCase();
40474
+ return v === "low" || v === "medium" || v === "high" ? v : null;
40475
+ }
40476
+ function resolveHeads(domain2, impact) {
40477
+ const d = normalizeDomain(domain2);
40478
+ if (d !== "code") return [...HEADS_BY_DOMAIN[d]];
40479
+ const i = normalizeImpact(impact) ?? "high";
40480
+ return [...CODE_HEADS_BY_IMPACT[i]];
40286
40481
  }
40287
40482
  var PERSONAS_BY_DOMAIN = {
40288
40483
  seo: "seo-writer",
@@ -40729,7 +40924,7 @@ function loadPersona(name) {
40729
40924
  personaCache.set(name, content);
40730
40925
  return content;
40731
40926
  }
40732
- function specHeadIntentBlock(taskId, title, seed) {
40927
+ function specHeadIntentBlock(taskId, title, seed, impact, adjudicated) {
40733
40928
  const identity = taskId ? `--- TASK IDENTITY (the spec MUST be about THIS task; if its scope or direction contradicts this identity, raise a \`scope\` issue instead of steering the spec elsewhere) ---
40734
40929
  TASK ${taskId}: ${title ?? ""}
40735
40930
 
@@ -40738,7 +40933,15 @@ TASK ${taskId}: ${title ?? ""}
40738
40933
  ${seed}
40739
40934
 
40740
40935
  ` : "";
40741
- return `${identity}${intent}`;
40936
+ const impactLine = impact ? `--- IMPACT: ${impact} ---
40937
+
40938
+ ` : "";
40939
+ const adjudicatedBlock = adjudicated && adjudicated.length > 0 ? `--- ADJUDICATED \u2014 the operator already decided these; do not re-raise without new information ---
40940
+ ${adjudicated.map((q) => `- Q: ${q.body}
40941
+ ${q.status === "rejected" ? "(rejected" + (q.answer ? `: ${q.answer}` : "") + ")" : `A: ${q.answer ?? "(acknowledged, no answer recorded)"}`}`).join("\n")}
40942
+
40943
+ ` : "";
40944
+ return `${identity}${intent}${impactLine}${adjudicatedBlock}`;
40742
40945
  }
40743
40946
  function reviserIdentityInstructions(taskId, title) {
40744
40947
  const lock = taskId ? `This spec gates task ${taskId}${title ? ` ("${title}")` : ""}. You MUST NOT change the spec's task identity or heading, retarget it to a different task id, or invert its direction (e.g. live-submission vs preparation-only). Preserve the leading \`task: ${taskId}\` frontmatter EXACTLY; never change it to another task id. Address ONLY the accepted issues.
@@ -40749,7 +40952,7 @@ function reviserIdentityInstructions(taskId, title) {
40749
40952
  function buildSpecGateDeps(args) {
40750
40953
  const { client, projectSlug, specPageId, cwd, codexExecutable, extraHeadCount = 0 } = args;
40751
40954
  const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ?? 5);
40752
- const maxHeadCalls = rounds * (4 + extraHeadCount + 1);
40955
+ const maxHeadCalls = 2 * rounds * (4 + extraHeadCount + 1);
40753
40956
  const timeoutMs = Number(process.env["MRRLIN_CONSENSUS_HEAD_TIMEOUT_MS"] ?? 15e4);
40754
40957
  let intentPromise = null;
40755
40958
  function loadIntent() {
@@ -40777,7 +40980,21 @@ function buildSpecGateDeps(args) {
40777
40980
  resolvedSeed = "(No operator statement on record for this task. Derived from the task's own notes \u2014 the following acceptance criteria were extracted verbatim:)\n\n" + parsed.items.map((c) => `- ${c}`).join("\n");
40778
40981
  }
40779
40982
  }
40780
- return { taskId, seed: resolvedSeed, title: task.title ?? "", domain: task.domain, notes };
40983
+ const taskInboxItems = await client.listInboxItems(projectSlug, { taskId });
40984
+ const adjudicated = taskInboxItems.filter((item) => item.origin === "gate" && (item.status === "acknowledged" || item.status === "rejected")).map((item) => ({
40985
+ body: item.body,
40986
+ answer: item.answer,
40987
+ status: item.status
40988
+ }));
40989
+ return {
40990
+ taskId,
40991
+ seed: resolvedSeed,
40992
+ title: task.title ?? "",
40993
+ domain: task.domain,
40994
+ notes,
40995
+ impact: task.impact,
40996
+ adjudicated
40997
+ };
40781
40998
  })();
40782
40999
  }
40783
41000
  return intentPromise;
@@ -40802,8 +41019,8 @@ function buildSpecGateDeps(args) {
40802
41019
  },
40803
41020
  async runHead(personaName, artifact, dismissed) {
40804
41021
  const persona = loadPersona(personaName);
40805
- const { taskId, title, seed } = await loadIntent();
40806
- const intentBlock = specHeadIntentBlock(taskId, title, seed);
41022
+ const { taskId, title, seed, impact, adjudicated } = await loadIntent();
41023
+ const intentBlock = specHeadIntentBlock(taskId, title, seed, impact, adjudicated);
40807
41024
  return runCodexExec(
40808
41025
  {
40809
41026
  prompt: `Review this spec against the operator's intent.
@@ -40913,7 +41130,7 @@ function buildCodeGateDeps(args) {
40913
41130
  const { client, projectSlug, baseRef, cwd, codexExecutable, extraHeads } = args;
40914
41131
  const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ?? 5);
40915
41132
  const heads = ["code-reviewer", ...extraHeads ?? []];
40916
- const maxHeadCalls = rounds * (heads.length + 1);
41133
+ const maxHeadCalls = 2 * rounds * (heads.length + 1);
40917
41134
  const headTimeoutMs = Number(process.env["MRRLIN_CONSENSUS_HEAD_TIMEOUT_MS"] ?? 15e4);
40918
41135
  const reviserTimeoutMs = Number(process.env["MRRLIN_CODEGATE_REVISER_TIMEOUT_MS"] ?? 6e5);
40919
41136
  const gitArgs = { cwd, baseRef, run: defaultRunCmd };
@@ -40997,12 +41214,14 @@ async function buildDeliverableGateDeps(args) {
40997
41214
  const page = await client.getWikiPage(projectSlug, deliverablePageId);
40998
41215
  const taskId = page.taskId ?? null;
40999
41216
  let domain2 = "code";
41217
+ let impact;
41000
41218
  let taskTitle = "";
41001
41219
  let specPageId = null;
41002
41220
  let criteria = [];
41003
41221
  if (taskId) {
41004
41222
  const task = await client.getTask(projectSlug, taskId);
41005
41223
  domain2 = normalizeDomain(task?.domain);
41224
+ impact = task?.impact;
41006
41225
  taskTitle = task?.title ?? "";
41007
41226
  if (task?.specWikiPageId) {
41008
41227
  specPageId = task.specWikiPageId;
@@ -41010,8 +41229,8 @@ async function buildDeliverableGateDeps(args) {
41010
41229
  criteria = parseAcceptanceCriteria(specPage.markdownBody)?.items ?? [];
41011
41230
  }
41012
41231
  }
41013
- const heads = resolveHeads(domain2);
41014
- const maxHeadCalls = rounds * (heads.length + 1);
41232
+ const heads = resolveHeads(domain2, impact);
41233
+ const maxHeadCalls = 2 * rounds * (heads.length + 1);
41015
41234
  const criteriaBlock = renderCriteriaBlock(criteria);
41016
41235
  return {
41017
41236
  heads,
@@ -41253,6 +41472,17 @@ var SPECS_FOLDER = "Specs";
41253
41472
  var DRAFT_SPEC_TLDR_MAX_CHARS = 300;
41254
41473
  var DRAFT_SPEC_PERSONA = "spec-author";
41255
41474
  var PAGE_ID_MAX_ATTEMPTS = 5;
41475
+ function resolveSpecBudget(impact) {
41476
+ const tier = impact?.toLowerCase();
41477
+ switch (tier) {
41478
+ case "high":
41479
+ return { maxRequirements: 12, maxChars: 25e3 };
41480
+ case "medium":
41481
+ return { maxRequirements: 6, maxChars: 12e3 };
41482
+ default:
41483
+ return { maxRequirements: 3, maxChars: 6e3 };
41484
+ }
41485
+ }
41256
41486
  function defaultSleep(ms) {
41257
41487
  return new Promise((resolve) => setTimeout(resolve, ms));
41258
41488
  }
@@ -41265,7 +41495,7 @@ function enforceSpecOutputContract(markdown, taskId, title) {
41265
41495
  ${body}`;
41266
41496
  }
41267
41497
  let tldrChars = 0;
41268
- const tldrMatch = body.match(/(^## Summary\s*\n)([\s\S]*?)(?=\n## |$)/im);
41498
+ const tldrMatch = body.match(/(^## TL;DR\s*\n)([\s\S]*?)(?=\n## |$)/im);
41269
41499
  if (tldrMatch && tldrMatch.index !== void 0) {
41270
41500
  const heading = tldrMatch[1] ?? "";
41271
41501
  const section = (tldrMatch[2] ?? "").trim();
@@ -41289,6 +41519,7 @@ function nextWikiPageId(pages) {
41289
41519
  return `WK-${max + 1}`;
41290
41520
  }
41291
41521
  function buildDraftSpecPrompt(task, constitution) {
41522
+ const budget = resolveSpecBudget(task.impact);
41292
41523
  const lines2 = [
41293
41524
  `Write a complete implementation spec for the task below.`,
41294
41525
  ``,
@@ -41310,11 +41541,19 @@ function buildDraftSpecPrompt(task, constitution) {
41310
41541
  }
41311
41542
  lines2.push(
41312
41543
  `--- OUTPUT CONTRACT (strict) ---`,
41313
- `Return ONLY the full spec markdown, no commentary. Required structure:`,
41544
+ `Return ONLY the full spec markdown, no commentary. Required structure, in this order:`,
41314
41545
  `- First line heading: \`# ${task.id} \u2014 ${task.title}\``,
41315
- `- Sections: Problem, Scope, Non-goals, Acceptance criteria, Verification.`,
41316
- `- Final section \`## Summary\`: a plain-English TL;DR of what will be done and how it`,
41317
- ` will be verified, at most ${DRAFT_SPEC_TLDR_MAX_CHARS} characters.`
41546
+ `- \`## TL;DR\``,
41547
+ `- One or more \`### Requirement: <name>\` sections, each stating what the change SHALL do.`,
41548
+ ` Each requirement has one or more \`#### Scenario: <name>\` sub-sections using WHEN/THEN`,
41549
+ ` (optionally AND) to describe a concrete situation and its expected outcome.`,
41550
+ `- \`## Acceptance criteria\`: a markdown checklist (\`- [ ] ...\`) of concrete, verifiable`,
41551
+ ` conditions \u2014 this is the machine-usable yardstick the review scores against.`,
41552
+ `- \`## Verification\`: the exact tests, commands, or manual checks that prove each`,
41553
+ ` acceptance criterion holds.`,
41554
+ ``,
41555
+ `The \`## TL;DR\` section MUST be at most ${DRAFT_SPEC_TLDR_MAX_CHARS} characters.`,
41556
+ `The spec MUST contain at most ${budget.maxRequirements} acceptance-criteria items and no more than ${budget.maxChars} characters total.`
41318
41557
  );
41319
41558
  return lines2.join("\n");
41320
41559
  }
@@ -42316,6 +42555,13 @@ function readPositiveIntEnv(name, fallback) {
42316
42555
  function delayMs(ms) {
42317
42556
  return new Promise((resolve) => setTimeout(resolve, ms));
42318
42557
  }
42558
+ function executionBundleBudget() {
42559
+ if ((process.env.MRRLIN_RETRIEVAL_BUDGET_DISABLE ?? "").trim() === "1") return void 0;
42560
+ return {
42561
+ totalBytes: readPositiveIntEnv("MRRLIN_RETRIEVAL_BUDGET_BUNDLE_CHARS", 15500),
42562
+ roundTripCap: 0
42563
+ };
42564
+ }
42319
42565
  function execTurnIdleTimeoutMs() {
42320
42566
  return readPositiveIntEnv("MRRLIN_DIRECTOR_BRIDGE_EXEC_TURN_IDLE_TIMEOUT_SECONDS", DEFAULT_EXEC_TURN_IDLE_TIMEOUT_SECONDS) * 1e3;
42321
42567
  }
@@ -42836,7 +43082,8 @@ async function driveRun(deps, projectSlug, runId) {
42836
43082
  ...context,
42837
43083
  compact: {
42838
43084
  projectSlug,
42839
- specWikiPageId: task.specWikiPageId ?? null
43085
+ specWikiPageId: task.specWikiPageId ?? null,
43086
+ budget: executionBundleBudget()
42840
43087
  }
42841
43088
  } : context
42842
43089
  );
@@ -43897,6 +44144,46 @@ function createBoundedSemaphore(slots) {
43897
44144
  };
43898
44145
  }
43899
44146
 
44147
+ // src/consensus/escalation.ts
44148
+ function diagnoseStall(history, configuredRounds) {
44149
+ const n = history.length;
44150
+ const nonRespPerRound = history.map((h) => h.headOutcomes.filter((o) => !o.responded).length);
44151
+ const infraRoundCount = nonRespPerRound.filter((c) => c >= 2).length;
44152
+ const infraQualifies = infraRoundCount > n / 2;
44153
+ if (infraQualifies) {
44154
+ const roundIndices = [];
44155
+ nonRespPerRound.forEach((c, i) => {
44156
+ if (c >= 2) roundIndices.push(i + 1);
44157
+ });
44158
+ const evidence2 = `INFRA basis: \u22652 non\u2011responding heads in ${infraRoundCount} of ${n} rounds (rounds ${roundIndices.join(",")})`;
44159
+ let grant = 3;
44160
+ const headroom = 2 * configuredRounds - n;
44161
+ grant = Math.max(1, Math.min(grant, headroom));
44162
+ return { diagnosis: "INFRA", grantedRounds: grant, evidence: evidence2 };
44163
+ }
44164
+ if (n >= 2) {
44165
+ const counts = history.map((h) => h.accepted.length);
44166
+ const last = counts[n - 1];
44167
+ const secondLast = counts[n - 2];
44168
+ const lastFall = last < secondLast;
44169
+ if (lastFall) {
44170
+ let falls = 1;
44171
+ if (n >= 3 && secondLast < counts[n - 3]) {
44172
+ falls++;
44173
+ }
44174
+ let grant = 2 + falls;
44175
+ if (grant > 5) grant = 5;
44176
+ const headroom = 2 * configuredRounds - n;
44177
+ grant = Math.max(1, Math.min(grant, headroom));
44178
+ const seqDesc = counts.map((c, i) => `round${i + 1}:${c}`).join(" \u2192 ");
44179
+ const evidence2 = `SUBSTANCE basis: accepted count fell (${seqDesc})`;
44180
+ return { diagnosis: "SUBSTANCE", grantedRounds: grant, evidence: evidence2 };
44181
+ }
44182
+ }
44183
+ const evidence = n === 0 ? "empty history" : `no infra or substance basis found in ${n} rounds`;
44184
+ return { diagnosis: "AMBIGUITY", grantedRounds: 0, evidence };
44185
+ }
44186
+
43900
44187
  // src/consensus/loop.ts
43901
44188
  async function runConsensus(input) {
43902
44189
  const sem = createBoundedSemaphore(input.concurrency ?? input.heads.length);
@@ -43904,9 +44191,12 @@ async function runConsensus(input) {
43904
44191
  let artifact = input.artifact;
43905
44192
  let calls = 0;
43906
44193
  const dismissedFwd = [];
43907
- for (let round = 1; round <= input.rounds; round++) {
44194
+ let effectiveRounds = input.rounds;
44195
+ let escalated = false;
44196
+ let decision;
44197
+ for (let round = 1; round <= effectiveRounds; round++) {
43908
44198
  if (calls + input.heads.length > input.maxHeadCalls) {
43909
- return { outcome: "CONSENSUS_BLOCKED", rounds: round - 1, finalArtifact: artifact, history };
44199
+ return { outcome: "CONSENSUS_BLOCKED", rounds: round - 1, finalArtifact: artifact, history, ...decision ? { escalation: decision } : {} };
43910
44200
  }
43911
44201
  const dismissedBlock = dismissedFwd.length ? `PREVIOUSLY DISMISSED (do not re-raise unless new information):
43912
44202
  ${dismissedFwd.slice(-20).join("\n")}` : "";
@@ -43930,12 +44220,19 @@ ${dismissedFwd.slice(-20).join("\n")}` : "";
43930
44220
  if (responding.length === 0) {
43931
44221
  return { outcome: "CONSENSUS_UNAVAILABLE", rounds: round, finalArtifact: artifact, history };
43932
44222
  }
44223
+ const headOutcomes = input.heads.map((persona) => ({
44224
+ persona,
44225
+ responded: false
44226
+ }));
43933
44227
  const issues = [];
43934
- const verdicts = [];
43935
44228
  for (const { idx, res } of responding) {
43936
44229
  const parsed = parseVerdict(res.text);
43937
- verdicts.push(parsed.verdict);
43938
44230
  parsed.issues.forEach((iss, n) => issues.push({ ...iss, id: `r${round}-h${idx}-${n}` }));
44231
+ const outcome = headOutcomes[idx];
44232
+ if (outcome) {
44233
+ outcome.responded = true;
44234
+ outcome.verdict = parsed.verdict;
44235
+ }
43939
44236
  if (parsed.verdict !== "APPROVE" && parsed.issues.length === 0 && parsed.parseFallbacks.length === 0) {
43940
44237
  issues.push({
43941
44238
  category: "ambiguity",
@@ -43947,7 +44244,7 @@ ${dismissedFwd.slice(-20).join("\n")}` : "";
43947
44244
  let adj;
43948
44245
  if (issues.length > 0) {
43949
44246
  if (calls + 1 > input.maxHeadCalls) {
43950
- return { outcome: "CONSENSUS_BLOCKED", rounds: round, finalArtifact: artifact, history };
44247
+ return { outcome: "CONSENSUS_BLOCKED", rounds: round, finalArtifact: artifact, history, ...decision ? { escalation: decision } : {} };
43951
44248
  }
43952
44249
  calls++;
43953
44250
  try {
@@ -43958,17 +44255,25 @@ ${dismissedFwd.slice(-20).join("\n")}` : "";
43958
44255
  } else {
43959
44256
  adj = { adjudicatedVerdict: "APPROVE", accepted: [], dismissed: [] };
43960
44257
  }
43961
- history.push({ round, verdicts, accepted: adj.accepted, dismissed: adj.dismissed });
44258
+ history.push({ round, accepted: adj.accepted, dismissed: adj.dismissed, headOutcomes });
43962
44259
  adj.dismissed.forEach((d) => dismissedFwd.push(`- ${d.id}: ${d.reason}`));
43963
44260
  if (adj.accepted.length === 0) {
43964
- return { outcome: "CONVERGED", rounds: round, finalArtifact: artifact, history };
44261
+ return { outcome: "CONVERGED", rounds: round, finalArtifact: artifact, history, ...decision ? { escalation: decision } : {} };
43965
44262
  }
43966
- if (round === input.rounds) {
43967
- return { outcome: "CONSENSUS_BLOCKED", rounds: round, finalArtifact: artifact, history };
44263
+ if (round === effectiveRounds) {
44264
+ if (!decision) {
44265
+ decision = diagnoseStall(history, input.rounds);
44266
+ }
44267
+ if (decision.grantedRounds > 0 && !escalated) {
44268
+ escalated = true;
44269
+ effectiveRounds = Math.min(effectiveRounds + decision.grantedRounds, input.rounds * 2);
44270
+ } else {
44271
+ return { outcome: "CONSENSUS_BLOCKED", rounds: round, finalArtifact: artifact, history, ...decision ? { escalation: decision } : {} };
44272
+ }
43968
44273
  }
43969
44274
  artifact = await input.revise(artifact, adj.accepted);
43970
44275
  }
43971
- return { outcome: "CONSENSUS_BLOCKED", rounds: input.rounds, finalArtifact: artifact, history };
44276
+ return { outcome: "CONSENSUS_BLOCKED", rounds: effectiveRounds, finalArtifact: artifact, history };
43972
44277
  }
43973
44278
 
43974
44279
  // src/consensus/deliverable-gate.ts
@@ -44018,7 +44323,12 @@ async function runDeliverableGate(_target, deps) {
44018
44323
  whyBlocking: `reviewer raised a ${issue2.category} issue`,
44019
44324
  category: issue2.category
44020
44325
  }));
44021
- return clarifications.length > 0 ? { outcome: "CONSENSUS_BLOCKED", rounds: result.rounds, clarifications } : { outcome: "CONSENSUS_BLOCKED", rounds: result.rounds };
44326
+ return {
44327
+ outcome: "CONSENSUS_BLOCKED",
44328
+ rounds: result.rounds,
44329
+ ...clarifications.length > 0 ? { clarifications } : {},
44330
+ ...result.escalation ? { escalation: result.escalation } : {}
44331
+ };
44022
44332
  }
44023
44333
  return { outcome: result.outcome, rounds: result.rounds };
44024
44334
  }
@@ -48907,8 +49217,8 @@ var DEFAULT_DIRECTOR_BRIDGE_ALLOWED_ORIGINS = [
48907
49217
  "http://[::1]:*",
48908
49218
  // Staging Vercel team migrated 2026-07-28 (see docs/reference/deployments.md). The prior
48909
49219
  // fnnzzz-fnnzzzs-projects web alias was dropped once no operator was left on an old build.
48910
- // mrrlin2 hit usage limits and was replaced by mrrlin3 on 2026-08-11.
48911
- "https://mrrlin-web-mrrlin2.vercel.app",
49220
+ // mrrlin2 hit usage limits and was replaced by mrrlin3 on 2026-08-11; the mrrlin2 web alias
49221
+ // was dropped the same day since no operator was left on that build either.
48912
49222
  "https://mrrlin-web-mrrlin3.vercel.app",
48913
49223
  "https://app.mrrlin.dev",
48914
49224
  "https://mrrlin-web.vercel.app"
@@ -49148,6 +49458,7 @@ function buildThreadStartParams(msg, config2, desiredModel) {
49148
49458
  "- Keep behavior contracts in the spec; implementation details belong in `## Implementation Plan`.",
49149
49459
  "- Apply this format ONLY to Specs/{id} pages; do not retro-fit it onto other wiki page types.",
49150
49460
  "- Spec consensus runs ASYNCHRONOUSLY. Call `run_spec_consensus` for a `Specs/{id}` page; it returns IMMEDIATELY. On `already_converged`, the spec is already marked ready \u2014 proceed straight to `create_execution_run`; do NOT call `mark_spec_ready` and do NOT re-run consensus. On `queued` or `already_running`, tell the user it was launched and END your turn \u2014 do NOT poll or call it again; the result arrives in the inbox (an `info` item on CONVERGED, a `handoff` item if it was blocked/failed). When a consensus `info` item says CONVERGED, proceed to `create_execution_run`. When a `handoff` says it was blocked, address the recorded issues in the spec (any edit re-arms the gate) and call `run_spec_consensus` once more. For a NON-code task (domain seo/marketing/content, ADR 0020): the low-risk lanes `seo`, `marketing`, and `content` proceed to `create_execution_run` after consensus converges, using the same flow as code tasks. Report the spec's review outcome to the operator.",
49461
+ "- When creating a task, classify and supply the `domain` field explicitly based on the nature of the work: pass `domain:'code'` for software engineering work, `domain:'seo'` for SEO/optimization, `domain:'marketing'` for marketing/growth, or `domain:'content'` for content creation/writing \u2014 the same way you select category/subcategory. A follow-up task spawned with `parentTaskId` inherits its parent's domain automatically and requires no explicit domain.",
49151
49462
  '- When a task needs a browser (login flows, UI verification/clicks, scraping), pass `prerequisites:[{mcp:"mrrlin-browser",reason:"..."}]` to `create_execution_run`. If that run later fails into a handoff, the operator gets a one-click "Launch browser & resume" that brings up the dedicated browser as the turn starts.',
49152
49463
  "- When you actually drive a browser (calling `browser_navigate`, `browser_click`, `browser_snapshot`, `browser_type`, etc.), invoke those tools from the **`mrrlin-browser`** MCP server ONLY. Do NOT use tools from any other browser MCP (`playwright`, `chrome-devtools`, `puppeteer`, etc.) even if they are also connected on this operator's box \u2014 those use a shared/default Playwright profile that is fragile across runs. Only `mrrlin-browser` uses this project's dedicated Chromium + persistent operator profile (`~/.mrrlin/browser/profile`). If your host exposes multiple browser MCPs with overlapping tool names, prefer the `mrrlin-browser` server unconditionally.",
49153
49464
  "",
@@ -55104,6 +55415,44 @@ function mapAcceptedIssueToClarification(issue2) {
55104
55415
  category: issue2.category
55105
55416
  };
55106
55417
  }
55418
+ var CLARIFICATION_CAP_BY_IMPACT = { low: 2, medium: 3, high: 5 };
55419
+ function resolveClarificationCap(impact) {
55420
+ const tier = impact?.trim().toLowerCase();
55421
+ if (tier === "medium") return CLARIFICATION_CAP_BY_IMPACT.medium;
55422
+ if (tier === "high") return CLARIFICATION_CAP_BY_IMPACT.high;
55423
+ return CLARIFICATION_CAP_BY_IMPACT.low;
55424
+ }
55425
+ function headIndexOf(id) {
55426
+ const match = /^r\d+-h(\d+)-\d+$/.exec(id);
55427
+ return match?.[1] !== void 0 ? Number(match[1]) : null;
55428
+ }
55429
+ function orderClarificationIssues(issues) {
55430
+ const distinctHeadsByKey = /* @__PURE__ */ new Map();
55431
+ for (const issue2 of issues) {
55432
+ const key = `${issue2.category} ${issue2.text}`;
55433
+ const heads = distinctHeadsByKey.get(key) ?? /* @__PURE__ */ new Set();
55434
+ const headIdx = headIndexOf(issue2.id);
55435
+ if (headIdx !== null) heads.add(headIdx);
55436
+ distinctHeadsByKey.set(key, heads);
55437
+ }
55438
+ const categoryRank = (category) => category === "scope" ? 0 : category === "ambiguity" ? 1 : 2;
55439
+ return issues.map((issue2, index) => ({ issue: issue2, index })).sort((a, b) => {
55440
+ const rankDiff = categoryRank(a.issue.category) - categoryRank(b.issue.category);
55441
+ if (rankDiff !== 0) return rankDiff;
55442
+ const headCountA = distinctHeadsByKey.get(`${a.issue.category} ${a.issue.text}`)?.size ?? 0;
55443
+ const headCountB = distinctHeadsByKey.get(`${b.issue.category} ${b.issue.text}`)?.size ?? 0;
55444
+ if (headCountA !== headCountB) return headCountB - headCountA;
55445
+ return a.index - b.index;
55446
+ }).map(({ issue: issue2 }) => issue2);
55447
+ }
55448
+ function applyClarificationCap(issues, impact) {
55449
+ const ordered = orderClarificationIssues(issues);
55450
+ const cap = resolveClarificationCap(impact);
55451
+ return {
55452
+ kept: ordered.slice(0, cap).map(mapAcceptedIssueToClarification),
55453
+ dropped: ordered.slice(cap).map(mapAcceptedIssueToClarification)
55454
+ };
55455
+ }
55107
55456
  async function runSpecGate(_target, deps, opts) {
55108
55457
  if (!await deps.consensusEnabled()) return { outcome: "SKIPPED" };
55109
55458
  const intent = await deps.readIntent();
@@ -55121,11 +55470,11 @@ async function runSpecGate(_target, deps, opts) {
55121
55470
  ]
55122
55471
  };
55123
55472
  }
55124
- const baseHeads = resolveHeads(intent.domain);
55473
+ const baseHeads = resolveHeads(intent.domain, intent.impact);
55125
55474
  const extraHeads = opts?.extraHeads ?? [];
55126
55475
  const heads = [...baseHeads, ...extraHeads.filter((h) => !baseHeads.includes(h))];
55127
55476
  const spec = await deps.readSpec();
55128
- if (normalizeDomain(intent.domain) !== "code" && parseAcceptanceCriteria(spec.markdownBody) === null) {
55477
+ if (parseAcceptanceCriteria(spec.markdownBody) === null) {
55129
55478
  return {
55130
55479
  outcome: "CONSENSUS_BLOCKED",
55131
55480
  operatorClarifications: [
@@ -55193,21 +55542,34 @@ async function runSpecGate(_target, deps, opts) {
55193
55542
  ]
55194
55543
  };
55195
55544
  }
55545
+ let keptClarifications = [];
55546
+ let droppedByCap = [];
55547
+ if (result.outcome === "CONSENSUS_BLOCKED") {
55548
+ const accepted = result.history.at(-1)?.accepted ?? [];
55549
+ const materialIssues = accepted.filter((issue2) => issue2.category === "ambiguity" || issue2.category === "scope");
55550
+ const capped = applyClarificationCap(materialIssues, intent.impact);
55551
+ keptClarifications = capped.kept;
55552
+ droppedByCap = capped.dropped;
55553
+ }
55196
55554
  await deps.writeEvidence({
55197
55555
  specPageId: spec.pageId,
55198
55556
  outcome: result.outcome,
55199
55557
  rounds: result.rounds,
55200
55558
  history: result.history,
55201
- contentHash: currentHash
55559
+ contentHash: currentHash,
55560
+ ...droppedByCap.length > 0 ? { droppedByCap } : {}
55202
55561
  });
55203
55562
  if (result.outcome === "CONVERGED") {
55204
55563
  await deps.promoteSpecReady(currentHash);
55205
55564
  return { outcome: "CONVERGED" };
55206
55565
  }
55207
55566
  if (result.outcome === "CONSENSUS_BLOCKED") {
55208
- const accepted = result.history.at(-1)?.accepted ?? [];
55209
- const operatorClarifications = accepted.filter((issue2) => issue2.category === "ambiguity" || issue2.category === "scope").map((issue2) => mapAcceptedIssueToClarification(issue2));
55210
- return operatorClarifications.length > 0 ? { outcome: "CONSENSUS_BLOCKED", operatorClarifications } : { outcome: "CONSENSUS_BLOCKED" };
55567
+ return {
55568
+ outcome: "CONSENSUS_BLOCKED",
55569
+ ...keptClarifications.length > 0 ? { operatorClarifications: keptClarifications } : {},
55570
+ ...droppedByCap.length > 0 ? { droppedByCap } : {},
55571
+ ...result.escalation ? { escalation: result.escalation } : {}
55572
+ };
55211
55573
  }
55212
55574
  return { outcome: result.outcome };
55213
55575
  }
@@ -55486,7 +55848,7 @@ async function maybeRunShadow(input, deps, logger) {
55486
55848
  const productionVerdict = productionVerdictFromOutcome(input.outcome);
55487
55849
  if (productionVerdict === null) return false;
55488
55850
  try {
55489
- const heads = input.heads ?? [...resolveHeads(input.domain), ...input.extraHeads ?? []];
55851
+ const heads = input.heads ?? [...resolveHeads(input.domain, input.impact), ...input.extraHeads ?? []];
55490
55852
  const personaInstructions = {};
55491
55853
  for (const name of heads) {
55492
55854
  try {
@@ -55528,7 +55890,7 @@ function multiLlmActive(env = process.env) {
55528
55890
  async function resolveActiveRunHead(input, deps) {
55529
55891
  if (!multiLlmActive(input.env ?? process.env)) return null;
55530
55892
  try {
55531
- const heads = input.heads ?? [...resolveHeads(input.domain), ...input.extraHeads ?? []];
55893
+ const heads = input.heads ?? [...resolveHeads(input.domain, input.impact), ...input.extraHeads ?? []];
55532
55894
  if (heads.length === 0) return null;
55533
55895
  const personaInstructions = {};
55534
55896
  for (const name of heads) {
@@ -55558,6 +55920,35 @@ async function resolveActiveRunHead(input, deps) {
55558
55920
 
55559
55921
  // src/async-tools/consensus.ts
55560
55922
  var import_node_crypto9 = require("node:crypto");
55923
+
55924
+ // src/consensus/question-similarity.ts
55925
+ var SUPPRESSION_SIMILARITY_THRESHOLD = 0.6;
55926
+ function tokenSet(text) {
55927
+ const normalized = text.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").trim();
55928
+ if (!normalized) return /* @__PURE__ */ new Set();
55929
+ return new Set(normalized.split(/\s+/));
55930
+ }
55931
+ function jaccardSimilarity(a, b) {
55932
+ const setA = tokenSet(a);
55933
+ const setB = tokenSet(b);
55934
+ if (setA.size === 0 || setB.size === 0) return 0;
55935
+ let intersection2 = 0;
55936
+ for (const token of setA) {
55937
+ if (setB.has(token)) intersection2 += 1;
55938
+ }
55939
+ const union2 = setA.size + setB.size - intersection2;
55940
+ return union2 === 0 ? 0 : intersection2 / union2;
55941
+ }
55942
+ function isCoveredByAdjudicated(rawQuestion, adjudicated) {
55943
+ for (const item of adjudicated) {
55944
+ if (jaccardSimilarity(rawQuestion, item.body) >= SUPPRESSION_SIMILARITY_THRESHOLD) {
55945
+ return item;
55946
+ }
55947
+ }
55948
+ return null;
55949
+ }
55950
+
55951
+ // src/async-tools/consensus.ts
55561
55952
  function normalizeClarificationQuestion(question) {
55562
55953
  return question.replace(/\s+/g, " ").trim().replace(/[?.!]+$/, "").trim();
55563
55954
  }
@@ -55688,35 +56079,43 @@ async function cachedResult(client, input) {
55688
56079
  }
55689
56080
  return null;
55690
56081
  }
56082
+ function stripHumanizeOpener(body) {
56083
+ for (const prefix of Object.values(HUMANIZE_TEMPLATE_BY_KIND)) {
56084
+ if (body.startsWith(prefix)) return body.slice(prefix.length).trim();
56085
+ }
56086
+ return body;
56087
+ }
55691
56088
  async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
55692
56089
  const operatorClarifications = result.operatorClarifications ?? [];
55693
56090
  const diagnostics = result.diagnostics ?? [];
55694
- if (operatorClarifications.length > 0) {
56091
+ const page = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
56092
+ const taskId = page?.taskId ?? null;
56093
+ if (!resolvedTaskId && taskId) resolvedTaskId = taskId;
56094
+ const adjudicated = [];
56095
+ if (taskId) {
55695
56096
  try {
55696
- const page = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
55697
- if (page) {
55698
- const domain2 = await resolveSpecDomain(ctx.client, input.projectSlug, {
55699
- id: page.id,
55700
- taskId: page.taskId ?? null
55701
- });
55702
- await ctx.client.recordSpecConsensusReview(input.projectSlug, input.pageId, {
55703
- contentHash: domainScopedReviewHash(page.contentHash, domain2),
55704
- outcome: "CONSENSUS_BLOCKED_CLARIFICATION",
55705
- rounds: 0
55706
- });
56097
+ const taskInboxItems = await ctx.client.listInboxItems(input.projectSlug, { taskId });
56098
+ for (const item of taskInboxItems) {
56099
+ if (item.origin === "gate" && (item.status === "acknowledged" || item.status === "rejected")) {
56100
+ adjudicated.push({ body: stripHumanizeOpener(item.body), answer: item.answer, status: item.status });
56101
+ }
55707
56102
  }
55708
56103
  } catch (error52) {
55709
56104
  process.stderr.write(
55710
- `[mrrlin-mcp consensus] clarification verdict write failed for ${input.projectSlug}/${input.pageId}: ${error52 instanceof Error ? error52.message : String(error52)}
56105
+ `[mrrlin-mcp consensus] adjudicated-questions read failed for ${input.projectSlug}/${input.pageId}: ${error52 instanceof Error ? error52.message : String(error52)}
55711
56106
  `
55712
56107
  );
55713
56108
  }
55714
56109
  }
56110
+ const suppressedAsAdjudicated = [];
56111
+ const toFile = operatorClarifications.filter((clarification) => {
56112
+ const covering = isCoveredByAdjudicated(clarification.rawQuestion, adjudicated);
56113
+ if (!covering) return true;
56114
+ suppressedAsAdjudicated.push({ rawQuestion: clarification.rawQuestion, coveredByAnswer: covering.answer });
56115
+ return false;
56116
+ });
55715
56117
  let filedQuestionCount = 0;
55716
- if (operatorClarifications.length > 0) {
55717
- const page = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
55718
- const taskId = page?.taskId ?? null;
55719
- if (!resolvedTaskId && taskId) resolvedTaskId = taskId;
56118
+ if (toFile.length > 0) {
55720
56119
  let planId = null;
55721
56120
  if (!taskId) {
55722
56121
  const plans = await ctx.client.listPlans(input.projectSlug);
@@ -55724,8 +56123,8 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
55724
56123
  }
55725
56124
  if (taskId) {
55726
56125
  const scopeId = taskId;
55727
- const groupId = operatorClarifications.length > 1 ? `II-${scopeId}-gate-group-${(0, import_node_crypto9.createHash)("sha256").update(operatorClarifications.map((c) => c.rawQuestion).sort().join("\n")).digest("hex").slice(0, 10)}` : void 0;
55728
- for (const clarification of operatorClarifications) {
56126
+ const groupId = toFile.length > 1 ? `II-${scopeId}-gate-group-${(0, import_node_crypto9.createHash)("sha256").update(toFile.map((c) => c.rawQuestion).sort().join("\n")).digest("hex").slice(0, 10)}` : void 0;
56127
+ for (const clarification of toFile) {
55729
56128
  const id = `II-${scopeId}-gate-${(0, import_node_crypto9.createHash)("sha256").update(clarification.rawQuestion).digest("hex").slice(0, 10)}`;
55730
56129
  try {
55731
56130
  const questionOptions = buildGateQuestionOptions(clarification.rawQuestion, clarification.category);
@@ -55741,11 +56140,11 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
55741
56140
  } catch {
55742
56141
  }
55743
56142
  }
55744
- filedQuestionCount = operatorClarifications.length;
56143
+ filedQuestionCount = toFile.length;
55745
56144
  } else if (planId) {
55746
56145
  const scopeId = planId;
55747
- const groupId = operatorClarifications.length > 1 ? `II-${scopeId}-gate-group-${(0, import_node_crypto9.createHash)("sha256").update(operatorClarifications.map((c) => c.rawQuestion).sort().join("\n")).digest("hex").slice(0, 10)}` : void 0;
55748
- for (const clarification of operatorClarifications) {
56146
+ const groupId = toFile.length > 1 ? `II-${scopeId}-gate-group-${(0, import_node_crypto9.createHash)("sha256").update(toFile.map((c) => c.rawQuestion).sort().join("\n")).digest("hex").slice(0, 10)}` : void 0;
56147
+ for (const clarification of toFile) {
55749
56148
  const id = `II-${scopeId}-gate-${(0, import_node_crypto9.createHash)("sha256").update(clarification.rawQuestion).digest("hex").slice(0, 10)}`;
55750
56149
  try {
55751
56150
  const questionOptions = buildGateQuestionOptions(clarification.rawQuestion, clarification.category);
@@ -55761,7 +56160,32 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
55761
56160
  } catch {
55762
56161
  }
55763
56162
  }
55764
- filedQuestionCount = operatorClarifications.length;
56163
+ filedQuestionCount = toFile.length;
56164
+ }
56165
+ }
56166
+ if (operatorClarifications.length > 0) {
56167
+ try {
56168
+ if (page) {
56169
+ const domain2 = await resolveSpecDomain(ctx.client, input.projectSlug, {
56170
+ id: page.id,
56171
+ taskId: page.taskId ?? null
56172
+ });
56173
+ const suppressionEvidence = (result.droppedByCap?.length ?? 0) > 0 || suppressedAsAdjudicated.length > 0 ? {
56174
+ ...result.droppedByCap?.length ? { droppedByCap: result.droppedByCap } : {},
56175
+ ...suppressedAsAdjudicated.length ? { suppressedAsAdjudicated } : {}
56176
+ } : void 0;
56177
+ await ctx.client.recordSpecConsensusReview(input.projectSlug, input.pageId, {
56178
+ contentHash: domainScopedReviewHash(page.contentHash, domain2),
56179
+ outcome: "CONSENSUS_BLOCKED_CLARIFICATION",
56180
+ rounds: 0,
56181
+ ...suppressionEvidence ? { suppressionEvidence } : {}
56182
+ });
56183
+ }
56184
+ } catch (error52) {
56185
+ process.stderr.write(
56186
+ `[mrrlin-mcp consensus] clarification verdict write failed for ${input.projectSlug}/${input.pageId}: ${error52 instanceof Error ? error52.message : String(error52)}
56187
+ `
56188
+ );
55765
56189
  }
55766
56190
  }
55767
56191
  let error51;
@@ -55769,11 +56193,16 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
55769
56193
  error51 = `Consensus blocked on ${filedQuestionCount} clarification(s); filed as operator question(s).`;
55770
56194
  } else if (diagnostics.length > 0 && operatorClarifications.length === 0) {
55771
56195
  error51 = "Consensus blocked: the spec content does not match its linked task. This requires fixing the spec/task relationship directly, not an inbox answer.";
56196
+ } else if (operatorClarifications.length > 0 && toFile.length === 0 && suppressedAsAdjudicated.length > 0) {
56197
+ error51 = `Consensus blocked, but all ${suppressedAsAdjudicated.length} clarification(s) were reworded repeats of already-adjudicated questions \u2014 nothing new filed.`;
55772
56198
  } else if (operatorClarifications.length > 0) {
55773
56199
  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.`;
55774
56200
  } else {
55775
56201
  error51 = "Consensus blocked: reviewers did not converge within budget.";
55776
56202
  }
56203
+ if (result.escalation) {
56204
+ error51 += ` (diagnosis: ${result.escalation.diagnosis})`;
56205
+ }
55777
56206
  return {
55778
56207
  kind: "failed",
55779
56208
  failureReason: "consensus_blocked",
@@ -56091,7 +56520,8 @@ function readCompactPolicy(env = process.env) {
56091
56520
  enabled: !disabled,
56092
56521
  thresholdChars: readPositiveInt(env.MRRLIN_MCP_COMPACT_THRESHOLD_CHARS, DEFAULT_THRESHOLD_CHARS),
56093
56522
  maxItems: readPositiveInt(env.MRRLIN_MCP_COMPACT_MAX_ITEMS, DEFAULT_MAX_ITEMS),
56094
- debug: (env.MRRLIN_MCP_COMPACT_DEBUG ?? "").trim() === "1"
56523
+ debug: (env.MRRLIN_MCP_COMPACT_DEBUG ?? "").trim() === "1",
56524
+ retrievalBudgetDisable: (env.MRRLIN_RETRIEVAL_BUDGET_DISABLE ?? "").trim() === "1"
56095
56525
  };
56096
56526
  }
56097
56527
 
@@ -56221,6 +56651,7 @@ function compactWikiPageList(projectSlug, pages, maxItems) {
56221
56651
  title: truncate(p.title, TITLE_TRUNCATE_CHARS),
56222
56652
  folder: p.folder,
56223
56653
  path: p.path,
56654
+ updatedAt: p.updatedAt,
56224
56655
  taskId: p.taskId,
56225
56656
  specReady: p.specReady,
56226
56657
  firstHeading: firstHeadingLine(p.markdownBody),
@@ -56248,6 +56679,10 @@ function compactWikiSearch(projectSlug, results, maxItems) {
56248
56679
  folder: r.folder,
56249
56680
  path: r.path,
56250
56681
  rank: r.rank,
56682
+ updatedAt: r.updatedAt,
56683
+ ageDays: r.ageDays,
56684
+ memoryClass: r.memoryClass,
56685
+ precedence: r.precedence,
56251
56686
  snippet: truncate(r.snippet, EXCERPT_TRUNCATE_CHARS),
56252
56687
  handle: formatWikiHandle(projectSlug, r.id)
56253
56688
  }));
@@ -56830,7 +57265,7 @@ var mcpToolDescriptions = {
56830
57265
  [mcpToolNames.appendTaskEvent]: "Append an operator note to a task's activity timeline.",
56831
57266
  [mcpToolNames.createWikiPage]: "Create a markdown Wiki page.",
56832
57267
  [mcpToolNames.deleteTaskDependency]: "Delete a blocked-by relationship.",
56833
- [mcpToolNames.createTask]: "Create a new task in a project. Always starts as status='todo' with a blank assignee. Pass parentTaskId when spawning a follow-up task to continue work from an existing task \u2014 its operator_seed_request is inherited automatically if you don't supply your own.",
57268
+ [mcpToolNames.createTask]: "Create a new task in a project. Always starts as status='todo' with a blank assignee. Pass parentTaskId when spawning a follow-up task to continue work from an existing task \u2014 its operator_seed_request is inherited automatically if you don't supply your own. Domain is required UNLESS parentTaskId is supplied (a follow-up inherits its parent's domain automatically). Domain selects which reviewer roster judges the task's spec. Valid values: code, seo, marketing, content.",
56834
57269
  [mcpToolNames.getProject]: "Fetch a single project summary by slug.",
56835
57270
  [mcpToolNames.getProjectSnapshot]: "Fetch the full project snapshot: GitHub binding, tasks, plans, and wiki pages.",
56836
57271
  [mcpToolNames.getTask]: "Fetch a single task by project slug and task id.",