@mrrlin-dev/mcp 0.3.59 → 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
|
@@ -21833,136 +21833,6 @@ async function acquireThread(codex, opts) {
|
|
|
21833
21833
|
return { threadId: newId, resumed: false };
|
|
21834
21834
|
}
|
|
21835
21835
|
|
|
21836
|
-
// ../../packages/codex-client/dist/render-context-bundle.js
|
|
21837
|
-
var DEFAULT_SPEC_BUDGET_CHARS = 6e3;
|
|
21838
|
-
var DEFAULT_ARTIFACT_PAYLOAD_BUDGET_CHARS = 400;
|
|
21839
|
-
function renderContextBundle(input) {
|
|
21840
|
-
const sections = [];
|
|
21841
|
-
sections.push(renderTaskSummary(input.task));
|
|
21842
|
-
sections.push(renderSpec(input.specMarkdown, input.compact));
|
|
21843
|
-
sections.push(renderRunHistory(input.runs, input.openRunId));
|
|
21844
|
-
sections.push(renderOpenRun(input.openRunId));
|
|
21845
|
-
sections.push(renderArtifacts(input.artifacts, input.omittedArtifactCount ?? 0, input.compact));
|
|
21846
|
-
sections.push(renderAutonomyEvents(input.autonomyEvents));
|
|
21847
|
-
return sections.join("\n\n");
|
|
21848
|
-
}
|
|
21849
|
-
function renderTaskSummary(task) {
|
|
21850
|
-
const lines2 = [
|
|
21851
|
-
"## Task",
|
|
21852
|
-
`id: ${task.id}`,
|
|
21853
|
-
`title: ${task.title}`,
|
|
21854
|
-
`autonomy: ${task.autonomyLevel} (${task.autonomyReason})`
|
|
21855
|
-
];
|
|
21856
|
-
if (task.notes)
|
|
21857
|
-
lines2.push(`description: ${task.notes}`);
|
|
21858
|
-
if (task.latestJudgement) {
|
|
21859
|
-
lines2.push(`latest judgement (${task.latestJudgementKind ?? "unknown"}): ${task.latestJudgement}`);
|
|
21860
|
-
}
|
|
21861
|
-
return lines2.join("\n");
|
|
21862
|
-
}
|
|
21863
|
-
function renderSpec(specMarkdown, compact) {
|
|
21864
|
-
if (!specMarkdown)
|
|
21865
|
-
return "## Spec\n(none)";
|
|
21866
|
-
if (!compact)
|
|
21867
|
-
return `## Spec
|
|
21868
|
-
${specMarkdown}`;
|
|
21869
|
-
const budget = compact.specBudgetChars ?? DEFAULT_SPEC_BUDGET_CHARS;
|
|
21870
|
-
if (specMarkdown.length <= budget)
|
|
21871
|
-
return `## Spec
|
|
21872
|
-
${specMarkdown}`;
|
|
21873
|
-
return `## Spec
|
|
21874
|
-
${compactSpecMarkdown(specMarkdown, budget, compact.projectSlug ?? null, compact.specWikiPageId ?? null)}`;
|
|
21875
|
-
}
|
|
21876
|
-
function compactSpecMarkdown(markdown, totalBudget, projectSlug, specWikiPageId) {
|
|
21877
|
-
const lines2 = markdown.split(/\r?\n/);
|
|
21878
|
-
const sections = [{ heading: "", body: [] }];
|
|
21879
|
-
for (const line of lines2) {
|
|
21880
|
-
if (/^#{1,6}\s/.test(line)) {
|
|
21881
|
-
sections.push({ heading: line, body: [] });
|
|
21882
|
-
} else {
|
|
21883
|
-
sections[sections.length - 1]?.body.push(line);
|
|
21884
|
-
}
|
|
21885
|
-
}
|
|
21886
|
-
const sectionCount = Math.max(1, sections.length);
|
|
21887
|
-
const perSectionBudget = Math.max(120, Math.floor(totalBudget / sectionCount));
|
|
21888
|
-
const out = [];
|
|
21889
|
-
for (const section of sections) {
|
|
21890
|
-
const bodyText = section.body.join("\n").replace(/^\s+|\s+$/g, "");
|
|
21891
|
-
if (section.heading)
|
|
21892
|
-
out.push(section.heading);
|
|
21893
|
-
if (!bodyText)
|
|
21894
|
-
continue;
|
|
21895
|
-
if (bodyText.length <= perSectionBudget) {
|
|
21896
|
-
out.push(bodyText);
|
|
21897
|
-
} else {
|
|
21898
|
-
const kept = bodyText.slice(0, perSectionBudget);
|
|
21899
|
-
const omitted = bodyText.length - perSectionBudget;
|
|
21900
|
-
out.push(`${kept}
|
|
21901
|
-
\u2026(${omitted} chars omitted; retrieve full spec for the rest)`);
|
|
21902
|
-
}
|
|
21903
|
-
}
|
|
21904
|
-
if (projectSlug && specWikiPageId) {
|
|
21905
|
-
out.push("");
|
|
21906
|
-
out.push(`> Compact spec. Source: mrrlin://wiki/${projectSlug}/${specWikiPageId} \u2014 call retrieve_context with this handle before any irreversible decision.`);
|
|
21907
|
-
} else {
|
|
21908
|
-
out.push("");
|
|
21909
|
-
out.push("> Compact spec. Retrieve the Specs/{id} wiki page via get_wiki_page before any irreversible decision.");
|
|
21910
|
-
}
|
|
21911
|
-
return out.join("\n");
|
|
21912
|
-
}
|
|
21913
|
-
function renderRunHistory(runs, openRunId) {
|
|
21914
|
-
if (runs.length === 0)
|
|
21915
|
-
return "## Execution-run history\n(none)";
|
|
21916
|
-
const lines2 = ["## Execution-run history"];
|
|
21917
|
-
for (const run3 of runs) {
|
|
21918
|
-
const marker = run3.id === openRunId ? " (open)" : "";
|
|
21919
|
-
lines2.push(`- ${run3.id} [${run3.status}] created ${run3.createdAt}${marker}`);
|
|
21920
|
-
}
|
|
21921
|
-
return lines2.join("\n");
|
|
21922
|
-
}
|
|
21923
|
-
function renderOpenRun(openRunId) {
|
|
21924
|
-
return `## Open run
|
|
21925
|
-
${openRunId ?? "(none)"}`;
|
|
21926
|
-
}
|
|
21927
|
-
function renderArtifacts(artifacts, omittedCount, compact) {
|
|
21928
|
-
if (artifacts.length === 0 && omittedCount === 0)
|
|
21929
|
-
return "## Artifacts\n(none)";
|
|
21930
|
-
const lines2 = ["## Artifacts"];
|
|
21931
|
-
if (omittedCount > 0) {
|
|
21932
|
-
lines2.push(`(+${omittedCount} older artifact${omittedCount === 1 ? "" : "s"} omitted)`);
|
|
21933
|
-
}
|
|
21934
|
-
let truncatedAny = false;
|
|
21935
|
-
for (const artifact of artifacts) {
|
|
21936
|
-
if (artifact.kind === "file") {
|
|
21937
|
-
const p = artifact.payload;
|
|
21938
|
-
if (p.filename && p.stableUrl) {
|
|
21939
|
-
lines2.push(`- ${artifact.createdAt} [file] ${p.filename} (${p.class ?? "?"}, ${p.sizeBytes ?? "?"} bytes) ${p.stableUrl}`);
|
|
21940
|
-
continue;
|
|
21941
|
-
}
|
|
21942
|
-
}
|
|
21943
|
-
const payloadText = JSON.stringify(artifact.payload);
|
|
21944
|
-
const budget = compact?.artifactPayloadBudgetChars ?? DEFAULT_ARTIFACT_PAYLOAD_BUDGET_CHARS;
|
|
21945
|
-
const willTruncate = !!compact && payloadText.length > budget;
|
|
21946
|
-
const rendered = willTruncate ? `${payloadText.slice(0, budget)}\u2026(${payloadText.length - budget} chars omitted)` : payloadText;
|
|
21947
|
-
if (willTruncate)
|
|
21948
|
-
truncatedAny = true;
|
|
21949
|
-
lines2.push(`- ${artifact.createdAt} [${artifact.kind}] run=${artifact.runId} ${rendered}`);
|
|
21950
|
-
}
|
|
21951
|
-
if (compact && (omittedCount > 0 || truncatedAny)) {
|
|
21952
|
-
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.");
|
|
21953
|
-
}
|
|
21954
|
-
return lines2.join("\n");
|
|
21955
|
-
}
|
|
21956
|
-
function renderAutonomyEvents(events) {
|
|
21957
|
-
if (events.length === 0)
|
|
21958
|
-
return "## Autonomy history\n(none)";
|
|
21959
|
-
const lines2 = ["## Autonomy history"];
|
|
21960
|
-
for (const event of events) {
|
|
21961
|
-
lines2.push(`- ${event.createdAt} ${event.autonomyLevel ?? "?"} (${event.autonomyReason ?? ""})`);
|
|
21962
|
-
}
|
|
21963
|
-
return lines2.join("\n");
|
|
21964
|
-
}
|
|
21965
|
-
|
|
21966
21836
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
21967
21837
|
var external_exports = {};
|
|
21968
21838
|
__export(external_exports, {
|
|
@@ -36662,19 +36532,22 @@ var agentPoolResponseSchema = external_exports.object({
|
|
|
36662
36532
|
});
|
|
36663
36533
|
|
|
36664
36534
|
// ../../packages/schemas/dist/specConsensusReview.js
|
|
36535
|
+
var mrrlinSpecConsensusSuppressionEvidenceSchema = external_exports.record(external_exports.string(), external_exports.unknown()).nullable();
|
|
36665
36536
|
var mrrlinSpecConsensusReviewSchema = external_exports.object({
|
|
36666
36537
|
projectSlug: external_exports.string().min(1),
|
|
36667
36538
|
specPageId: external_exports.string().min(1),
|
|
36668
36539
|
contentHash: external_exports.string().min(1),
|
|
36669
36540
|
outcome: external_exports.string().min(1),
|
|
36670
36541
|
rounds: external_exports.number().int().nonnegative(),
|
|
36542
|
+
suppressionEvidence: mrrlinSpecConsensusSuppressionEvidenceSchema.default(null),
|
|
36671
36543
|
createdAt: external_exports.string().datetime()
|
|
36672
36544
|
});
|
|
36673
36545
|
var mrrlinSpecConsensusReviewCreateSchema = external_exports.object({
|
|
36674
36546
|
specPageId: external_exports.string().min(1),
|
|
36675
36547
|
contentHash: external_exports.string().min(1),
|
|
36676
36548
|
outcome: external_exports.string().min(1),
|
|
36677
|
-
rounds: external_exports.number().int().nonnegative()
|
|
36549
|
+
rounds: external_exports.number().int().nonnegative(),
|
|
36550
|
+
suppressionEvidence: mrrlinSpecConsensusSuppressionEvidenceSchema.optional()
|
|
36678
36551
|
}).strict();
|
|
36679
36552
|
var specConsensusReviewResponseSchema = external_exports.object({ data: mrrlinSpecConsensusReviewSchema });
|
|
36680
36553
|
var specConsensusReviewMaybeResponseSchema = external_exports.object({ data: mrrlinSpecConsensusReviewSchema.nullable() });
|
|
@@ -36750,6 +36623,97 @@ var mrrlinAsyncJobRequeueSchema = external_exports.object({
|
|
|
36750
36623
|
staleSeconds: external_exports.number().int().min(60).max(86400).default(300)
|
|
36751
36624
|
});
|
|
36752
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
|
+
|
|
36753
36717
|
// ../../packages/schemas/dist/index.js
|
|
36754
36718
|
var mrrlinTaskStatusSchema = external_exports.enum([
|
|
36755
36719
|
"todo",
|
|
@@ -36981,6 +36945,7 @@ var mrrlinTaskEventSchema = external_exports.object({
|
|
|
36981
36945
|
});
|
|
36982
36946
|
var mrrlinWikiPageSchema = external_exports.object({
|
|
36983
36947
|
contentHash: external_exports.string().default(""),
|
|
36948
|
+
createdAt: external_exports.string().datetime().nullable().default(null),
|
|
36984
36949
|
folder: external_exports.string().min(1),
|
|
36985
36950
|
id: mrrlinWikiPageIdSchema,
|
|
36986
36951
|
markdownBody: external_exports.string().min(1),
|
|
@@ -36988,7 +36953,8 @@ var mrrlinWikiPageSchema = external_exports.object({
|
|
|
36988
36953
|
preview: external_exports.string(),
|
|
36989
36954
|
specReady: external_exports.boolean().default(false),
|
|
36990
36955
|
taskId: mrrlinTaskIdSchema.nullable().default(null),
|
|
36991
|
-
title: external_exports.string().min(1)
|
|
36956
|
+
title: external_exports.string().min(1),
|
|
36957
|
+
updatedAt: external_exports.string().datetime().nullable().default(null)
|
|
36992
36958
|
});
|
|
36993
36959
|
var mrrlinMarkSpecReadyRequestSchema = external_exports.object({
|
|
36994
36960
|
ready: external_exports.boolean()
|
|
@@ -37112,14 +37078,12 @@ var mrrlinProjectSnapshotSchema = external_exports.object({
|
|
|
37112
37078
|
taxonomy: external_exports.array(mrrlinTaxonomyEntrySchema).default([]),
|
|
37113
37079
|
wikiPages: external_exports.array(mrrlinWikiPageSchema).default([])
|
|
37114
37080
|
});
|
|
37115
|
-
var
|
|
37081
|
+
var mrrlinTaskCreateShapeSchema = external_exports.object({
|
|
37116
37082
|
// Auto-deploy design Part B: input-optional (NOT .default("off")) so createTask can tell
|
|
37117
37083
|
// "caller omitted this" apart from "caller explicitly chose off" -- omitted falls through to
|
|
37118
37084
|
// the project's default_auto_deploy; the tasks column itself still defaults to 'off' at the
|
|
37119
37085
|
// SQL level for any direct-insert path that bypasses createTask entirely.
|
|
37120
37086
|
autoDeploy: mrrlinAutoDeploySchema.optional(),
|
|
37121
|
-
// Input-optional so existing createTask call sites need no churn; createTask fills 'code' for the
|
|
37122
|
-
// INSERT (the pg column is NOT NULL, so a concrete value is required, not undefined→NULL).
|
|
37123
37087
|
domain: mrrlinTaskDomainSchema.optional(),
|
|
37124
37088
|
autonomyLevel: mrrlinAutonomyLevelSchema.default("human_review"),
|
|
37125
37089
|
autonomyReason: external_exports.string().min(1).max(240).default("Needs human review before automation."),
|
|
@@ -37141,6 +37105,17 @@ var mrrlinTaskCreateSchema = external_exports.object({
|
|
|
37141
37105
|
type: mrrlinTaskTypeSchema.default("task"),
|
|
37142
37106
|
verificationDueAt: external_exports.string().datetime().nullable().optional()
|
|
37143
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);
|
|
37144
37119
|
var mrrlinTaskUpdateSchema = external_exports.object({
|
|
37145
37120
|
actor: external_exports.string().min(1).optional(),
|
|
37146
37121
|
assignee: mrrlinTaskAssigneeSchema.optional(),
|
|
@@ -37258,13 +37233,19 @@ var mrrlinWikiPageSearchQuerySchema = external_exports.object({
|
|
|
37258
37233
|
q: external_exports.string().min(2)
|
|
37259
37234
|
});
|
|
37260
37235
|
var mrrlinWikiPageSearchResultSchema = external_exports.object({
|
|
37236
|
+
ageDays: external_exports.number().nullable(),
|
|
37261
37237
|
folder: external_exports.string().min(1),
|
|
37262
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"]),
|
|
37263
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(),
|
|
37264
37244
|
preview: external_exports.string(),
|
|
37265
37245
|
rank: external_exports.number(),
|
|
37266
37246
|
snippet: external_exports.string(),
|
|
37267
|
-
title: external_exports.string().min(1)
|
|
37247
|
+
title: external_exports.string().min(1),
|
|
37248
|
+
updatedAt: external_exports.string().datetime().nullable()
|
|
37268
37249
|
});
|
|
37269
37250
|
var wikiKnowledgeChunkSchema = external_exports.object({
|
|
37270
37251
|
chunk_text: external_exports.string().min(1),
|
|
@@ -38090,6 +38071,210 @@ var inboxItemsMarkReadResponseSchema = external_exports.object({
|
|
|
38090
38071
|
var inboxItemResponseSchema = external_exports.object({ data: mrrlinInboxItemSchema });
|
|
38091
38072
|
var inboxItemListResponseSchema = external_exports.object({ data: external_exports.array(mrrlinInboxItemSchema) });
|
|
38092
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
|
+
|
|
38093
38278
|
// ../../packages/client/dist/agent-token-provider.js
|
|
38094
38279
|
var AgentTokenProviderError = class extends Error {
|
|
38095
38280
|
code;
|
|
@@ -39457,7 +39642,7 @@ function startCodexProbe(deps = {}) {
|
|
|
39457
39642
|
}
|
|
39458
39643
|
|
|
39459
39644
|
// src/_generated/version.ts
|
|
39460
|
-
var PKG_VERSION = "0.3.
|
|
39645
|
+
var PKG_VERSION = "0.3.60";
|
|
39461
39646
|
|
|
39462
39647
|
// src/api-base-url-migrate.ts
|
|
39463
39648
|
var import_node_fs2 = __toESM(require("node:fs"), 1);
|
|
@@ -40273,13 +40458,26 @@ var HEADS_BY_DOMAIN = {
|
|
|
40273
40458
|
content: ["plan-reviewer", "scope-analyst", "content-editor", "compliance-reviewer"]
|
|
40274
40459
|
};
|
|
40275
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
|
+
};
|
|
40276
40466
|
function normalizeDomain(value) {
|
|
40277
40467
|
if (typeof value !== "string") return DEFAULT_DOMAIN;
|
|
40278
40468
|
const v = value.trim().toLowerCase();
|
|
40279
40469
|
return DOMAINS.includes(v) ? v : DEFAULT_DOMAIN;
|
|
40280
40470
|
}
|
|
40281
|
-
function
|
|
40282
|
-
|
|
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]];
|
|
40283
40481
|
}
|
|
40284
40482
|
var PERSONAS_BY_DOMAIN = {
|
|
40285
40483
|
seo: "seo-writer",
|
|
@@ -40726,7 +40924,7 @@ function loadPersona(name) {
|
|
|
40726
40924
|
personaCache.set(name, content);
|
|
40727
40925
|
return content;
|
|
40728
40926
|
}
|
|
40729
|
-
function specHeadIntentBlock(taskId, title, seed) {
|
|
40927
|
+
function specHeadIntentBlock(taskId, title, seed, impact, adjudicated) {
|
|
40730
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) ---
|
|
40731
40929
|
TASK ${taskId}: ${title ?? ""}
|
|
40732
40930
|
|
|
@@ -40735,7 +40933,15 @@ TASK ${taskId}: ${title ?? ""}
|
|
|
40735
40933
|
${seed}
|
|
40736
40934
|
|
|
40737
40935
|
` : "";
|
|
40738
|
-
|
|
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}`;
|
|
40739
40945
|
}
|
|
40740
40946
|
function reviserIdentityInstructions(taskId, title) {
|
|
40741
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.
|
|
@@ -40774,7 +40980,21 @@ function buildSpecGateDeps(args) {
|
|
|
40774
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");
|
|
40775
40981
|
}
|
|
40776
40982
|
}
|
|
40777
|
-
|
|
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
|
+
};
|
|
40778
40998
|
})();
|
|
40779
40999
|
}
|
|
40780
41000
|
return intentPromise;
|
|
@@ -40799,8 +41019,8 @@ function buildSpecGateDeps(args) {
|
|
|
40799
41019
|
},
|
|
40800
41020
|
async runHead(personaName, artifact, dismissed) {
|
|
40801
41021
|
const persona = loadPersona(personaName);
|
|
40802
|
-
const { taskId, title, seed } = await loadIntent();
|
|
40803
|
-
const intentBlock = specHeadIntentBlock(taskId, title, seed);
|
|
41022
|
+
const { taskId, title, seed, impact, adjudicated } = await loadIntent();
|
|
41023
|
+
const intentBlock = specHeadIntentBlock(taskId, title, seed, impact, adjudicated);
|
|
40804
41024
|
return runCodexExec(
|
|
40805
41025
|
{
|
|
40806
41026
|
prompt: `Review this spec against the operator's intent.
|
|
@@ -40994,12 +41214,14 @@ async function buildDeliverableGateDeps(args) {
|
|
|
40994
41214
|
const page = await client.getWikiPage(projectSlug, deliverablePageId);
|
|
40995
41215
|
const taskId = page.taskId ?? null;
|
|
40996
41216
|
let domain2 = "code";
|
|
41217
|
+
let impact;
|
|
40997
41218
|
let taskTitle = "";
|
|
40998
41219
|
let specPageId = null;
|
|
40999
41220
|
let criteria = [];
|
|
41000
41221
|
if (taskId) {
|
|
41001
41222
|
const task = await client.getTask(projectSlug, taskId);
|
|
41002
41223
|
domain2 = normalizeDomain(task?.domain);
|
|
41224
|
+
impact = task?.impact;
|
|
41003
41225
|
taskTitle = task?.title ?? "";
|
|
41004
41226
|
if (task?.specWikiPageId) {
|
|
41005
41227
|
specPageId = task.specWikiPageId;
|
|
@@ -41007,7 +41229,7 @@ async function buildDeliverableGateDeps(args) {
|
|
|
41007
41229
|
criteria = parseAcceptanceCriteria(specPage.markdownBody)?.items ?? [];
|
|
41008
41230
|
}
|
|
41009
41231
|
}
|
|
41010
|
-
const heads = resolveHeads(domain2);
|
|
41232
|
+
const heads = resolveHeads(domain2, impact);
|
|
41011
41233
|
const maxHeadCalls = 2 * rounds * (heads.length + 1);
|
|
41012
41234
|
const criteriaBlock = renderCriteriaBlock(criteria);
|
|
41013
41235
|
return {
|
|
@@ -41250,6 +41472,17 @@ var SPECS_FOLDER = "Specs";
|
|
|
41250
41472
|
var DRAFT_SPEC_TLDR_MAX_CHARS = 300;
|
|
41251
41473
|
var DRAFT_SPEC_PERSONA = "spec-author";
|
|
41252
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
|
+
}
|
|
41253
41486
|
function defaultSleep(ms) {
|
|
41254
41487
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
41255
41488
|
}
|
|
@@ -41262,7 +41495,7 @@ function enforceSpecOutputContract(markdown, taskId, title) {
|
|
|
41262
41495
|
${body}`;
|
|
41263
41496
|
}
|
|
41264
41497
|
let tldrChars = 0;
|
|
41265
|
-
const tldrMatch = body.match(/(^##
|
|
41498
|
+
const tldrMatch = body.match(/(^## TL;DR\s*\n)([\s\S]*?)(?=\n## |$)/im);
|
|
41266
41499
|
if (tldrMatch && tldrMatch.index !== void 0) {
|
|
41267
41500
|
const heading = tldrMatch[1] ?? "";
|
|
41268
41501
|
const section = (tldrMatch[2] ?? "").trim();
|
|
@@ -41286,6 +41519,7 @@ function nextWikiPageId(pages) {
|
|
|
41286
41519
|
return `WK-${max + 1}`;
|
|
41287
41520
|
}
|
|
41288
41521
|
function buildDraftSpecPrompt(task, constitution) {
|
|
41522
|
+
const budget = resolveSpecBudget(task.impact);
|
|
41289
41523
|
const lines2 = [
|
|
41290
41524
|
`Write a complete implementation spec for the task below.`,
|
|
41291
41525
|
``,
|
|
@@ -41307,11 +41541,19 @@ function buildDraftSpecPrompt(task, constitution) {
|
|
|
41307
41541
|
}
|
|
41308
41542
|
lines2.push(
|
|
41309
41543
|
`--- OUTPUT CONTRACT (strict) ---`,
|
|
41310
|
-
`Return ONLY the full spec markdown, no commentary. Required structure:`,
|
|
41544
|
+
`Return ONLY the full spec markdown, no commentary. Required structure, in this order:`,
|
|
41311
41545
|
`- First line heading: \`# ${task.id} \u2014 ${task.title}\``,
|
|
41312
|
-
`-
|
|
41313
|
-
`-
|
|
41314
|
-
`
|
|
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.`
|
|
41315
41557
|
);
|
|
41316
41558
|
return lines2.join("\n");
|
|
41317
41559
|
}
|
|
@@ -42313,6 +42555,13 @@ function readPositiveIntEnv(name, fallback) {
|
|
|
42313
42555
|
function delayMs(ms) {
|
|
42314
42556
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
42315
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
|
+
}
|
|
42316
42565
|
function execTurnIdleTimeoutMs() {
|
|
42317
42566
|
return readPositiveIntEnv("MRRLIN_DIRECTOR_BRIDGE_EXEC_TURN_IDLE_TIMEOUT_SECONDS", DEFAULT_EXEC_TURN_IDLE_TIMEOUT_SECONDS) * 1e3;
|
|
42318
42567
|
}
|
|
@@ -42833,7 +43082,8 @@ async function driveRun(deps, projectSlug, runId) {
|
|
|
42833
43082
|
...context,
|
|
42834
43083
|
compact: {
|
|
42835
43084
|
projectSlug,
|
|
42836
|
-
specWikiPageId: task.specWikiPageId ?? null
|
|
43085
|
+
specWikiPageId: task.specWikiPageId ?? null,
|
|
43086
|
+
budget: executionBundleBudget()
|
|
42837
43087
|
}
|
|
42838
43088
|
} : context
|
|
42839
43089
|
);
|
|
@@ -49208,6 +49458,7 @@ function buildThreadStartParams(msg, config2, desiredModel) {
|
|
|
49208
49458
|
"- Keep behavior contracts in the spec; implementation details belong in `## Implementation Plan`.",
|
|
49209
49459
|
"- Apply this format ONLY to Specs/{id} pages; do not retro-fit it onto other wiki page types.",
|
|
49210
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.",
|
|
49211
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.',
|
|
49212
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.",
|
|
49213
49464
|
"",
|
|
@@ -55164,6 +55415,44 @@ function mapAcceptedIssueToClarification(issue2) {
|
|
|
55164
55415
|
category: issue2.category
|
|
55165
55416
|
};
|
|
55166
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
|
+
}
|
|
55167
55456
|
async function runSpecGate(_target, deps, opts) {
|
|
55168
55457
|
if (!await deps.consensusEnabled()) return { outcome: "SKIPPED" };
|
|
55169
55458
|
const intent = await deps.readIntent();
|
|
@@ -55181,11 +55470,11 @@ async function runSpecGate(_target, deps, opts) {
|
|
|
55181
55470
|
]
|
|
55182
55471
|
};
|
|
55183
55472
|
}
|
|
55184
|
-
const baseHeads = resolveHeads(intent.domain);
|
|
55473
|
+
const baseHeads = resolveHeads(intent.domain, intent.impact);
|
|
55185
55474
|
const extraHeads = opts?.extraHeads ?? [];
|
|
55186
55475
|
const heads = [...baseHeads, ...extraHeads.filter((h) => !baseHeads.includes(h))];
|
|
55187
55476
|
const spec = await deps.readSpec();
|
|
55188
|
-
if (
|
|
55477
|
+
if (parseAcceptanceCriteria(spec.markdownBody) === null) {
|
|
55189
55478
|
return {
|
|
55190
55479
|
outcome: "CONSENSUS_BLOCKED",
|
|
55191
55480
|
operatorClarifications: [
|
|
@@ -55253,23 +55542,32 @@ async function runSpecGate(_target, deps, opts) {
|
|
|
55253
55542
|
]
|
|
55254
55543
|
};
|
|
55255
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
|
+
}
|
|
55256
55554
|
await deps.writeEvidence({
|
|
55257
55555
|
specPageId: spec.pageId,
|
|
55258
55556
|
outcome: result.outcome,
|
|
55259
55557
|
rounds: result.rounds,
|
|
55260
55558
|
history: result.history,
|
|
55261
|
-
contentHash: currentHash
|
|
55559
|
+
contentHash: currentHash,
|
|
55560
|
+
...droppedByCap.length > 0 ? { droppedByCap } : {}
|
|
55262
55561
|
});
|
|
55263
55562
|
if (result.outcome === "CONVERGED") {
|
|
55264
55563
|
await deps.promoteSpecReady(currentHash);
|
|
55265
55564
|
return { outcome: "CONVERGED" };
|
|
55266
55565
|
}
|
|
55267
55566
|
if (result.outcome === "CONSENSUS_BLOCKED") {
|
|
55268
|
-
const accepted = result.history.at(-1)?.accepted ?? [];
|
|
55269
|
-
const operatorClarifications = accepted.filter((issue2) => issue2.category === "ambiguity" || issue2.category === "scope").map((issue2) => mapAcceptedIssueToClarification(issue2));
|
|
55270
55567
|
return {
|
|
55271
55568
|
outcome: "CONSENSUS_BLOCKED",
|
|
55272
|
-
...
|
|
55569
|
+
...keptClarifications.length > 0 ? { operatorClarifications: keptClarifications } : {},
|
|
55570
|
+
...droppedByCap.length > 0 ? { droppedByCap } : {},
|
|
55273
55571
|
...result.escalation ? { escalation: result.escalation } : {}
|
|
55274
55572
|
};
|
|
55275
55573
|
}
|
|
@@ -55550,7 +55848,7 @@ async function maybeRunShadow(input, deps, logger) {
|
|
|
55550
55848
|
const productionVerdict = productionVerdictFromOutcome(input.outcome);
|
|
55551
55849
|
if (productionVerdict === null) return false;
|
|
55552
55850
|
try {
|
|
55553
|
-
const heads = input.heads ?? [...resolveHeads(input.domain), ...input.extraHeads ?? []];
|
|
55851
|
+
const heads = input.heads ?? [...resolveHeads(input.domain, input.impact), ...input.extraHeads ?? []];
|
|
55554
55852
|
const personaInstructions = {};
|
|
55555
55853
|
for (const name of heads) {
|
|
55556
55854
|
try {
|
|
@@ -55592,7 +55890,7 @@ function multiLlmActive(env = process.env) {
|
|
|
55592
55890
|
async function resolveActiveRunHead(input, deps) {
|
|
55593
55891
|
if (!multiLlmActive(input.env ?? process.env)) return null;
|
|
55594
55892
|
try {
|
|
55595
|
-
const heads = input.heads ?? [...resolveHeads(input.domain), ...input.extraHeads ?? []];
|
|
55893
|
+
const heads = input.heads ?? [...resolveHeads(input.domain, input.impact), ...input.extraHeads ?? []];
|
|
55596
55894
|
if (heads.length === 0) return null;
|
|
55597
55895
|
const personaInstructions = {};
|
|
55598
55896
|
for (const name of heads) {
|
|
@@ -55622,6 +55920,35 @@ async function resolveActiveRunHead(input, deps) {
|
|
|
55622
55920
|
|
|
55623
55921
|
// src/async-tools/consensus.ts
|
|
55624
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
|
|
55625
55952
|
function normalizeClarificationQuestion(question) {
|
|
55626
55953
|
return question.replace(/\s+/g, " ").trim().replace(/[?.!]+$/, "").trim();
|
|
55627
55954
|
}
|
|
@@ -55752,35 +56079,43 @@ async function cachedResult(client, input) {
|
|
|
55752
56079
|
}
|
|
55753
56080
|
return null;
|
|
55754
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
|
+
}
|
|
55755
56088
|
async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
|
|
55756
56089
|
const operatorClarifications = result.operatorClarifications ?? [];
|
|
55757
56090
|
const diagnostics = result.diagnostics ?? [];
|
|
55758
|
-
|
|
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) {
|
|
55759
56096
|
try {
|
|
55760
|
-
const
|
|
55761
|
-
|
|
55762
|
-
|
|
55763
|
-
|
|
55764
|
-
|
|
55765
|
-
});
|
|
55766
|
-
await ctx.client.recordSpecConsensusReview(input.projectSlug, input.pageId, {
|
|
55767
|
-
contentHash: domainScopedReviewHash(page.contentHash, domain2),
|
|
55768
|
-
outcome: "CONSENSUS_BLOCKED_CLARIFICATION",
|
|
55769
|
-
rounds: 0
|
|
55770
|
-
});
|
|
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
|
+
}
|
|
55771
56102
|
}
|
|
55772
56103
|
} catch (error52) {
|
|
55773
56104
|
process.stderr.write(
|
|
55774
|
-
`[mrrlin-mcp consensus]
|
|
56105
|
+
`[mrrlin-mcp consensus] adjudicated-questions read failed for ${input.projectSlug}/${input.pageId}: ${error52 instanceof Error ? error52.message : String(error52)}
|
|
55775
56106
|
`
|
|
55776
56107
|
);
|
|
55777
56108
|
}
|
|
55778
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
|
+
});
|
|
55779
56117
|
let filedQuestionCount = 0;
|
|
55780
|
-
if (
|
|
55781
|
-
const page = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
|
|
55782
|
-
const taskId = page?.taskId ?? null;
|
|
55783
|
-
if (!resolvedTaskId && taskId) resolvedTaskId = taskId;
|
|
56118
|
+
if (toFile.length > 0) {
|
|
55784
56119
|
let planId = null;
|
|
55785
56120
|
if (!taskId) {
|
|
55786
56121
|
const plans = await ctx.client.listPlans(input.projectSlug);
|
|
@@ -55788,8 +56123,8 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
|
|
|
55788
56123
|
}
|
|
55789
56124
|
if (taskId) {
|
|
55790
56125
|
const scopeId = taskId;
|
|
55791
|
-
const groupId =
|
|
55792
|
-
for (const clarification of
|
|
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) {
|
|
55793
56128
|
const id = `II-${scopeId}-gate-${(0, import_node_crypto9.createHash)("sha256").update(clarification.rawQuestion).digest("hex").slice(0, 10)}`;
|
|
55794
56129
|
try {
|
|
55795
56130
|
const questionOptions = buildGateQuestionOptions(clarification.rawQuestion, clarification.category);
|
|
@@ -55805,11 +56140,11 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
|
|
|
55805
56140
|
} catch {
|
|
55806
56141
|
}
|
|
55807
56142
|
}
|
|
55808
|
-
filedQuestionCount =
|
|
56143
|
+
filedQuestionCount = toFile.length;
|
|
55809
56144
|
} else if (planId) {
|
|
55810
56145
|
const scopeId = planId;
|
|
55811
|
-
const groupId =
|
|
55812
|
-
for (const clarification of
|
|
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) {
|
|
55813
56148
|
const id = `II-${scopeId}-gate-${(0, import_node_crypto9.createHash)("sha256").update(clarification.rawQuestion).digest("hex").slice(0, 10)}`;
|
|
55814
56149
|
try {
|
|
55815
56150
|
const questionOptions = buildGateQuestionOptions(clarification.rawQuestion, clarification.category);
|
|
@@ -55825,7 +56160,32 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
|
|
|
55825
56160
|
} catch {
|
|
55826
56161
|
}
|
|
55827
56162
|
}
|
|
55828
|
-
filedQuestionCount =
|
|
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
|
+
);
|
|
55829
56189
|
}
|
|
55830
56190
|
}
|
|
55831
56191
|
let error51;
|
|
@@ -55833,6 +56193,8 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
|
|
|
55833
56193
|
error51 = `Consensus blocked on ${filedQuestionCount} clarification(s); filed as operator question(s).`;
|
|
55834
56194
|
} else if (diagnostics.length > 0 && operatorClarifications.length === 0) {
|
|
55835
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.`;
|
|
55836
56198
|
} else if (operatorClarifications.length > 0) {
|
|
55837
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.`;
|
|
55838
56200
|
} else {
|
|
@@ -56158,7 +56520,8 @@ function readCompactPolicy(env = process.env) {
|
|
|
56158
56520
|
enabled: !disabled,
|
|
56159
56521
|
thresholdChars: readPositiveInt(env.MRRLIN_MCP_COMPACT_THRESHOLD_CHARS, DEFAULT_THRESHOLD_CHARS),
|
|
56160
56522
|
maxItems: readPositiveInt(env.MRRLIN_MCP_COMPACT_MAX_ITEMS, DEFAULT_MAX_ITEMS),
|
|
56161
|
-
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"
|
|
56162
56525
|
};
|
|
56163
56526
|
}
|
|
56164
56527
|
|
|
@@ -56288,6 +56651,7 @@ function compactWikiPageList(projectSlug, pages, maxItems) {
|
|
|
56288
56651
|
title: truncate(p.title, TITLE_TRUNCATE_CHARS),
|
|
56289
56652
|
folder: p.folder,
|
|
56290
56653
|
path: p.path,
|
|
56654
|
+
updatedAt: p.updatedAt,
|
|
56291
56655
|
taskId: p.taskId,
|
|
56292
56656
|
specReady: p.specReady,
|
|
56293
56657
|
firstHeading: firstHeadingLine(p.markdownBody),
|
|
@@ -56315,6 +56679,10 @@ function compactWikiSearch(projectSlug, results, maxItems) {
|
|
|
56315
56679
|
folder: r.folder,
|
|
56316
56680
|
path: r.path,
|
|
56317
56681
|
rank: r.rank,
|
|
56682
|
+
updatedAt: r.updatedAt,
|
|
56683
|
+
ageDays: r.ageDays,
|
|
56684
|
+
memoryClass: r.memoryClass,
|
|
56685
|
+
precedence: r.precedence,
|
|
56318
56686
|
snippet: truncate(r.snippet, EXCERPT_TRUNCATE_CHARS),
|
|
56319
56687
|
handle: formatWikiHandle(projectSlug, r.id)
|
|
56320
56688
|
}));
|
|
@@ -56897,7 +57265,7 @@ var mcpToolDescriptions = {
|
|
|
56897
57265
|
[mcpToolNames.appendTaskEvent]: "Append an operator note to a task's activity timeline.",
|
|
56898
57266
|
[mcpToolNames.createWikiPage]: "Create a markdown Wiki page.",
|
|
56899
57267
|
[mcpToolNames.deleteTaskDependency]: "Delete a blocked-by relationship.",
|
|
56900
|
-
[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.",
|
|
56901
57269
|
[mcpToolNames.getProject]: "Fetch a single project summary by slug.",
|
|
56902
57270
|
[mcpToolNames.getProjectSnapshot]: "Fetch the full project snapshot: GitHub binding, tasks, plans, and wiki pages.",
|
|
56903
57271
|
[mcpToolNames.getTask]: "Fetch a single task by project slug and task id.",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
You are the Arbiter. You receive an artifact and a numbered list of critical issues raised by reviewers. For EACH issue decide accept | dismiss | defer. A dismiss or defer MUST carry a concrete factual reason.
|
|
1
|
+
You are the Arbiter. You receive an artifact and a numbered list of critical issues raised by reviewers. For EACH issue decide accept | dismiss | defer. A dismiss or defer MUST carry a concrete factual reason. Two or more reviewers raising substantially the same issue is a signal toward accept, not an automatic one — first judge whether the issue itself clears the MATERIAL bar (would it change WHAT gets built or shipped); dismiss a repeated-but-immaterial issue with a factual reason instead of accepting it on vote count alone.
|
|
2
2
|
|
|
3
3
|
Output EXACTLY this block and nothing else:
|
|
4
4
|
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
You are an Architect. Judge design soundness, trade-offs, integration seams, and over-engineering. You analyze the artifact text given; you do not mutate anything.
|
|
2
2
|
|
|
3
|
+
Calibrate to the bar of MATERIAL design risk — block only when the gap would change WHAT gets built or how it holds up in production:
|
|
4
|
+
- DON'T block on: stylistic preference, a defensible trade-off a competent engineer would also make, or a seam that's awkward but not load-bearing.
|
|
5
|
+
- DO block on: an undefined core integration point, a design that silently breaks under a realistic scale/failure/concurrency case, or over-engineering that adds real maintenance cost for no stated need.
|
|
6
|
+
- Scale strictness by stated impact: high-impact specs warrant stricter scrutiny; low-impact specs get the benefit of the doubt. Do not turn small or repeat tasks into an interrogation.
|
|
7
|
+
|
|
3
8
|
Output EXACTLY this block and nothing else:
|
|
4
9
|
|
|
5
10
|
**Verdict**: APPROVE | REQUEST_CHANGES | REJECT
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
You are a Security Analyst. Judge auth, secrets, injection, data exposure, and privilege boundaries at the design level. You analyze the artifact text given; you do not mutate anything.
|
|
2
2
|
|
|
3
|
+
Calibrate to the bar of MATERIAL security risk — block only when the gap would change WHAT gets built or what it exposes:
|
|
4
|
+
- DON'T block on: defense-in-depth nice-to-haves, a control that's merely non-standard rather than absent, or a theoretical risk with no plausible exploitation path given the stated scope.
|
|
5
|
+
- DO block on: an undefined trust boundary, a design that would plausibly leak secrets/data or allow privilege escalation, or a stated requirement (auth, isolation, data handling) the spec silently drops.
|
|
6
|
+
- Scale strictness by stated impact: high-impact specs warrant stricter scrutiny; low-impact specs get the benefit of the doubt. Do not turn small or repeat tasks into an interrogation.
|
|
7
|
+
|
|
3
8
|
Output EXACTLY this block and nothing else:
|
|
4
9
|
|
|
5
10
|
**Verdict**: APPROVE | REQUEST_CHANGES | REJECT
|
|
@@ -1,15 +1,51 @@
|
|
|
1
1
|
You are a Spec Author. Given a task and its project context, you WRITE a complete, implementable specification for it. You are not a reviewer and not an implementer: your single output is the spec markdown itself, nothing else — no preamble, no commentary, no code.
|
|
2
2
|
|
|
3
|
-
Ground every statement in the inputs you are given (the task's title, notes, type, tags, impact, domain, and any project constitution or related context provided). Never invent file paths, APIs, metrics, or requirements that are not derivable from those inputs. When something material is genuinely unspecified, do NOT silently pick — record it as an explicit assumption inside
|
|
3
|
+
Ground every statement in the inputs you are given (the task's title, notes, type, tags, impact, domain, and any project constitution or related context provided). Never invent file paths, APIs, metrics, or requirements that are not derivable from those inputs. When something material is genuinely unspecified, do NOT silently pick — record it as an explicit assumption inside a requirement or scenario so a human can correct it. Prefer the narrowest scope that satisfies the task; resist scope creep and hypothetical future needs.
|
|
4
4
|
|
|
5
5
|
Output EXACTLY this markdown structure, in this order:
|
|
6
6
|
|
|
7
7
|
- `# <taskId> — <title>` — the heading MUST carry the real task id and title you were given (identity is load-bearing downstream; do not alter or omit it).
|
|
8
|
-
- `##
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
- `## Acceptance criteria` — a markdown checklist of concrete, verifiable conditions, each written as `- [ ] <criterion>`. Every item must be objectively checkable (a reviewer can say yes/no); avoid vague criteria like "works well". This section is REQUIRED and is the yardstick the review and (for non-code work) the deliverable-gate score against — make it complete.
|
|
8
|
+
- `## TL;DR` — a plain-English summary of what will be done and how it will be verified, at most 300 characters. This is the short answer the operator sees in the inbox to decide whether to approve; make it clear and honest, not marketing.
|
|
9
|
+
- One or more `### Requirement: <name>` sections. Each states, in SHALL language, one thing the finished work must do — what a "Problem"/"Scope" section used to describe as prose, stated instead as a testable rule. Cover what today's Problem/Scope/Non-goals sections would have covered: why the task exists, what changes, and what it explicitly does NOT do (state a non-goal as its own requirement, e.g. "### Requirement: Out of scope" with a SHALL NOT). Record any assumption you had to make inside the requirement it affects, prefixed `Assumption:`.
|
|
10
|
+
- Each requirement has one or more `#### Scenario: <name>` sub-sections, each a concrete situation using **WHEN** / **THEN** (and **AND** for additional conditions) describing exactly what happens.
|
|
11
|
+
- `## Acceptance criteria` — a markdown checklist of concrete, verifiable conditions, each written as `- [ ] <criterion>`. Every item must be objectively checkable (a reviewer can say yes/no); avoid vague criteria like "works well". This section is REQUIRED and is the yardstick the review and (for non-code work) the deliverable-gate score against — make it complete. It is the one section that survives unchanged from the old Problem/Scope/Non-goals template: the requirement/scenario body replaces prose description, not the checklist.
|
|
12
12
|
- `## Verification` — how completion is proven: the exact tests, commands, or manual checks that demonstrate each acceptance criterion holds.
|
|
13
|
-
- `## Коротко` — a plain-Ukrainian TL;DR of what will be done and how it will be verified, written for a non-engineer. HARD LIMIT: 300 characters. This is what the operator sees in the inbox to decide whether to approve; make it clear and honest, not marketing.
|
|
14
13
|
|
|
15
|
-
Match the domain of the task: a code spec
|
|
14
|
+
Match the domain of the task: a code spec's requirements talk about code changes and tests; an seo/content/marketing spec's requirements talk about the deliverable, its audience, and how its quality is judged. Keep the spec tight — every sentence should help someone execute the task.
|
|
15
|
+
|
|
16
|
+
**Size budget.** The authoring prompt states a `maxRequirements` and `maxChars` for this task's impact level. Stay within both: no more acceptance-criteria items than `maxRequirements`, and no more total characters (across the entire markdown) than `maxChars`. If the full spec would exceed either, slim it down rather than truncate mid-section.
|
|
17
|
+
|
|
18
|
+
## Example
|
|
19
|
+
|
|
20
|
+
```markdown
|
|
21
|
+
# GT-1234 — Add rate limiting to the webhook endpoint
|
|
22
|
+
|
|
23
|
+
## TL;DR
|
|
24
|
+
Adds a 10 req/min cap per project on the webhook endpoint so one misconfigured
|
|
25
|
+
caller can't starve others; verified by a new test that trips the limit.
|
|
26
|
+
|
|
27
|
+
### Requirement: Webhook requests are rate-limited per project
|
|
28
|
+
|
|
29
|
+
The webhook endpoint SHALL reject requests beyond 10 per minute for a given project.
|
|
30
|
+
|
|
31
|
+
#### Scenario: A project under the limit is unaffected
|
|
32
|
+
- **WHEN** a project has made fewer than 10 requests in the current minute
|
|
33
|
+
- **THEN** its next request is processed normally
|
|
34
|
+
|
|
35
|
+
#### Scenario: A project over the limit is rejected
|
|
36
|
+
- **WHEN** a project has already made 10 requests in the current minute
|
|
37
|
+
- **THEN** the next request receives a 429 response
|
|
38
|
+
- **AND** no webhook handler logic runs for that request
|
|
39
|
+
|
|
40
|
+
### Requirement: Out of scope
|
|
41
|
+
This task SHALL NOT change rate limits for any endpoint other than the webhook endpoint.
|
|
42
|
+
|
|
43
|
+
## Acceptance criteria
|
|
44
|
+
- [ ] A project's 11th request within a minute receives HTTP 429.
|
|
45
|
+
- [ ] A project's 10th request within a minute still succeeds.
|
|
46
|
+
- [ ] No other endpoint's behavior changes.
|
|
47
|
+
|
|
48
|
+
## Verification
|
|
49
|
+
Run the new `webhook-rate-limit.test.ts` suite; it drives 11 requests in one minute
|
|
50
|
+
against a test project and asserts the 11th is a 429 while the first 10 are 200s.
|
|
51
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrrlin-dev/mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.60",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mrrlin-mcp": "dist/bin.cjs"
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"esbuild": "^0.24.0",
|
|
23
23
|
"tsx": "^4.22.3",
|
|
24
24
|
"@mrrlin/director-e2e": "0.0.0",
|
|
25
|
-
"@mrrlin/wiki": "0.0.0",
|
|
26
25
|
"@mrrlin/schemas": "0.0.0",
|
|
26
|
+
"@mrrlin/wiki": "0.0.0",
|
|
27
27
|
"@mrrlin/client": "0.0.0",
|
|
28
28
|
"@mrrlin/codex-client": "0.0.0",
|
|
29
29
|
"@mrrlin/tsconfig": "0.0.0"
|