@mrrlin-dev/mcp 0.4.1 → 0.4.4
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 +380 -171
- package/dist/consensus/personas/findings-writer.md +20 -0
- package/package.json +2 -2
package/dist/bin.cjs
CHANGED
|
@@ -4273,7 +4273,7 @@ var require_websocket = __commonJS({
|
|
|
4273
4273
|
var http2 = require("http");
|
|
4274
4274
|
var net = require("net");
|
|
4275
4275
|
var tls = require("tls");
|
|
4276
|
-
var { randomBytes: randomBytes4, createHash:
|
|
4276
|
+
var { randomBytes: randomBytes4, createHash: createHash4 } = require("crypto");
|
|
4277
4277
|
var { Duplex, Readable } = require("stream");
|
|
4278
4278
|
var { URL: URL2 } = require("url");
|
|
4279
4279
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -4941,7 +4941,7 @@ var require_websocket = __commonJS({
|
|
|
4941
4941
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
4942
4942
|
return;
|
|
4943
4943
|
}
|
|
4944
|
-
const digest =
|
|
4944
|
+
const digest = createHash4("sha1").update(key + GUID).digest("base64");
|
|
4945
4945
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
4946
4946
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
4947
4947
|
return;
|
|
@@ -5310,7 +5310,7 @@ var require_websocket_server = __commonJS({
|
|
|
5310
5310
|
var EventEmitter3 = require("events");
|
|
5311
5311
|
var http2 = require("http");
|
|
5312
5312
|
var { Duplex } = require("stream");
|
|
5313
|
-
var { createHash:
|
|
5313
|
+
var { createHash: createHash4 } = require("crypto");
|
|
5314
5314
|
var extension2 = require_extension();
|
|
5315
5315
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
5316
5316
|
var subprotocol2 = require_subprotocol();
|
|
@@ -5617,7 +5617,7 @@ var require_websocket_server = __commonJS({
|
|
|
5617
5617
|
);
|
|
5618
5618
|
}
|
|
5619
5619
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
5620
|
-
const digest =
|
|
5620
|
+
const digest = createHash4("sha1").update(key + GUID).digest("base64");
|
|
5621
5621
|
const headers = [
|
|
5622
5622
|
"HTTP/1.1 101 Switching Protocols",
|
|
5623
5623
|
"Upgrade: websocket",
|
|
@@ -21174,7 +21174,7 @@ var import_node_http = __toESM(require("node:http"), 1);
|
|
|
21174
21174
|
var import_node_os9 = __toESM(require("node:os"), 1);
|
|
21175
21175
|
var import_node_path18 = __toESM(require("node:path"), 1);
|
|
21176
21176
|
var import_node_child_process11 = require("node:child_process");
|
|
21177
|
-
var
|
|
21177
|
+
var import_node_crypto7 = __toESM(require("node:crypto"), 1);
|
|
21178
21178
|
|
|
21179
21179
|
// src/turn-replay-buffer.ts
|
|
21180
21180
|
var DEFAULT_MAX_EVENTS = 2e3;
|
|
@@ -36574,6 +36574,8 @@ var agentPoolResponseSchema = external_exports.object({
|
|
|
36574
36574
|
|
|
36575
36575
|
// ../../packages/schemas/dist/specConsensusReview.js
|
|
36576
36576
|
var mrrlinSpecConsensusSuppressionEvidenceSchema = external_exports.record(external_exports.string(), external_exports.unknown()).nullable();
|
|
36577
|
+
var mrrlinSpecConsensusHistorySchema = external_exports.unknown();
|
|
36578
|
+
var mrrlinSpecConsensusHighlightsSchema = external_exports.object({ findings: external_exports.array(external_exports.string()) });
|
|
36577
36579
|
var mrrlinSpecConsensusReviewSchema = external_exports.object({
|
|
36578
36580
|
projectSlug: external_exports.string().min(1),
|
|
36579
36581
|
specPageId: external_exports.string().min(1),
|
|
@@ -36581,6 +36583,8 @@ var mrrlinSpecConsensusReviewSchema = external_exports.object({
|
|
|
36581
36583
|
outcome: external_exports.string().min(1),
|
|
36582
36584
|
rounds: external_exports.number().int().nonnegative(),
|
|
36583
36585
|
suppressionEvidence: mrrlinSpecConsensusSuppressionEvidenceSchema.default(null),
|
|
36586
|
+
history: mrrlinSpecConsensusHistorySchema.nullable().default(null),
|
|
36587
|
+
highlights: mrrlinSpecConsensusHighlightsSchema.nullable().default(null),
|
|
36584
36588
|
createdAt: external_exports.string().datetime()
|
|
36585
36589
|
});
|
|
36586
36590
|
var mrrlinSpecConsensusReviewCreateSchema = external_exports.object({
|
|
@@ -36588,10 +36592,26 @@ var mrrlinSpecConsensusReviewCreateSchema = external_exports.object({
|
|
|
36588
36592
|
contentHash: external_exports.string().min(1),
|
|
36589
36593
|
outcome: external_exports.string().min(1),
|
|
36590
36594
|
rounds: external_exports.number().int().nonnegative(),
|
|
36591
|
-
suppressionEvidence: mrrlinSpecConsensusSuppressionEvidenceSchema.optional()
|
|
36595
|
+
suppressionEvidence: mrrlinSpecConsensusSuppressionEvidenceSchema.optional(),
|
|
36596
|
+
history: mrrlinSpecConsensusHistorySchema.optional(),
|
|
36597
|
+
highlights: mrrlinSpecConsensusHighlightsSchema.optional()
|
|
36592
36598
|
}).strict();
|
|
36593
36599
|
var specConsensusReviewResponseSchema = external_exports.object({ data: mrrlinSpecConsensusReviewSchema });
|
|
36594
36600
|
var specConsensusReviewMaybeResponseSchema = external_exports.object({ data: mrrlinSpecConsensusReviewSchema.nullable() });
|
|
36601
|
+
var PERSONA_ROLE_MAP = {
|
|
36602
|
+
architect: "design",
|
|
36603
|
+
"security-analyst": "security",
|
|
36604
|
+
"seo-analyst": "SEO",
|
|
36605
|
+
"compliance-reviewer": "compliance",
|
|
36606
|
+
debugger: "root cause",
|
|
36607
|
+
researcher: "research",
|
|
36608
|
+
"content-editor": "wording",
|
|
36609
|
+
"code-reviewer": "code",
|
|
36610
|
+
"content-author": "content",
|
|
36611
|
+
"seo-writer": "SEO writing",
|
|
36612
|
+
"spec-author": "spec"
|
|
36613
|
+
};
|
|
36614
|
+
var UNNAMED_PERSONAS = ["plan-reviewer", "scope-analyst"];
|
|
36595
36615
|
|
|
36596
36616
|
// ../../packages/schemas/dist/asyncJob.js
|
|
36597
36617
|
var ASYNC_JOB_STATUSES = ["queued", "running", "done", "failed", "superseded"];
|
|
@@ -38187,6 +38207,9 @@ var mrrlinInboxQuestionOptionsSchema = external_exports.array(mrrlinInboxQuestio
|
|
|
38187
38207
|
});
|
|
38188
38208
|
}
|
|
38189
38209
|
});
|
|
38210
|
+
var REVIEW_AGAIN_ANSWER = "Review the current spec again without changing it.";
|
|
38211
|
+
var EDIT_SPEC_ANSWER = "Hold the task until the spec is edited by hand.";
|
|
38212
|
+
var PROCEED_ANYWAY_ANSWER = "Proceed without agreement; record the unsettled points on the spec.";
|
|
38190
38213
|
var mrrlinInboxItemSchema = external_exports.object({
|
|
38191
38214
|
actionPayload: mrrlinInboxItemActionPayloadSchema.nullable(),
|
|
38192
38215
|
answer: external_exports.string().nullable(),
|
|
@@ -40187,7 +40210,7 @@ function startCodexProbe(deps = {}) {
|
|
|
40187
40210
|
}
|
|
40188
40211
|
|
|
40189
40212
|
// src/_generated/version.ts
|
|
40190
|
-
var PKG_VERSION = "0.4.
|
|
40213
|
+
var PKG_VERSION = "0.4.4";
|
|
40191
40214
|
|
|
40192
40215
|
// src/api-base-url-migrate.ts
|
|
40193
40216
|
var import_node_fs2 = __toESM(require("node:fs"), 1);
|
|
@@ -40989,7 +41012,7 @@ function startAgentPoolLoop(deps) {
|
|
|
40989
41012
|
|
|
40990
41013
|
// src/execution-consumer.ts
|
|
40991
41014
|
var import_node_child_process9 = require("node:child_process");
|
|
40992
|
-
var
|
|
41015
|
+
var import_node_crypto4 = __toESM(require("node:crypto"), 1);
|
|
40993
41016
|
var import_node_path10 = __toESM(require("node:path"), 1);
|
|
40994
41017
|
var import_node_util = require("node:util");
|
|
40995
41018
|
|
|
@@ -41312,7 +41335,7 @@ async function runConsensus(input) {
|
|
|
41312
41335
|
let decision;
|
|
41313
41336
|
for (let round = 1; round <= effectiveRounds; round++) {
|
|
41314
41337
|
if (calls + input.heads.length > input.maxHeadCalls) {
|
|
41315
|
-
return { outcome: "CONSENSUS_BLOCKED", rounds: round - 1, finalArtifact: artifact, history, ...decision ? { escalation: decision } : {} };
|
|
41338
|
+
return { outcome: "CONSENSUS_BLOCKED", rounds: round - 1, finalArtifact: artifact, history, unresolved: history[history.length - 1]?.accepted ?? [], ...decision ? { escalation: decision } : {} };
|
|
41316
41339
|
}
|
|
41317
41340
|
const dismissedBlock = dismissedFwd.length ? `PREVIOUSLY DISMISSED (do not re-raise unless new information):
|
|
41318
41341
|
${dismissedFwd.slice(-20).join("\n")}` : "";
|
|
@@ -41334,7 +41357,7 @@ ${dismissedFwd.slice(-20).join("\n")}` : "";
|
|
|
41334
41357
|
(r) => r.res.ok
|
|
41335
41358
|
);
|
|
41336
41359
|
if (responding.length === 0) {
|
|
41337
|
-
return { outcome: "CONSENSUS_UNAVAILABLE", rounds: round, finalArtifact: artifact, history };
|
|
41360
|
+
return { outcome: "CONSENSUS_UNAVAILABLE", rounds: round, finalArtifact: artifact, history, unresolved: [] };
|
|
41338
41361
|
}
|
|
41339
41362
|
const headOutcomes = input.heads.map((persona) => ({
|
|
41340
41363
|
persona,
|
|
@@ -41360,7 +41383,7 @@ ${dismissedFwd.slice(-20).join("\n")}` : "";
|
|
|
41360
41383
|
let adj;
|
|
41361
41384
|
if (issues.length > 0) {
|
|
41362
41385
|
if (calls + 1 > input.maxHeadCalls) {
|
|
41363
|
-
return { outcome: "CONSENSUS_BLOCKED", rounds: round, finalArtifact: artifact, history, ...decision ? { escalation: decision } : {} };
|
|
41386
|
+
return { outcome: "CONSENSUS_BLOCKED", rounds: round, finalArtifact: artifact, history, unresolved: history[history.length - 1]?.accepted ?? [], ...decision ? { escalation: decision } : {} };
|
|
41364
41387
|
}
|
|
41365
41388
|
calls++;
|
|
41366
41389
|
try {
|
|
@@ -41374,7 +41397,7 @@ ${dismissedFwd.slice(-20).join("\n")}` : "";
|
|
|
41374
41397
|
history.push({ round, accepted: adj.accepted, dismissed: adj.dismissed, headOutcomes });
|
|
41375
41398
|
adj.dismissed.forEach((d) => dismissedFwd.push(`- ${d.id}: ${d.reason}`));
|
|
41376
41399
|
if (adj.accepted.length === 0) {
|
|
41377
|
-
return { outcome: "CONVERGED", rounds: round, finalArtifact: artifact, history, ...decision ? { escalation: decision } : {} };
|
|
41400
|
+
return { outcome: "CONVERGED", rounds: round, finalArtifact: artifact, history, unresolved: [], ...decision ? { escalation: decision } : {} };
|
|
41378
41401
|
}
|
|
41379
41402
|
if (round === effectiveRounds) {
|
|
41380
41403
|
if (!decision) {
|
|
@@ -41384,12 +41407,12 @@ ${dismissedFwd.slice(-20).join("\n")}` : "";
|
|
|
41384
41407
|
escalated = true;
|
|
41385
41408
|
effectiveRounds = Math.min(effectiveRounds + decision.grantedRounds, input.rounds * 2);
|
|
41386
41409
|
} else {
|
|
41387
|
-
return { outcome: "CONSENSUS_BLOCKED", rounds: round, finalArtifact: artifact, history, ...decision ? { escalation: decision } : {} };
|
|
41410
|
+
return { outcome: "CONSENSUS_BLOCKED", rounds: round, finalArtifact: artifact, history, unresolved: history[history.length - 1]?.accepted ?? [], ...decision ? { escalation: decision } : {} };
|
|
41388
41411
|
}
|
|
41389
41412
|
}
|
|
41390
41413
|
artifact = await input.revise(artifact, adj.accepted);
|
|
41391
41414
|
}
|
|
41392
|
-
return { outcome: "CONSENSUS_BLOCKED", rounds: effectiveRounds, finalArtifact: artifact, history };
|
|
41415
|
+
return { outcome: "CONSENSUS_BLOCKED", rounds: effectiveRounds, finalArtifact: artifact, history, unresolved: history[history.length - 1]?.accepted ?? [] };
|
|
41393
41416
|
}
|
|
41394
41417
|
|
|
41395
41418
|
// src/consensus/spec-identity.ts
|
|
@@ -41681,14 +41704,15 @@ async function runSpecGate(_target, deps, opts) {
|
|
|
41681
41704
|
});
|
|
41682
41705
|
if (result.outcome === "CONVERGED") {
|
|
41683
41706
|
await deps.promoteSpecReady(currentHash);
|
|
41684
|
-
return { outcome: "CONVERGED" };
|
|
41707
|
+
return { outcome: "CONVERGED", headOutcomes: result.history.at(-1)?.headOutcomes ?? [] };
|
|
41685
41708
|
}
|
|
41686
41709
|
if (result.outcome === "CONSENSUS_BLOCKED") {
|
|
41687
41710
|
return {
|
|
41688
41711
|
outcome: "CONSENSUS_BLOCKED",
|
|
41689
41712
|
...keptClarifications.length > 0 ? { operatorClarifications: keptClarifications } : {},
|
|
41690
41713
|
...droppedByCap.length > 0 ? { droppedByCap } : {},
|
|
41691
|
-
...result.escalation ? { escalation: result.escalation } : {}
|
|
41714
|
+
...result.escalation ? { escalation: result.escalation } : {},
|
|
41715
|
+
unresolved: result.unresolved
|
|
41692
41716
|
};
|
|
41693
41717
|
}
|
|
41694
41718
|
return { outcome: result.outcome };
|
|
@@ -41730,6 +41754,63 @@ async function resolveSpecDomain(client, projectSlug, page) {
|
|
|
41730
41754
|
}
|
|
41731
41755
|
}
|
|
41732
41756
|
|
|
41757
|
+
// src/consensus/findings.ts
|
|
41758
|
+
function shouldCurateFindings(history) {
|
|
41759
|
+
return history.some((entry) => entry.accepted.length > 0);
|
|
41760
|
+
}
|
|
41761
|
+
var SYNTHESIZED_PLACEHOLDER = /^head .* returned .* but no parseable critical issues/;
|
|
41762
|
+
function selectFindingsCandidates(history) {
|
|
41763
|
+
const result = [];
|
|
41764
|
+
for (const entry of history) {
|
|
41765
|
+
for (const issue2 of entry.accepted) {
|
|
41766
|
+
if (SYNTHESIZED_PLACEHOLDER.test(issue2.text)) {
|
|
41767
|
+
continue;
|
|
41768
|
+
}
|
|
41769
|
+
result.push(issue2);
|
|
41770
|
+
}
|
|
41771
|
+
}
|
|
41772
|
+
if (result.length > 3) {
|
|
41773
|
+
return result.slice(0, 3);
|
|
41774
|
+
}
|
|
41775
|
+
return result;
|
|
41776
|
+
}
|
|
41777
|
+
function isFindingsHighlights(value) {
|
|
41778
|
+
return typeof value === "object" && value !== null && Array.isArray(value.findings) && value.findings.every((f) => typeof f === "string");
|
|
41779
|
+
}
|
|
41780
|
+
async function curateFindings(history, deps) {
|
|
41781
|
+
if (!shouldCurateFindings(history)) return null;
|
|
41782
|
+
try {
|
|
41783
|
+
const candidates = selectFindingsCandidates(history);
|
|
41784
|
+
const issuesBlock = candidates.length > 0 ? candidates.map((issue2) => `- (${issue2.category}) ${issue2.text}`).join("\n") : "(no issues survived the deterministic pre-filter)";
|
|
41785
|
+
const result = await deps.runCodexExec({
|
|
41786
|
+
prompt: `--- ACCEPTED ISSUES ---
|
|
41787
|
+
${issuesBlock}`,
|
|
41788
|
+
developerInstructions: deps.loadPersona("findings-writer"),
|
|
41789
|
+
cwd: deps.cwd,
|
|
41790
|
+
timeoutMs: deps.timeoutMs,
|
|
41791
|
+
sandbox: "read-only",
|
|
41792
|
+
codexExecutable: deps.codexExecutable
|
|
41793
|
+
});
|
|
41794
|
+
if (!result.ok) {
|
|
41795
|
+
process.stderr.write(`[mrrlin-mcp consensus] findings curation failed: ${result.error}
|
|
41796
|
+
`);
|
|
41797
|
+
return null;
|
|
41798
|
+
}
|
|
41799
|
+
const parsed = JSON.parse(result.text);
|
|
41800
|
+
if (!isFindingsHighlights(parsed)) {
|
|
41801
|
+
process.stderr.write("[mrrlin-mcp consensus] findings curation returned unparseable output\n");
|
|
41802
|
+
return null;
|
|
41803
|
+
}
|
|
41804
|
+
return { findings: parsed.findings.slice(0, 3) };
|
|
41805
|
+
} catch (err) {
|
|
41806
|
+
process.stderr.write(
|
|
41807
|
+
`[mrrlin-mcp consensus] findings curation threw: ${err instanceof Error ? err.message : String(err)}
|
|
41808
|
+
`
|
|
41809
|
+
);
|
|
41810
|
+
return null;
|
|
41811
|
+
}
|
|
41812
|
+
}
|
|
41813
|
+
|
|
41733
41814
|
// src/consensus/code-gate-git.ts
|
|
41734
41815
|
var import_node_child_process7 = require("node:child_process");
|
|
41735
41816
|
var DIFF_MAX_BYTES = 2e5;
|
|
@@ -42169,12 +42250,22 @@ ${artifact}`,
|
|
|
42169
42250
|
}
|
|
42170
42251
|
const outcome = r["outcome"];
|
|
42171
42252
|
const rounds2 = r["rounds"];
|
|
42253
|
+
const history = r["history"];
|
|
42254
|
+
const highlights = await curateFindings(Array.isArray(history) ? history : [], {
|
|
42255
|
+
runCodexExec,
|
|
42256
|
+
loadPersona,
|
|
42257
|
+
cwd,
|
|
42258
|
+
timeoutMs,
|
|
42259
|
+
codexExecutable
|
|
42260
|
+
});
|
|
42172
42261
|
const { domain: domain2 } = await loadIntent();
|
|
42173
42262
|
try {
|
|
42174
42263
|
await client.recordSpecConsensusReview(projectSlug, specPageId, {
|
|
42175
42264
|
contentHash: domainScopedReviewHash2(contentHash, domain2),
|
|
42176
42265
|
outcome: String(outcome),
|
|
42177
|
-
rounds: typeof rounds2 === "number" ? rounds2 : 0
|
|
42266
|
+
rounds: typeof rounds2 === "number" ? rounds2 : 0,
|
|
42267
|
+
...history !== void 0 ? { history } : {},
|
|
42268
|
+
...highlights ? { highlights } : {}
|
|
42178
42269
|
});
|
|
42179
42270
|
} catch (err) {
|
|
42180
42271
|
process.stderr.write(
|
|
@@ -42382,11 +42473,21 @@ You revise a deliverable to address the accepted issues so it satisfies the acce
|
|
|
42382
42473
|
if (typeof contentHash !== "string" || contentHash.length === 0) return;
|
|
42383
42474
|
const outcome = String(r["outcome"]);
|
|
42384
42475
|
const rounds2 = typeof r["rounds"] === "number" ? r["rounds"] : 0;
|
|
42476
|
+
const history = r["history"];
|
|
42477
|
+
const highlights = await curateFindings(Array.isArray(history) ? history : [], {
|
|
42478
|
+
runCodexExec,
|
|
42479
|
+
loadPersona,
|
|
42480
|
+
cwd,
|
|
42481
|
+
timeoutMs,
|
|
42482
|
+
codexExecutable
|
|
42483
|
+
});
|
|
42385
42484
|
try {
|
|
42386
42485
|
await client.recordSpecConsensusReview(projectSlug, deliverablePageId, {
|
|
42387
42486
|
contentHash: domainScopedReviewHash2(contentHash, domain2),
|
|
42388
42487
|
outcome,
|
|
42389
|
-
rounds: rounds2
|
|
42488
|
+
rounds: rounds2,
|
|
42489
|
+
...history !== void 0 ? { history } : {},
|
|
42490
|
+
...highlights ? { highlights } : {}
|
|
42390
42491
|
});
|
|
42391
42492
|
} catch (err) {
|
|
42392
42493
|
process.stderr.write(
|
|
@@ -42396,6 +42497,7 @@ You revise a deliverable to address the accepted issues so it satisfies the acce
|
|
|
42396
42497
|
}
|
|
42397
42498
|
if (runId && taskId) {
|
|
42398
42499
|
try {
|
|
42500
|
+
const diagnostics = Array.isArray(r["diagnostics"]) ? r["diagnostics"] : void 0;
|
|
42399
42501
|
await client.appendExecutionArtifact(projectSlug, runId, {
|
|
42400
42502
|
id: `EA-${import_node_crypto2.default.randomUUID()}`,
|
|
42401
42503
|
kind: "verification",
|
|
@@ -42405,7 +42507,8 @@ You revise a deliverable to address the accepted issues so it satisfies the acce
|
|
|
42405
42507
|
runId,
|
|
42406
42508
|
taskId,
|
|
42407
42509
|
deliverablePageId,
|
|
42408
|
-
contentHash
|
|
42510
|
+
contentHash,
|
|
42511
|
+
...diagnostics ? { diagnostics } : {}
|
|
42409
42512
|
}
|
|
42410
42513
|
});
|
|
42411
42514
|
} catch {
|
|
@@ -42416,6 +42519,35 @@ You revise a deliverable to address the accepted issues so it satisfies the acce
|
|
|
42416
42519
|
}
|
|
42417
42520
|
|
|
42418
42521
|
// src/consensus/deliverable-gate-outcome.ts
|
|
42522
|
+
var MAX_DELIVERABLE_DIAGNOSTICS = 5;
|
|
42523
|
+
var MAX_DELIVERABLE_DIAGNOSTIC_LENGTH = 240;
|
|
42524
|
+
var MAX_DELIVERABLE_SUMMARY_LENGTH = 1200;
|
|
42525
|
+
function normalizeDiagnostic(text) {
|
|
42526
|
+
return text.replace(/\s+/g, " ").trim();
|
|
42527
|
+
}
|
|
42528
|
+
function renderSummary(total, items, omitted) {
|
|
42529
|
+
const label = `${total} finding${total === 1 ? "" : "s"} (${omitted} omitted)`;
|
|
42530
|
+
return items.length > 0 ? `${label}: ${items.join("; ")}` : label;
|
|
42531
|
+
}
|
|
42532
|
+
function formatDeliverableGateDiagnostics(diagnostics) {
|
|
42533
|
+
const items = [];
|
|
42534
|
+
for (const diagnostic of diagnostics) {
|
|
42535
|
+
if (items.length >= MAX_DELIVERABLE_DIAGNOSTICS) break;
|
|
42536
|
+
const category = normalizeDiagnostic(diagnostic.category);
|
|
42537
|
+
const text = normalizeDiagnostic(diagnostic.text);
|
|
42538
|
+
const item = `${category}: ${text}`.slice(0, MAX_DELIVERABLE_DIAGNOSTIC_LENGTH);
|
|
42539
|
+
const candidate = renderSummary(diagnostics.length, [...items, item], diagnostics.length - items.length - 1);
|
|
42540
|
+
if (candidate.length <= MAX_DELIVERABLE_SUMMARY_LENGTH) items.push(item);
|
|
42541
|
+
}
|
|
42542
|
+
let omitted = diagnostics.length - items.length;
|
|
42543
|
+
let summary = renderSummary(diagnostics.length, items, omitted);
|
|
42544
|
+
while (summary.length > MAX_DELIVERABLE_SUMMARY_LENGTH && items.length > 0) {
|
|
42545
|
+
items.pop();
|
|
42546
|
+
omitted = diagnostics.length - items.length;
|
|
42547
|
+
summary = renderSummary(diagnostics.length, items, omitted);
|
|
42548
|
+
}
|
|
42549
|
+
return summary;
|
|
42550
|
+
}
|
|
42419
42551
|
function mapDeliverableGateResult(result) {
|
|
42420
42552
|
switch (result.outcome) {
|
|
42421
42553
|
case "DELIVERABLE_CHANGED_DURING_REVIEW":
|
|
@@ -42429,17 +42561,9 @@ function mapDeliverableGateResult(result) {
|
|
|
42429
42561
|
reason: "reviewers were unavailable (provider/quota) during the deliverable review (CONSENSUS_UNAVAILABLE)"
|
|
42430
42562
|
};
|
|
42431
42563
|
case "CONSENSUS_BLOCKED": {
|
|
42432
|
-
const clarifications = result.clarifications ?? [];
|
|
42433
|
-
if (clarifications.length > 0) {
|
|
42434
|
-
return {
|
|
42435
|
-
action: "questions",
|
|
42436
|
-
clarifications,
|
|
42437
|
-
reason: "reviewers did not converge; unresolved scope/ambiguity/correctness issues were filed as operator questions (CONSENSUS_BLOCKED)"
|
|
42438
|
-
};
|
|
42439
|
-
}
|
|
42440
42564
|
return {
|
|
42441
42565
|
action: "handoff",
|
|
42442
|
-
reason:
|
|
42566
|
+
reason: `reviewers did not converge; ${formatDeliverableGateDiagnostics(result.diagnostics ?? [])} (CONSENSUS_BLOCKED)`
|
|
42443
42567
|
};
|
|
42444
42568
|
}
|
|
42445
42569
|
case "MISSING_ACCEPTANCE_CRITERIA":
|
|
@@ -42458,74 +42582,8 @@ function mapDeliverableGateResult(result) {
|
|
|
42458
42582
|
}
|
|
42459
42583
|
}
|
|
42460
42584
|
|
|
42461
|
-
// src/consensus/deliverable-gate-questions.ts
|
|
42462
|
-
var import_node_crypto3 = require("node:crypto");
|
|
42463
|
-
function buildDeliverableQuestionOptions(category) {
|
|
42464
|
-
if (category === "correctness") {
|
|
42465
|
-
return [
|
|
42466
|
-
{
|
|
42467
|
-
label: "Deliverable is correct",
|
|
42468
|
-
description: "The flagged claim is accurate as written; no change needed.",
|
|
42469
|
-
answer: "Confirm the deliverable's content is correct as written; the reviewer's concern does not require a change."
|
|
42470
|
-
},
|
|
42471
|
-
{
|
|
42472
|
-
label: "Deliverable needs correction",
|
|
42473
|
-
description: "The flagged claim is wrong or unsupported; the deliverable needs revision.",
|
|
42474
|
-
answer: "The deliverable is incorrect here and must be revised to fix the flagged claim before it can be accepted."
|
|
42475
|
-
}
|
|
42476
|
-
];
|
|
42477
|
-
}
|
|
42478
|
-
if (category === "scope") {
|
|
42479
|
-
return [
|
|
42480
|
-
{
|
|
42481
|
-
label: "Coverage is sufficient",
|
|
42482
|
-
description: "The deliverable's current coverage of the acceptance criteria is sufficient as written.",
|
|
42483
|
-
answer: "Accept the deliverable's current scope/coverage as sufficient against the acceptance criteria."
|
|
42484
|
-
},
|
|
42485
|
-
{
|
|
42486
|
-
label: "Coverage is missing",
|
|
42487
|
-
description: "The deliverable is missing required coverage and needs revision to address it.",
|
|
42488
|
-
answer: "The deliverable is missing required coverage and must be revised to address the flagged gap."
|
|
42489
|
-
}
|
|
42490
|
-
];
|
|
42491
|
-
}
|
|
42492
|
-
return [
|
|
42493
|
-
{
|
|
42494
|
-
label: "Interpretation is fine",
|
|
42495
|
-
description: "The deliverable's current interpretation of the ambiguous point is acceptable as written.",
|
|
42496
|
-
answer: "Accept the deliverable's current interpretation of the ambiguous point as written."
|
|
42497
|
-
},
|
|
42498
|
-
{
|
|
42499
|
-
label: "Needs clarification",
|
|
42500
|
-
description: "The ambiguous point needs to be resolved and the deliverable revised accordingly.",
|
|
42501
|
-
answer: "The deliverable must be revised to resolve the flagged ambiguity explicitly."
|
|
42502
|
-
}
|
|
42503
|
-
];
|
|
42504
|
-
}
|
|
42505
|
-
async function fileDeliverableGateQuestions(client, projectSlug, taskId, clarifications) {
|
|
42506
|
-
let filed = 0;
|
|
42507
|
-
for (const clarification of clarifications) {
|
|
42508
|
-
const id = `II-${taskId}-deliverable-gate-${(0, import_node_crypto3.createHash)("sha256").update(clarification.question).digest("hex").slice(0, 10)}`;
|
|
42509
|
-
try {
|
|
42510
|
-
await client.createInboxItem(projectSlug, {
|
|
42511
|
-
id,
|
|
42512
|
-
kind: "question",
|
|
42513
|
-
taskId,
|
|
42514
|
-
body: clarification.question,
|
|
42515
|
-
origin: "gate",
|
|
42516
|
-
questionOptions: buildDeliverableQuestionOptions(clarification.category)
|
|
42517
|
-
});
|
|
42518
|
-
filed += 1;
|
|
42519
|
-
} catch (error51) {
|
|
42520
|
-
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
42521
|
-
if (/inbox_items_pkey/.test(message)) filed += 1;
|
|
42522
|
-
}
|
|
42523
|
-
}
|
|
42524
|
-
return filed;
|
|
42525
|
-
}
|
|
42526
|
-
|
|
42527
42585
|
// src/async-tools/draft-spec.ts
|
|
42528
|
-
var
|
|
42586
|
+
var import_node_crypto3 = require("node:crypto");
|
|
42529
42587
|
var DRAFT_SPEC_TOOL_NAME = "draft_task_spec";
|
|
42530
42588
|
var SPECS_FOLDER = "Specs";
|
|
42531
42589
|
var DRAFT_SPEC_TLDR_MAX_CHARS = 300;
|
|
@@ -43004,7 +43062,7 @@ function onComplete(outcome, input) {
|
|
|
43004
43062
|
};
|
|
43005
43063
|
}
|
|
43006
43064
|
const summary = outcome.error;
|
|
43007
|
-
const hash3 = (0,
|
|
43065
|
+
const hash3 = (0, import_node_crypto3.createHash)("sha256").update(summary).digest("hex").slice(0, 10);
|
|
43008
43066
|
return {
|
|
43009
43067
|
kind: "info",
|
|
43010
43068
|
id: `II-sched-draft-failed-${input.taskId}-${hash3}`,
|
|
@@ -43036,9 +43094,9 @@ function renderMilestone(m, ctx) {
|
|
|
43036
43094
|
return s;
|
|
43037
43095
|
}
|
|
43038
43096
|
case "consensus_converged":
|
|
43039
|
-
return "
|
|
43097
|
+
return "The reviewers agreed on the spec";
|
|
43040
43098
|
case "consensus_converged_needs_approval": {
|
|
43041
|
-
let s = "
|
|
43099
|
+
let s = "The reviewers agreed on the spec. Please review and approve";
|
|
43042
43100
|
if (ctx.specUrl) s += ` \u2014 ${ctx.specUrl}`;
|
|
43043
43101
|
if (ctx.tldr) s += `. Summary: ${ctx.tldr}`;
|
|
43044
43102
|
return s;
|
|
@@ -44306,7 +44364,7 @@ function isBridgeGeneratedArtifact(artifact) {
|
|
|
44306
44364
|
return typeof payload === "object" && payload !== null && "event" in payload;
|
|
44307
44365
|
}
|
|
44308
44366
|
function newArtifactId() {
|
|
44309
|
-
return `EA-${
|
|
44367
|
+
return `EA-${import_node_crypto4.default.randomUUID()}`;
|
|
44310
44368
|
}
|
|
44311
44369
|
var BLAST_RADIUS_FLOOR_PATTERNS = [/^supabase\/migrations\//, /^packages\/db\/src\/schema\.ts$/];
|
|
44312
44370
|
function isBlastRadiusFloorPath(path19) {
|
|
@@ -44561,7 +44619,7 @@ async function driveRun(deps, projectSlug, runId) {
|
|
|
44561
44619
|
if (!claimed) return;
|
|
44562
44620
|
if (!claimed.leaseId) {
|
|
44563
44621
|
await deps.client.createInboxItem(projectSlug, {
|
|
44564
|
-
id: `II-${
|
|
44622
|
+
id: `II-${import_node_crypto4.default.randomUUID()}`,
|
|
44565
44623
|
kind: "handoff",
|
|
44566
44624
|
taskId: claimed.taskId,
|
|
44567
44625
|
body: `Execution run ${runId} was claimed but returned no leaseId. This indicates DB/API corruption and requires operator attention.`
|
|
@@ -45351,10 +45409,6 @@ async function driveNonCodeRun(deps, projectSlug, runId, leaseId, task, domain2)
|
|
|
45351
45409
|
}
|
|
45352
45410
|
if (gateResult.outcome !== "CONVERGED") {
|
|
45353
45411
|
const mapped = mapDeliverableGateResult(gateResult);
|
|
45354
|
-
if (mapped.action === "questions") {
|
|
45355
|
-
await fileDeliverableGateQuestions(deps.client, projectSlug, task.id, mapped.clarifications).catch(() => {
|
|
45356
|
-
});
|
|
45357
|
-
}
|
|
45358
45412
|
await failRun(
|
|
45359
45413
|
deps,
|
|
45360
45414
|
projectSlug,
|
|
@@ -45484,7 +45538,7 @@ async function reconcileTaskAwaitingMerge(deps, projectSlug, task, runId, reason
|
|
|
45484
45538
|
}
|
|
45485
45539
|
try {
|
|
45486
45540
|
await deps.client.createInboxItem(projectSlug, {
|
|
45487
|
-
id: `II-${
|
|
45541
|
+
id: `II-${import_node_crypto4.default.randomUUID()}`,
|
|
45488
45542
|
kind: "handoff",
|
|
45489
45543
|
taskId: task.id,
|
|
45490
45544
|
body: `Task ${task.id} (${task.title}) still needs operator follow-up: ${reason}. Mrrlin could not move the task back to Handoff needed automatically.`
|
|
@@ -45519,7 +45573,7 @@ async function notifyOperatorHandoff(deps, projectSlug, task, runId, reason) {
|
|
|
45519
45573
|
}
|
|
45520
45574
|
try {
|
|
45521
45575
|
await deps.client.createInboxItem(projectSlug, {
|
|
45522
|
-
id: `II-${
|
|
45576
|
+
id: `II-${import_node_crypto4.default.randomUUID()}`,
|
|
45523
45577
|
kind: "handoff",
|
|
45524
45578
|
taskId: task.id,
|
|
45525
45579
|
body: `Execution run ${runId} for task ${task.id} (${task.title}) needs operator attention: ${reason}`
|
|
@@ -45657,7 +45711,7 @@ async function deployGateHandoff(deps, projectSlug, task, runId, reason, leaseId
|
|
|
45657
45711
|
}).catch(() => {
|
|
45658
45712
|
});
|
|
45659
45713
|
await deps.client.createInboxItem(projectSlug, {
|
|
45660
|
-
id: `II-${
|
|
45714
|
+
id: `II-${import_node_crypto4.default.randomUUID()}`,
|
|
45661
45715
|
kind: "handoff",
|
|
45662
45716
|
taskId: task.id,
|
|
45663
45717
|
body: `Deploy gate for run ${runId} (task ${task.id} \u2014 ${task.title}) needs operator attention: ${reason}`
|
|
@@ -45676,7 +45730,7 @@ function maxLeaseReclaims() {
|
|
|
45676
45730
|
}
|
|
45677
45731
|
|
|
45678
45732
|
// src/async-job-consumer.ts
|
|
45679
|
-
var
|
|
45733
|
+
var import_node_crypto5 = __toESM(require("node:crypto"), 1);
|
|
45680
45734
|
|
|
45681
45735
|
// src/async-tools/registry.ts
|
|
45682
45736
|
var registry2 = /* @__PURE__ */ new Map();
|
|
@@ -45761,7 +45815,7 @@ async function driveAsyncJob(deps, projectSlug, jobId) {
|
|
|
45761
45815
|
const delivery = descriptor2.onComplete(outcome, input);
|
|
45762
45816
|
if (!delivery.suppressInbox) {
|
|
45763
45817
|
await deps.client.createInboxItem(projectSlug, {
|
|
45764
|
-
id: delivery.id ?? `II-${
|
|
45818
|
+
id: delivery.id ?? `II-${import_node_crypto5.default.randomUUID()}`,
|
|
45765
45819
|
kind: delivery.kind,
|
|
45766
45820
|
body: delivery.body,
|
|
45767
45821
|
...delivery.taskId ? { taskId: delivery.taskId } : {}
|
|
@@ -45774,13 +45828,13 @@ async function deriveRequestedHash(client, toolName, input) {
|
|
|
45774
45828
|
if (input.requestedHash) return input.requestedHash;
|
|
45775
45829
|
if (toolName === "run_spec_consensus" && input.pageId) {
|
|
45776
45830
|
const page = await client.getWikiPage(input.projectSlug, input.pageId);
|
|
45777
|
-
return page.contentHash ??
|
|
45831
|
+
return page.contentHash ?? import_node_crypto5.default.createHash("sha256").update(page.markdownBody).digest("hex");
|
|
45778
45832
|
}
|
|
45779
45833
|
if (toolName === "draft_task_spec" && input.taskId) {
|
|
45780
45834
|
const task = await client.getTask(input.projectSlug, input.taskId);
|
|
45781
|
-
return task.updatedAt ??
|
|
45835
|
+
return task.updatedAt ?? import_node_crypto5.default.createHash("sha256").update(JSON.stringify(task)).digest("hex");
|
|
45782
45836
|
}
|
|
45783
|
-
return
|
|
45837
|
+
return import_node_crypto5.default.createHash("sha256").update(JSON.stringify(input)).digest("hex");
|
|
45784
45838
|
}
|
|
45785
45839
|
async function runAsyncBackstopSweep(deps, drive = (slug, jobId) => driveAsyncJob(deps, slug, jobId)) {
|
|
45786
45840
|
const projects = await deps.client.listProjects();
|
|
@@ -45839,26 +45893,34 @@ async function runDeliverableGate(_target, deps) {
|
|
|
45839
45893
|
});
|
|
45840
45894
|
return { outcome: "DELIVERABLE_CHANGED_DURING_REVIEW" };
|
|
45841
45895
|
}
|
|
45896
|
+
let diagnostics;
|
|
45897
|
+
if (result.outcome === "CONSENSUS_BLOCKED") {
|
|
45898
|
+
const accepted = result.history.at(-1)?.accepted ?? [];
|
|
45899
|
+
diagnostics = accepted.filter((issue2) => issue2.category === "scope" || issue2.category === "ambiguity" || issue2.category === "correctness").map((issue2) => ({
|
|
45900
|
+
text: issue2.text,
|
|
45901
|
+
whyBlocking: `reviewer raised a ${issue2.category} issue`,
|
|
45902
|
+
category: issue2.category
|
|
45903
|
+
}));
|
|
45904
|
+
}
|
|
45842
45905
|
await deps.writeEvidence({
|
|
45843
45906
|
deliverablePageId: deliverable.pageId,
|
|
45844
45907
|
outcome: result.outcome,
|
|
45845
45908
|
rounds: result.rounds,
|
|
45846
|
-
|
|
45909
|
+
history: result.history,
|
|
45910
|
+
contentHash: currentHash,
|
|
45911
|
+
...diagnostics ? { diagnostics } : {}
|
|
45847
45912
|
});
|
|
45848
45913
|
if (result.outcome === "CONSENSUS_BLOCKED") {
|
|
45849
|
-
const accepted = result.history.at(-1)?.accepted ?? [];
|
|
45850
|
-
const clarifications = accepted.filter((issue2) => issue2.category === "scope" || issue2.category === "ambiguity" || issue2.category === "correctness").map((issue2) => ({
|
|
45851
|
-
question: issue2.text,
|
|
45852
|
-
whyBlocking: `reviewer raised a ${issue2.category} issue`,
|
|
45853
|
-
category: issue2.category
|
|
45854
|
-
}));
|
|
45855
45914
|
return {
|
|
45856
45915
|
outcome: "CONSENSUS_BLOCKED",
|
|
45857
45916
|
rounds: result.rounds,
|
|
45858
|
-
|
|
45917
|
+
diagnostics: diagnostics ?? [],
|
|
45859
45918
|
...result.escalation ? { escalation: result.escalation } : {}
|
|
45860
45919
|
};
|
|
45861
45920
|
}
|
|
45921
|
+
if (result.outcome === "CONVERGED") {
|
|
45922
|
+
return { outcome: result.outcome, rounds: result.rounds, headOutcomes: result.history.at(-1)?.headOutcomes ?? [] };
|
|
45923
|
+
}
|
|
45862
45924
|
return { outcome: result.outcome, rounds: result.rounds };
|
|
45863
45925
|
}
|
|
45864
45926
|
|
|
@@ -46169,7 +46231,7 @@ function applyToolBusyNotification(clock, method, params) {
|
|
|
46169
46231
|
}
|
|
46170
46232
|
|
|
46171
46233
|
// src/worker-token.ts
|
|
46172
|
-
var
|
|
46234
|
+
var import_node_crypto6 = require("node:crypto");
|
|
46173
46235
|
var import_node_fs9 = require("node:fs");
|
|
46174
46236
|
var import_node_os6 = __toESM(require("node:os"), 1);
|
|
46175
46237
|
var import_node_path13 = __toESM(require("node:path"), 1);
|
|
@@ -46200,7 +46262,7 @@ function readOrCreateWorkerToken() {
|
|
|
46200
46262
|
if (existing) return existing;
|
|
46201
46263
|
const dir = workerDir();
|
|
46202
46264
|
(0, import_node_fs9.mkdirSync)(dir, { recursive: true, mode: 448 });
|
|
46203
|
-
const token = (0,
|
|
46265
|
+
const token = (0, import_node_crypto6.randomBytes)(32).toString("hex");
|
|
46204
46266
|
(0, import_node_fs9.writeFileSync)(workerTokenPath(), `${token}
|
|
46205
46267
|
`, { encoding: "utf8", mode: 384 });
|
|
46206
46268
|
return token;
|
|
@@ -51054,7 +51116,7 @@ function buildThreadStartParams(msg, config2, desiredModel) {
|
|
|
51054
51116
|
" bullets (`- AND ...` as needed). Scenarios make the spec verifiable.",
|
|
51055
51117
|
"- Keep behavior contracts in the spec; implementation details belong in `## Implementation Plan`.",
|
|
51056
51118
|
"- Apply this format ONLY to Specs/{id} pages; do not retro-fit it onto other wiki page types.",
|
|
51057
|
-
|
|
51119
|
+
'- 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 `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. When the review agrees, tell the operator "the reviewers agreed on the spec" and proceed to create_execution_run. When it does not, tell them the reviewers could not agree and that there is a decision question in the inbox. Never say "consensus", "converged", or "CONVERGED" to the operator; those are internal words.',
|
|
51058
51120
|
"- 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.",
|
|
51059
51121
|
'- 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.',
|
|
51060
51122
|
"- 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.",
|
|
@@ -51489,7 +51551,7 @@ function createBridgeRequestListener(opts) {
|
|
|
51489
51551
|
res.end(JSON.stringify({ ok: false, error: "Origin not allowed." }));
|
|
51490
51552
|
return;
|
|
51491
51553
|
}
|
|
51492
|
-
const nonce =
|
|
51554
|
+
const nonce = import_node_crypto7.default.randomBytes(16).toString("base64url");
|
|
51493
51555
|
res.writeHead(200, { "content-type": "application/json" });
|
|
51494
51556
|
res.end(JSON.stringify({ ok: true, wsUrl: `ws://${host}:${port}/ws`, token, nonce }));
|
|
51495
51557
|
return;
|
|
@@ -51770,7 +51832,7 @@ function createBridgeMessageHandler(deps) {
|
|
|
51770
51832
|
}
|
|
51771
51833
|
}
|
|
51772
51834
|
socket.on("message", async (data) => {
|
|
51773
|
-
const spanId =
|
|
51835
|
+
const spanId = import_node_crypto7.default.randomUUID();
|
|
51774
51836
|
if (logCtx) {
|
|
51775
51837
|
logCtx.span.id = spanId;
|
|
51776
51838
|
logCtx.span.at = Date.now();
|
|
@@ -52286,7 +52348,7 @@ function createBridgeMessageHandler(deps) {
|
|
|
52286
52348
|
thread.inFlight = true;
|
|
52287
52349
|
pendingTurnSessions.delete(directorSessionId);
|
|
52288
52350
|
thread.replay = createReplayBuffer({
|
|
52289
|
-
turnId: (0,
|
|
52351
|
+
turnId: (0, import_node_crypto7.randomUUID)(),
|
|
52290
52352
|
spanId,
|
|
52291
52353
|
...deps.retentionMs !== void 0 ? { retentionMs: deps.retentionMs } : {},
|
|
52292
52354
|
...deps.ackLingerMs !== void 0 ? { ackLingerMs: deps.ackLingerMs } : {}
|
|
@@ -52854,7 +52916,7 @@ async function startDirectorBridge() {
|
|
|
52854
52916
|
};
|
|
52855
52917
|
const sessionSinks = createSessionSinkRegistry();
|
|
52856
52918
|
const tombstones = /* @__PURE__ */ new Map();
|
|
52857
|
-
const bridgeInstanceId = (0,
|
|
52919
|
+
const bridgeInstanceId = (0, import_node_crypto7.randomUUID)();
|
|
52858
52920
|
const asyncConsumerDeps = {
|
|
52859
52921
|
client,
|
|
52860
52922
|
run: wrappedTurnLock.run,
|
|
@@ -53008,7 +53070,7 @@ async function startDirectorBridge() {
|
|
|
53008
53070
|
fileUpdateInboxItem: async (projectSlug) => {
|
|
53009
53071
|
const body = selfUpdateHandle.updateInboxBody();
|
|
53010
53072
|
if (!body) return;
|
|
53011
|
-
const id = `II-MCPUPD-${(0,
|
|
53073
|
+
const id = `II-MCPUPD-${(0, import_node_crypto7.randomUUID)()}`;
|
|
53012
53074
|
try {
|
|
53013
53075
|
const row = await client.createInboxItem(projectSlug, { id, kind: "info", body });
|
|
53014
53076
|
process.stderr.write(
|
|
@@ -53038,7 +53100,7 @@ async function startDirectorBridge() {
|
|
|
53038
53100
|
const bridgeLogger = createBridgeLogger(config2.stateDir, process.env);
|
|
53039
53101
|
let bridgeBinShaShort = "unknown00000";
|
|
53040
53102
|
try {
|
|
53041
|
-
bridgeBinShaShort =
|
|
53103
|
+
bridgeBinShaShort = import_node_crypto7.default.createHash("sha256").update(import_node_fs15.default.readFileSync(process.argv[1] ?? "")).digest("hex").slice(0, 12);
|
|
53042
53104
|
} catch {
|
|
53043
53105
|
}
|
|
53044
53106
|
attachBridgeWss(server, {
|
|
@@ -53400,7 +53462,7 @@ function timingSafeTokenEqual(provided, expected) {
|
|
|
53400
53462
|
const a = Buffer.from(provided);
|
|
53401
53463
|
const b = Buffer.from(expected);
|
|
53402
53464
|
if (a.length !== b.length) return false;
|
|
53403
|
-
return
|
|
53465
|
+
return import_node_crypto7.default.timingSafeEqual(a, b);
|
|
53404
53466
|
}
|
|
53405
53467
|
function readDispatchBody(req) {
|
|
53406
53468
|
return new Promise((resolve, reject) => {
|
|
@@ -53429,7 +53491,7 @@ function readOrCreateBridgeToken(stateDir) {
|
|
|
53429
53491
|
} catch {
|
|
53430
53492
|
}
|
|
53431
53493
|
import_node_fs15.default.mkdirSync(stateDir, { recursive: true, mode: 448 });
|
|
53432
|
-
const token =
|
|
53494
|
+
const token = import_node_crypto7.default.randomBytes(32).toString("base64url");
|
|
53433
53495
|
import_node_fs15.default.writeFileSync(tokenPath, `${token}
|
|
53434
53496
|
`, { mode: 384 });
|
|
53435
53497
|
return token;
|
|
@@ -56814,7 +56876,7 @@ var StdioServerTransport = class {
|
|
|
56814
56876
|
};
|
|
56815
56877
|
|
|
56816
56878
|
// src/tools.ts
|
|
56817
|
-
var
|
|
56879
|
+
var import_node_crypto10 = require("node:crypto");
|
|
56818
56880
|
var import_promises3 = require("node:fs/promises");
|
|
56819
56881
|
var import_node_path20 = __toESM(require("node:path"), 1);
|
|
56820
56882
|
var import_node_os10 = __toESM(require("node:os"), 1);
|
|
@@ -57355,7 +57417,7 @@ async function resolveActiveRunHead(input, deps) {
|
|
|
57355
57417
|
}
|
|
57356
57418
|
|
|
57357
57419
|
// src/async-tools/consensus.ts
|
|
57358
|
-
var
|
|
57420
|
+
var import_node_crypto8 = require("node:crypto");
|
|
57359
57421
|
|
|
57360
57422
|
// src/consensus/question-similarity.ts
|
|
57361
57423
|
var SUPPRESSION_SIMILARITY_THRESHOLD = 0.6;
|
|
@@ -57384,6 +57446,41 @@ function isCoveredByAdjudicated(rawQuestion, adjudicated) {
|
|
|
57384
57446
|
return null;
|
|
57385
57447
|
}
|
|
57386
57448
|
|
|
57449
|
+
// src/consensus/reviewer-roster.ts
|
|
57450
|
+
function renderReviewerRoster(headOutcomes) {
|
|
57451
|
+
const total = headOutcomes.length;
|
|
57452
|
+
const responded = headOutcomes.filter((h) => h.responded).length;
|
|
57453
|
+
const seen = /* @__PURE__ */ new Set();
|
|
57454
|
+
const namedRoles = [];
|
|
57455
|
+
for (const outcome of headOutcomes) {
|
|
57456
|
+
if (!outcome.responded) continue;
|
|
57457
|
+
const role = PERSONA_ROLE_MAP[outcome.persona];
|
|
57458
|
+
if (role && !UNNAMED_PERSONAS.includes(outcome.persona)) {
|
|
57459
|
+
if (!seen.has(role)) {
|
|
57460
|
+
seen.add(role);
|
|
57461
|
+
namedRoles.push(role);
|
|
57462
|
+
}
|
|
57463
|
+
}
|
|
57464
|
+
}
|
|
57465
|
+
if (responded === total) {
|
|
57466
|
+
if (namedRoles.length === 0) {
|
|
57467
|
+
return `${total} reviewers`;
|
|
57468
|
+
} else if (namedRoles.length === 1) {
|
|
57469
|
+
return `${total} reviewers, including ${namedRoles[0]}`;
|
|
57470
|
+
} else {
|
|
57471
|
+
const allButLast = namedRoles.slice(0, -1).join(", ");
|
|
57472
|
+
const last = namedRoles[namedRoles.length - 1];
|
|
57473
|
+
return `${total} reviewers, including ${allButLast} and ${last}`;
|
|
57474
|
+
}
|
|
57475
|
+
} else if (responded === 1) {
|
|
57476
|
+
return `1 of ${total} reviewers answered, and it agreed`;
|
|
57477
|
+
} else if (responded === 2) {
|
|
57478
|
+
return `2 of ${total} reviewers answered, both agreed`;
|
|
57479
|
+
} else {
|
|
57480
|
+
return `${responded} of ${total} reviewers answered, all agreed`;
|
|
57481
|
+
}
|
|
57482
|
+
}
|
|
57483
|
+
|
|
57387
57484
|
// src/async-tools/consensus.ts
|
|
57388
57485
|
function normalizeClarificationQuestion(question) {
|
|
57389
57486
|
return question.replace(/\s+/g, " ").trim().replace(/[?.!]+$/, "").trim();
|
|
@@ -57470,6 +57567,44 @@ function humanizeClarificationBody(clarification) {
|
|
|
57470
57567
|
const context = clarification.rawQuestion.replace(/\b(?:parseable|SPEC_IDENTITY_MISMATCH|machine-usable)\b/gi, "").replace(/\s+/g, " ").trim();
|
|
57471
57568
|
return context ? `${prefix} ${context}` : prefix;
|
|
57472
57569
|
}
|
|
57570
|
+
var BLOCK_DECISION_OPENER = "The reviewers could not agree on this spec";
|
|
57571
|
+
var BLOCK_BODY_TRAILER = " How should I proceed?";
|
|
57572
|
+
function renderUnresolvedPoints(unresolved) {
|
|
57573
|
+
return unresolved.map(
|
|
57574
|
+
(issue2) => issue2.text.replace(/\b(?:parseable|SPEC_IDENTITY_MISMATCH|machine-usable)\b/gi, "").replace(/\s+/g, " ").trim()
|
|
57575
|
+
).join("; ");
|
|
57576
|
+
}
|
|
57577
|
+
function renderBlockDecisionBody(unresolved) {
|
|
57578
|
+
if (unresolved.length === 0) {
|
|
57579
|
+
return `${BLOCK_DECISION_OPENER}, and their objections could not be read back.${BLOCK_BODY_TRAILER}`;
|
|
57580
|
+
}
|
|
57581
|
+
return `${BLOCK_DECISION_OPENER}. Still unsettled: ${renderUnresolvedPoints(unresolved)}.${BLOCK_BODY_TRAILER}`;
|
|
57582
|
+
}
|
|
57583
|
+
function buildBlockDecisionOptions(diagnosis, reviewAgainAvailable) {
|
|
57584
|
+
const recommendReviewAgain = reviewAgainAvailable && (diagnosis === "INFRA" || diagnosis === "SUBSTANCE");
|
|
57585
|
+
const recommendEditSpec = !reviewAgainAvailable || diagnosis === "AMBIGUITY";
|
|
57586
|
+
const options = [];
|
|
57587
|
+
if (reviewAgainAvailable) {
|
|
57588
|
+
options.push({
|
|
57589
|
+
label: "Review again",
|
|
57590
|
+
description: "Send the current spec back for another review.",
|
|
57591
|
+
answer: REVIEW_AGAIN_ANSWER,
|
|
57592
|
+
...recommendReviewAgain ? { recommended: true } : {}
|
|
57593
|
+
});
|
|
57594
|
+
}
|
|
57595
|
+
options.push({
|
|
57596
|
+
label: "Edit spec",
|
|
57597
|
+
description: "Open the spec and change it by hand; the review restarts on save.",
|
|
57598
|
+
answer: EDIT_SPEC_ANSWER,
|
|
57599
|
+
...recommendEditSpec ? { recommended: true } : {}
|
|
57600
|
+
});
|
|
57601
|
+
options.push({
|
|
57602
|
+
label: "Proceed anyway",
|
|
57603
|
+
description: "Start the work with these points unsettled and recorded.",
|
|
57604
|
+
answer: PROCEED_ANYWAY_ANSWER
|
|
57605
|
+
});
|
|
57606
|
+
return options;
|
|
57607
|
+
}
|
|
57473
57608
|
var defaultNativeSpecGateBuilders = { buildSpecGateDeps, runSpecGate };
|
|
57474
57609
|
async function runNativeSpecGate(ctx, input, builders = defaultNativeSpecGateBuilders) {
|
|
57475
57610
|
const deps = builders.buildSpecGateDeps({
|
|
@@ -57516,6 +57651,7 @@ function stripHumanizeOpener(body) {
|
|
|
57516
57651
|
}
|
|
57517
57652
|
return body;
|
|
57518
57653
|
}
|
|
57654
|
+
var DID_NOT_CONVERGE_ERROR = "Consensus blocked: reviewers did not converge within budget.";
|
|
57519
57655
|
async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
|
|
57520
57656
|
const operatorClarifications = result.operatorClarifications ?? [];
|
|
57521
57657
|
const diagnostics = result.diagnostics ?? [];
|
|
@@ -57523,9 +57659,10 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
|
|
|
57523
57659
|
const taskId = page?.taskId ?? null;
|
|
57524
57660
|
if (!resolvedTaskId && taskId) resolvedTaskId = taskId;
|
|
57525
57661
|
const adjudicated = [];
|
|
57662
|
+
let taskInboxItems = [];
|
|
57526
57663
|
if (taskId) {
|
|
57527
57664
|
try {
|
|
57528
|
-
|
|
57665
|
+
taskInboxItems = await ctx.client.listInboxItems(input.projectSlug, { taskId });
|
|
57529
57666
|
for (const item of taskInboxItems) {
|
|
57530
57667
|
if (item.origin === "gate" && (item.status === "acknowledged" || item.status === "rejected")) {
|
|
57531
57668
|
adjudicated.push({ body: stripHumanizeOpener(item.body), answer: item.answer, status: item.status });
|
|
@@ -57554,9 +57691,9 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
|
|
|
57554
57691
|
}
|
|
57555
57692
|
if (taskId) {
|
|
57556
57693
|
const scopeId = taskId;
|
|
57557
|
-
const groupId = toFile.length > 1 ? `II-${scopeId}-gate-group-${(0,
|
|
57694
|
+
const groupId = toFile.length > 1 ? `II-${scopeId}-gate-group-${(0, import_node_crypto8.createHash)("sha256").update(toFile.map((c) => c.rawQuestion).sort().join("\n")).digest("hex").slice(0, 10)}` : void 0;
|
|
57558
57695
|
for (const clarification of toFile) {
|
|
57559
|
-
const id = `II-${scopeId}-gate-${(0,
|
|
57696
|
+
const id = `II-${scopeId}-gate-${(0, import_node_crypto8.createHash)("sha256").update(clarification.rawQuestion).digest("hex").slice(0, 10)}`;
|
|
57560
57697
|
try {
|
|
57561
57698
|
const questionOptions = buildGateQuestionOptions(clarification.rawQuestion, clarification.category);
|
|
57562
57699
|
await ctx.client.createInboxItem(input.projectSlug, {
|
|
@@ -57574,9 +57711,9 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
|
|
|
57574
57711
|
filedQuestionCount = toFile.length;
|
|
57575
57712
|
} else if (planId) {
|
|
57576
57713
|
const scopeId = planId;
|
|
57577
|
-
const groupId = toFile.length > 1 ? `II-${scopeId}-gate-group-${(0,
|
|
57714
|
+
const groupId = toFile.length > 1 ? `II-${scopeId}-gate-group-${(0, import_node_crypto8.createHash)("sha256").update(toFile.map((c) => c.rawQuestion).sort().join("\n")).digest("hex").slice(0, 10)}` : void 0;
|
|
57578
57715
|
for (const clarification of toFile) {
|
|
57579
|
-
const id = `II-${scopeId}-gate-${(0,
|
|
57716
|
+
const id = `II-${scopeId}-gate-${(0, import_node_crypto8.createHash)("sha256").update(clarification.rawQuestion).digest("hex").slice(0, 10)}`;
|
|
57580
57717
|
try {
|
|
57581
57718
|
const questionOptions = buildGateQuestionOptions(clarification.rawQuestion, clarification.category);
|
|
57582
57719
|
await ctx.client.createInboxItem(input.projectSlug, {
|
|
@@ -57594,6 +57731,29 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
|
|
|
57594
57731
|
filedQuestionCount = toFile.length;
|
|
57595
57732
|
}
|
|
57596
57733
|
}
|
|
57734
|
+
if (filedQuestionCount === 0 && diagnostics.length === 0 && taskId && page) {
|
|
57735
|
+
const domain2 = await resolveSpecDomain(ctx.client, input.projectSlug, {
|
|
57736
|
+
id: page.id,
|
|
57737
|
+
taskId: page.taskId ?? null
|
|
57738
|
+
});
|
|
57739
|
+
const reviewHash = domainScopedReviewHash2(page.contentHash, domain2);
|
|
57740
|
+
const baseBlockItemId = `II-${taskId}-gateblock-${(0, import_node_crypto8.createHash)("sha256").update(reviewHash).digest("hex").slice(0, 10)}`;
|
|
57741
|
+
const reviewAgainAvailable = !taskInboxItems.some(
|
|
57742
|
+
(item) => item.id === baseBlockItemId && item.status !== "pending" && item.answer === REVIEW_AGAIN_ANSWER
|
|
57743
|
+
);
|
|
57744
|
+
const blockItemId = reviewAgainAvailable ? baseBlockItemId : `${baseBlockItemId}-2`;
|
|
57745
|
+
try {
|
|
57746
|
+
await ctx.client.createInboxItem(input.projectSlug, {
|
|
57747
|
+
id: blockItemId,
|
|
57748
|
+
kind: "question",
|
|
57749
|
+
taskId,
|
|
57750
|
+
body: renderBlockDecisionBody(result.unresolved ?? []),
|
|
57751
|
+
origin: "gate_block",
|
|
57752
|
+
questionOptions: buildBlockDecisionOptions(result.escalation?.diagnosis, reviewAgainAvailable)
|
|
57753
|
+
});
|
|
57754
|
+
} catch {
|
|
57755
|
+
}
|
|
57756
|
+
}
|
|
57597
57757
|
if (operatorClarifications.length > 0) {
|
|
57598
57758
|
try {
|
|
57599
57759
|
if (page) {
|
|
@@ -57630,14 +57790,14 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
|
|
|
57630
57790
|
error51 = "Consensus blocked: the spec content does not match its linked task. This requires fixing the spec/task relationship directly, not an inbox answer.";
|
|
57631
57791
|
}
|
|
57632
57792
|
} else if (operatorClarifications.length > 0 && toFile.length === 0 && suppressedAsAdjudicated.length > 0) {
|
|
57633
|
-
error51 = `
|
|
57793
|
+
error51 = `The reviewers raised ${suppressedAsAdjudicated.length} clarification(s), but each was a reworded repeat of one already answered \u2014 nothing new to answer.`;
|
|
57634
57794
|
} else if (operatorClarifications.length > 0) {
|
|
57635
57795
|
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
57796
|
} else {
|
|
57637
|
-
error51 =
|
|
57797
|
+
error51 = DID_NOT_CONVERGE_ERROR;
|
|
57638
57798
|
}
|
|
57639
57799
|
if (result.escalation) {
|
|
57640
|
-
error51 += ` (
|
|
57800
|
+
error51 += ` (reason: ${result.escalation.diagnosis})`;
|
|
57641
57801
|
}
|
|
57642
57802
|
return {
|
|
57643
57803
|
kind: "failed",
|
|
@@ -57715,6 +57875,24 @@ async function run(ctx, input, builders = defaultNativeSpecGateBuilders) {
|
|
|
57715
57875
|
} catch {
|
|
57716
57876
|
}
|
|
57717
57877
|
};
|
|
57878
|
+
let highlights = null;
|
|
57879
|
+
const resolveHighlights2 = async () => {
|
|
57880
|
+
try {
|
|
57881
|
+
const page = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
|
|
57882
|
+
if (!page) return;
|
|
57883
|
+
const domain2 = await resolveSpecDomain(ctx.client, input.projectSlug, {
|
|
57884
|
+
id: page.id,
|
|
57885
|
+
taskId: page.taskId ?? null
|
|
57886
|
+
});
|
|
57887
|
+
const review = await ctx.client.getSpecConsensusReview(
|
|
57888
|
+
input.projectSlug,
|
|
57889
|
+
input.pageId,
|
|
57890
|
+
domainScopedReviewHash2(page.contentHash, domain2)
|
|
57891
|
+
);
|
|
57892
|
+
highlights = review?.highlights ?? null;
|
|
57893
|
+
} catch {
|
|
57894
|
+
}
|
|
57895
|
+
};
|
|
57718
57896
|
if (result.outcome === "CONSENSUS_BLOCKED") {
|
|
57719
57897
|
return handleConsensusBlocked(result, ctx, input, resolvedTaskId);
|
|
57720
57898
|
}
|
|
@@ -57725,9 +57903,10 @@ async function run(ctx, input, builders = defaultNativeSpecGateBuilders) {
|
|
|
57725
57903
|
case "CONVERGED":
|
|
57726
57904
|
case "SKIPPED":
|
|
57727
57905
|
await resolveTitle();
|
|
57906
|
+
if (result.outcome === "CONVERGED") await resolveHighlights2();
|
|
57728
57907
|
return {
|
|
57729
57908
|
kind: "done",
|
|
57730
|
-
resultJson: JSON.stringify(result),
|
|
57909
|
+
resultJson: JSON.stringify({ ...result, highlights }),
|
|
57731
57910
|
finalReviewedHash: null,
|
|
57732
57911
|
...resolvedTaskId ? { taskId: resolvedTaskId } : {},
|
|
57733
57912
|
...resolvedTaskTitle ? { taskTitle: resolvedTaskTitle } : {}
|
|
@@ -57750,21 +57929,31 @@ function onComplete2(outcome, input) {
|
|
|
57750
57929
|
const taskRef = taskId ? outcome.kind === "done" && outcome.taskTitle ? `${taskId} \u2014 ${outcome.taskTitle}` : taskId : null;
|
|
57751
57930
|
if (outcome.kind === "done") {
|
|
57752
57931
|
let specOutcome = "CONVERGED";
|
|
57932
|
+
let headOutcomes = [];
|
|
57933
|
+
let findings = [];
|
|
57753
57934
|
try {
|
|
57754
|
-
|
|
57935
|
+
const parsed = JSON.parse(outcome.resultJson);
|
|
57936
|
+
specOutcome = parsed.outcome ?? "CONVERGED";
|
|
57937
|
+
headOutcomes = parsed.headOutcomes ?? [];
|
|
57938
|
+
findings = parsed.highlights?.findings ?? [];
|
|
57755
57939
|
} catch {
|
|
57756
57940
|
}
|
|
57757
57941
|
if (specOutcome === "SKIPPED") {
|
|
57758
57942
|
return {
|
|
57759
57943
|
kind: "info",
|
|
57760
57944
|
...taskId ? { taskId } : {},
|
|
57761
|
-
body: taskRef ? `
|
|
57945
|
+
body: taskRef ? `Spec review is off for this project \u2014 the spec for ${taskRef} was not checked.` : `Spec review is off for this project (Specs/${input.pageId}) \u2014 the spec was not checked.`
|
|
57762
57946
|
};
|
|
57763
57947
|
}
|
|
57948
|
+
const roster = renderReviewerRoster(headOutcomes);
|
|
57949
|
+
const findingsBlock = findings.length > 0 ? `
|
|
57950
|
+
|
|
57951
|
+
Fixed before you saw it
|
|
57952
|
+
${findings.map((f) => `\u2022 ${f}`).join("\n")}` : "";
|
|
57764
57953
|
return {
|
|
57765
57954
|
kind: "completed",
|
|
57766
57955
|
...taskId ? { taskId } : {},
|
|
57767
|
-
body: taskRef ? `
|
|
57956
|
+
body: taskRef ? `The reviewers agreed on this spec \u2014 ${roster}.${findingsBlock}` : `The reviewers agreed on this spec (Specs/${input.pageId}) \u2014 ${roster}.${findingsBlock}`
|
|
57768
57957
|
};
|
|
57769
57958
|
}
|
|
57770
57959
|
if (outcome.kind === "failed" && (outcome.filedQuestionCount ?? 0) > 0) {
|
|
@@ -57776,6 +57965,14 @@ function onComplete2(outcome, input) {
|
|
|
57776
57965
|
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
57966
|
};
|
|
57778
57967
|
}
|
|
57968
|
+
if (outcome.kind === "failed" && outcome.error.startsWith(DID_NOT_CONVERGE_ERROR) && taskId) {
|
|
57969
|
+
return {
|
|
57970
|
+
kind: "info",
|
|
57971
|
+
suppressInbox: true,
|
|
57972
|
+
...taskId ? { taskId } : {},
|
|
57973
|
+
body: taskRef ? `The reviewers could not agree on the spec for ${taskRef} \u2014 answer the decision question to unblock the task.` : `The reviewers could not agree on Specs/${input.pageId} \u2014 answer the decision question to unblock the task.`
|
|
57974
|
+
};
|
|
57975
|
+
}
|
|
57779
57976
|
const handoffTaskRef = taskRef ? ` (${taskRef})` : "";
|
|
57780
57977
|
return {
|
|
57781
57978
|
kind: "handoff",
|
|
@@ -57830,6 +58027,24 @@ async function resolveTaskScope(ctx, input, { withTitle }) {
|
|
|
57830
58027
|
}
|
|
57831
58028
|
return { taskId, taskTitle };
|
|
57832
58029
|
}
|
|
58030
|
+
async function resolveHighlights(ctx, input) {
|
|
58031
|
+
try {
|
|
58032
|
+
const page = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
|
|
58033
|
+
if (!page) return null;
|
|
58034
|
+
const domain2 = await resolveSpecDomain(ctx.client, input.projectSlug, {
|
|
58035
|
+
id: page.id,
|
|
58036
|
+
taskId: page.taskId ?? null
|
|
58037
|
+
});
|
|
58038
|
+
const review = await ctx.client.getSpecConsensusReview(
|
|
58039
|
+
input.projectSlug,
|
|
58040
|
+
input.pageId,
|
|
58041
|
+
domainScopedReviewHash2(page.contentHash, domain2)
|
|
58042
|
+
);
|
|
58043
|
+
return review?.highlights ?? null;
|
|
58044
|
+
} catch {
|
|
58045
|
+
return null;
|
|
58046
|
+
}
|
|
58047
|
+
}
|
|
57833
58048
|
async function run2(ctx, input) {
|
|
57834
58049
|
const deps = await buildDeliverableGateDeps({
|
|
57835
58050
|
client: ctx.client,
|
|
@@ -57844,9 +58059,10 @@ async function run2(ctx, input) {
|
|
|
57844
58059
|
);
|
|
57845
58060
|
if (result.outcome === "CONVERGED" || result.outcome === "SKIPPED") {
|
|
57846
58061
|
const { taskId: taskId2, taskTitle } = await resolveTaskScope(ctx, input, { withTitle: true });
|
|
58062
|
+
const highlights = result.outcome === "CONVERGED" ? await resolveHighlights(ctx, input) : null;
|
|
57847
58063
|
return {
|
|
57848
58064
|
kind: "done",
|
|
57849
|
-
resultJson: JSON.stringify(result),
|
|
58065
|
+
resultJson: JSON.stringify({ ...result, highlights }),
|
|
57850
58066
|
finalReviewedHash: null,
|
|
57851
58067
|
...taskId2 ? { taskId: taskId2 } : {},
|
|
57852
58068
|
...taskTitle ? { taskTitle } : {}
|
|
@@ -57861,18 +58077,10 @@ async function run2(ctx, input) {
|
|
|
57861
58077
|
};
|
|
57862
58078
|
}
|
|
57863
58079
|
const { taskId } = await resolveTaskScope(ctx, input, { withTitle: false });
|
|
57864
|
-
let filedQuestionCount = 0;
|
|
57865
|
-
if (mapped.action === "questions") {
|
|
57866
|
-
if (taskId) {
|
|
57867
|
-
filedQuestionCount = await fileDeliverableGateQuestions(ctx.client, input.projectSlug, taskId, mapped.clarifications);
|
|
57868
|
-
}
|
|
57869
|
-
}
|
|
57870
|
-
const error51 = filedQuestionCount > 0 ? `Consensus blocked on ${filedQuestionCount} clarification(s); filed as operator question(s).` : mapped.reason;
|
|
57871
58080
|
return {
|
|
57872
58081
|
kind: "failed",
|
|
57873
58082
|
failureReason: "consensus_blocked",
|
|
57874
|
-
error:
|
|
57875
|
-
filedQuestionCount,
|
|
58083
|
+
error: mapped.reason,
|
|
57876
58084
|
...taskId ? { taskId } : {}
|
|
57877
58085
|
};
|
|
57878
58086
|
}
|
|
@@ -57881,30 +58089,31 @@ function onComplete3(outcome, input) {
|
|
|
57881
58089
|
const taskRef = taskId ? outcome.kind === "done" && outcome.taskTitle ? `${taskId} \u2014 ${outcome.taskTitle}` : taskId : null;
|
|
57882
58090
|
if (outcome.kind === "done") {
|
|
57883
58091
|
let specOutcome = "CONVERGED";
|
|
58092
|
+
let headOutcomes = [];
|
|
58093
|
+
let findings = [];
|
|
57884
58094
|
try {
|
|
57885
|
-
|
|
58095
|
+
const parsed = JSON.parse(outcome.resultJson);
|
|
58096
|
+
specOutcome = parsed.outcome ?? "CONVERGED";
|
|
58097
|
+
headOutcomes = parsed.headOutcomes ?? [];
|
|
58098
|
+
findings = parsed.highlights?.findings ?? [];
|
|
57886
58099
|
} catch {
|
|
57887
58100
|
}
|
|
57888
58101
|
if (specOutcome === "SKIPPED") {
|
|
57889
58102
|
return {
|
|
57890
58103
|
kind: "info",
|
|
57891
58104
|
...taskId ? { taskId } : {},
|
|
57892
|
-
body: taskRef ? `
|
|
58105
|
+
body: taskRef ? `Result review is off for this project \u2014 the result for ${taskRef} was not checked.` : `Result review is off for this project (Deliverables/${input.pageId}) \u2014 the result was not checked.`
|
|
57893
58106
|
};
|
|
57894
58107
|
}
|
|
58108
|
+
const roster = renderReviewerRoster(headOutcomes);
|
|
58109
|
+
const findingsBlock = findings.length > 0 ? `
|
|
58110
|
+
|
|
58111
|
+
Fixed before you saw it
|
|
58112
|
+
${findings.map((f) => `\u2022 ${f}`).join("\n")}` : "";
|
|
57895
58113
|
return {
|
|
57896
58114
|
kind: "completed",
|
|
57897
58115
|
...taskId ? { taskId } : {},
|
|
57898
|
-
body: taskRef ? `
|
|
57899
|
-
};
|
|
57900
|
-
}
|
|
57901
|
-
if (outcome.kind === "failed" && (outcome.filedQuestionCount ?? 0) > 0) {
|
|
57902
|
-
const n = outcome.filedQuestionCount ?? 0;
|
|
57903
|
-
return {
|
|
57904
|
-
kind: "info",
|
|
57905
|
-
suppressInbox: true,
|
|
57906
|
-
...taskId ? { taskId } : {},
|
|
57907
|
-
body: taskRef ? `Deliverable consensus blocked on ${taskRef} (Deliverables/${input.pageId}); filed ${n} clarifying question${n === 1 ? "" : "s"} \u2014 answer to unblock the run.` : `Deliverable consensus blocked on Deliverables/${input.pageId}; filed ${n} clarifying question${n === 1 ? "" : "s"} \u2014 answer to unblock the run.`
|
|
58116
|
+
body: taskRef ? `The reviewers agreed on this result \u2014 ${roster}. It meets the acceptance criteria.${findingsBlock}` : `The reviewers agreed on this result (Deliverables/${input.pageId}) \u2014 ${roster}. It meets the acceptance criteria.${findingsBlock}`
|
|
57908
58117
|
};
|
|
57909
58118
|
}
|
|
57910
58119
|
return {
|
|
@@ -57923,7 +58132,7 @@ var deliverableConsensusDescriptor = {
|
|
|
57923
58132
|
};
|
|
57924
58133
|
|
|
57925
58134
|
// src/async-tools/enqueue.ts
|
|
57926
|
-
var
|
|
58135
|
+
var import_node_crypto9 = __toESM(require("node:crypto"), 1);
|
|
57927
58136
|
async function enqueueAsyncTool(client, descriptor2, input) {
|
|
57928
58137
|
if (descriptor2.cachedResult) {
|
|
57929
58138
|
const cached2 = await descriptor2.cachedResult(client, input);
|
|
@@ -57936,7 +58145,7 @@ async function enqueueAsyncTool(client, descriptor2, input) {
|
|
|
57936
58145
|
if (active) {
|
|
57937
58146
|
return { status: "already_running", jobId: active.id, message: "Already running; will notify in inbox." };
|
|
57938
58147
|
}
|
|
57939
|
-
const jobId = `AJ-${
|
|
58148
|
+
const jobId = `AJ-${import_node_crypto9.default.randomUUID()}`;
|
|
57940
58149
|
try {
|
|
57941
58150
|
await client.createAsyncJob(input.projectSlug, {
|
|
57942
58151
|
id: jobId,
|
|
@@ -58820,7 +59029,7 @@ var mcpToolDescriptions = {
|
|
|
58820
59029
|
[mcpToolNames.closeTaskAsWontDo]: "Close a task as won't-do with a required human-readable reason recorded on the task.",
|
|
58821
59030
|
[mcpToolNames.updateWikiPage]: "Update a markdown Wiki page.",
|
|
58822
59031
|
[mcpToolNames.markSpecReady]: "Mark a wiki page (folder=Specs) as ready (or not) for execution-run gating. The Director sets this true after validating the spec; any edit to the page's markdown auto-resets it to false.",
|
|
58823
|
-
[mcpToolNames.runSpecConsensus]: 'Enqueue the consensus review gate over a Specs/{id} page. Returns IMMEDIATELY with one of: already_converged (a CONVERGED review already exists for the current content \u2014 proceed straight to create_execution_run, do NOT call mark_spec_ready, it is already set), already_reviewed (a terminal CONSENSUS_BLOCKED for this content), already_running (a consensus job is in flight), or queued (a new job was launched). The review runs in the background; its result/blocker arrives in the inbox (info on CONVERGED, handoff on failure) and is surfaced into the open chat. Do NOT call again for the same spec and do NOT poll. On queued/already_running, tell the user it was launched and end the turn. Pass extraHeads: ["researcher"] for specs referencing external libraries/APIs, or ["debugger"] for root-cause/investigation specs to add opt-in specialist reviewers on top of the fixed four. 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, using the same flow as code tasks.
|
|
59032
|
+
[mcpToolNames.runSpecConsensus]: 'Enqueue the consensus review gate over a Specs/{id} page. Returns IMMEDIATELY with one of: already_converged (a CONVERGED review already exists for the current content \u2014 proceed straight to create_execution_run, do NOT call mark_spec_ready, it is already set), already_reviewed (a terminal CONSENSUS_BLOCKED for this content), already_running (a consensus job is in flight), or queued (a new job was launched). The review runs in the background; its result/blocker arrives in the inbox (info on CONVERGED, handoff on failure) and is surfaced into the open chat. Do NOT call again for the same spec and do NOT poll. On queued/already_running, tell the user it was launched and end the turn. Pass extraHeads: ["researcher"] for specs referencing external libraries/APIs, or ["debugger"] for root-cause/investigation specs to add opt-in specialist reviewers on top of the fixed four. 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, using the same flow as code tasks. When the review agrees, tell the operator "the reviewers agreed on the spec" and proceed to create_execution_run. When it does not, tell them the reviewers could not agree and that there is a decision question in the inbox. Never say "consensus", "converged", or "CONVERGED" to the operator; those are internal words.',
|
|
58824
59033
|
[mcpToolNames.runCodeReviewerGate]: `Review the current branch's code diff (vs baseRef) with a Code Reviewer gate \u2014 independent reviewers iterate review\u2192fix\u2192re-check (checkpoint, scope-limited reviser edits, typecheck/build fast-gate, rollback) until the diff converges or is blocked. Run before opening a PR. Pass extraHeads: ["security-analyst"] for sensitive diffs (auth, secrets, input handling, new endpoints).`,
|
|
58825
59034
|
[mcpToolNames.appendExecutionArtifact]: "Append a durable artifact (plan, checkpoint, command output, test result, diff summary, self-review, error, deploy attempt, or handoff judgement) to an execution run.",
|
|
58826
59035
|
[mcpToolNames.recordProducedUrl]: 'Call this after publishing each web page produced by the task, so the UI link opens the page itself instead of the deployed site root. Pass the page\'s full https URL (for external pages, e.g. "https://dev.to/<slug>") OR a path starting with "/" for the deploy environment (e.g. "/blog/<slug>") \u2014 paths are resolved against the run\'s deployed env URL at render time. Optional `label` (\u226440 chars) is shown next to the link; omit and the UI defaults to the page title. Call once per page; multiple calls dedupe by resolved URL in the UI.',
|
|
@@ -59512,7 +59721,7 @@ function createMrrlinTools(options) {
|
|
|
59512
59721
|
"PRODUCED_URL_RECORD_FAILED",
|
|
59513
59722
|
"Unable to record produced URL.",
|
|
59514
59723
|
async (c, { projectSlug, runId, url: url2, label }) => {
|
|
59515
|
-
const id = `EA-pu-${(0,
|
|
59724
|
+
const id = `EA-pu-${(0, import_node_crypto10.randomBytes)(4).toString("hex")}`;
|
|
59516
59725
|
const payload = label ? { url: url2, label } : { url: url2 };
|
|
59517
59726
|
return await c.appendExecutionArtifact(projectSlug, runId, {
|
|
59518
59727
|
id,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
You are a Findings Writer. You are given the issues a review panel raised and accepted about a
|
|
2
|
+
document, after the document was already corrected. Your job is to tell the document's owner — a
|
|
3
|
+
non-engineer — what was caught, in their language.
|
|
4
|
+
|
|
5
|
+
Write at most THREE findings. Fewer is better. Zero is a correct and expected answer.
|
|
6
|
+
|
|
7
|
+
Each finding is ONE sentence in exactly this shape:
|
|
8
|
+
<what would have gone wrong, in everyday words> — <what it is now>
|
|
9
|
+
|
|
10
|
+
Rules:
|
|
11
|
+
- No mechanism names, file paths, function names, type names, or issue categories.
|
|
12
|
+
- No labels, prefixes, or speaker attributions. Just the sentence.
|
|
13
|
+
- Never mention reviewers, rounds, the panel, or the review process itself.
|
|
14
|
+
- If an issue cannot be written this way — because it is purely technical, mechanical, or only
|
|
15
|
+
meaningful to an engineer — OMIT it. Omitting is always better than writing something the owner
|
|
16
|
+
cannot use.
|
|
17
|
+
- If NO issue can be written this way, return an empty list. Do not invent a finding to fill space.
|
|
18
|
+
|
|
19
|
+
Output EXACTLY this JSON and nothing else:
|
|
20
|
+
{"findings": ["...", "..."]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrrlin-dev/mcp",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mrrlin-mcp": "dist/bin.cjs"
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
"@types/ws": "^8.18.1",
|
|
22
22
|
"esbuild": "^0.24.0",
|
|
23
23
|
"tsx": "^4.22.3",
|
|
24
|
-
"@mrrlin/client": "0.0.0",
|
|
25
24
|
"@mrrlin/codex-client": "0.0.0",
|
|
26
25
|
"@mrrlin/director-e2e": "0.0.0",
|
|
26
|
+
"@mrrlin/client": "0.0.0",
|
|
27
27
|
"@mrrlin/schemas": "0.0.0",
|
|
28
28
|
"@mrrlin/tsconfig": "0.0.0",
|
|
29
29
|
"@mrrlin/wiki": "0.0.0"
|