@mrrlin-dev/mcp 0.3.67 → 0.3.68
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 +355 -65
- package/package.json +6 -6
package/dist/bin.cjs
CHANGED
|
@@ -20658,14 +20658,17 @@ var toml = __toESM(require_toml(), 1);
|
|
|
20658
20658
|
|
|
20659
20659
|
// src/api-base-url.ts
|
|
20660
20660
|
var DEFAULT_API_BASE_URL = "http://127.0.0.1:8787";
|
|
20661
|
-
var STAGING_API_BASE_URL = "https://
|
|
20661
|
+
var STAGING_API_BASE_URL = "https://api-staging.mrrlin.com";
|
|
20662
20662
|
function normalizeUrl(value) {
|
|
20663
20663
|
return value.replace(/\/$/, "");
|
|
20664
20664
|
}
|
|
20665
20665
|
var RETIRED_API_BASE_URLS = {
|
|
20666
20666
|
// Retired 2026-07-28 when staging moved to the mrrlin2 Vercel team; serves a decommissioned
|
|
20667
20667
|
// Supabase project and now answers `GET /health` with 402 DEPLOYMENT_DISABLED. Added in 0.3.50.
|
|
20668
|
-
"mrrlin-api-fnnzzz-fnnzzzs-projects.vercel.app": STAGING_API_BASE_URL
|
|
20668
|
+
"mrrlin-api-fnnzzz-fnnzzzs-projects.vercel.app": STAGING_API_BASE_URL,
|
|
20669
|
+
// Retired 2026-08-14 when staging decoupled onto the stable custom domain api-staging.mrrlin.com.
|
|
20670
|
+
// A persisted operator configuration may still pin the mrrlin3 Vercel alias; retarget it in one hop.
|
|
20671
|
+
"mrrlin-api-mrrlin3.vercel.app": STAGING_API_BASE_URL
|
|
20669
20672
|
};
|
|
20670
20673
|
function isSameApiHost(a, b) {
|
|
20671
20674
|
const origin = (value) => {
|
|
@@ -36387,6 +36390,9 @@ var openApiOperationIds = {
|
|
|
36387
36390
|
getExecutionRun: "getExecutionRun",
|
|
36388
36391
|
getProjectSchedulerPolicy: "getProjectSchedulerPolicy",
|
|
36389
36392
|
getProjectDeployWorkflow: "getProjectDeployWorkflow",
|
|
36393
|
+
getDeployTopology: "getDeployTopology",
|
|
36394
|
+
writeDeployTopology: "writeDeployTopology",
|
|
36395
|
+
getTaskDeployTarget: "getTaskDeployTarget",
|
|
36390
36396
|
getSchedulerPendingTasks: "getSchedulerPendingTasks",
|
|
36391
36397
|
wakeDirector: "wakeDirector",
|
|
36392
36398
|
getGithubInstallationRepositories: "getGithubInstallationRepositories",
|
|
@@ -37133,6 +37139,47 @@ function refineTaskCreateDomain(data, ctx) {
|
|
|
37133
37139
|
}
|
|
37134
37140
|
var mrrlinTaskCreateSchema = mrrlinTaskCreateShapeSchema.superRefine(refineTaskCreateDomain);
|
|
37135
37141
|
var mrrlinTaskCreateWithoutIdSchema = mrrlinTaskCreateShapeSchema.omit({ id: true }).superRefine(refineTaskCreateDomain);
|
|
37142
|
+
var mrrlinDeployWorkflowEnvSchema = external_exports.enum(["dev", "prod"]);
|
|
37143
|
+
var mrrlinTaskDeployTargetInputSchema = external_exports.object({
|
|
37144
|
+
env: mrrlinDeployWorkflowEnvSchema,
|
|
37145
|
+
targetLabel: external_exports.string().min(1),
|
|
37146
|
+
workflowId: external_exports.number().int().positive(),
|
|
37147
|
+
workflowPath: external_exports.string().min(1),
|
|
37148
|
+
workflowSha: external_exports.string().min(1),
|
|
37149
|
+
topologyFingerprint: external_exports.string().min(1)
|
|
37150
|
+
});
|
|
37151
|
+
var deployTopologyResponseSchema = external_exports.object({
|
|
37152
|
+
data: external_exports.object({
|
|
37153
|
+
fingerprint: external_exports.string().nullable(),
|
|
37154
|
+
snapshot: external_exports.array(external_exports.object({ id: external_exports.number(), path: external_exports.string(), sha: external_exports.string(), state: external_exports.string() })).nullable(),
|
|
37155
|
+
entries: external_exports.array(external_exports.object({
|
|
37156
|
+
label: external_exports.string(),
|
|
37157
|
+
env: mrrlinDeployWorkflowEnvSchema,
|
|
37158
|
+
workflowId: external_exports.number(),
|
|
37159
|
+
workflowPath: external_exports.string(),
|
|
37160
|
+
workflowSha: external_exports.string()
|
|
37161
|
+
}))
|
|
37162
|
+
})
|
|
37163
|
+
});
|
|
37164
|
+
var mrrlinDeployTopologyWriteSchema = external_exports.object({
|
|
37165
|
+
fingerprint: external_exports.string().min(1),
|
|
37166
|
+
entries: external_exports.array(external_exports.object({
|
|
37167
|
+
label: external_exports.string().min(1),
|
|
37168
|
+
env: mrrlinDeployWorkflowEnvSchema,
|
|
37169
|
+
workflowId: external_exports.number().int().positive(),
|
|
37170
|
+
workflowPath: external_exports.string().min(1),
|
|
37171
|
+
workflowSha: external_exports.string().min(1)
|
|
37172
|
+
}))
|
|
37173
|
+
});
|
|
37174
|
+
var taskDeployTargetResponseSchema = external_exports.object({
|
|
37175
|
+
data: external_exports.object({
|
|
37176
|
+
targetLabel: external_exports.string(),
|
|
37177
|
+
workflowId: external_exports.number(),
|
|
37178
|
+
workflowPath: external_exports.string(),
|
|
37179
|
+
workflowSha: external_exports.string(),
|
|
37180
|
+
topologyFingerprint: external_exports.string()
|
|
37181
|
+
})
|
|
37182
|
+
});
|
|
37136
37183
|
var mrrlinTaskUpdateSchema = external_exports.object({
|
|
37137
37184
|
actor: external_exports.string().min(1).optional(),
|
|
37138
37185
|
assignee: mrrlinTaskAssigneeSchema.optional(),
|
|
@@ -37155,8 +37202,13 @@ var mrrlinTaskUpdateSchema = external_exports.object({
|
|
|
37155
37202
|
title: external_exports.string().min(1).optional(),
|
|
37156
37203
|
type: mrrlinTaskTypeSchema.optional(),
|
|
37157
37204
|
followUpAt: external_exports.string().datetime().nullable().optional(),
|
|
37158
|
-
verificationDueAt: external_exports.string().datetime().nullable().optional()
|
|
37159
|
-
|
|
37205
|
+
verificationDueAt: external_exports.string().datetime().nullable().optional(),
|
|
37206
|
+
deployTargets: external_exports.array(mrrlinTaskDeployTargetInputSchema).optional(),
|
|
37207
|
+
// design.md D5 step 6 hint (task 3.16): the deploy gate's own bookkeeping write when the
|
|
37208
|
+
// fingerprint moved but the topology still maps the label to the same workflow. Never sent
|
|
37209
|
+
// by any other caller.
|
|
37210
|
+
reverifyRequested: external_exports.boolean().optional()
|
|
37211
|
+
}).refine((value) => value.assignee !== void 0 || value.autoDeploy !== void 0 || value.domain !== void 0 || value.autonomyLevel !== void 0 || value.autonomyReason !== void 0 || value.category !== void 0 || value.directorThreadId !== void 0 || value.dueDate !== void 0 || value.followUpAt !== void 0 || value.impact !== void 0 || value.notes !== void 0 || value.planId !== void 0 || value.specApproved !== void 0 || value.specWikiPageId !== void 0 || value.status !== void 0 || value.subcategory !== void 0 || value.tags !== void 0 || value.title !== void 0 || value.type !== void 0 || value.verificationDueAt !== void 0 || value.deployTargets !== void 0 || value.reverifyRequested !== void 0, { message: "TaskUpdate requires at least one field" }).superRefine((value, context) => {
|
|
37160
37212
|
if (value.autonomyLevel !== void 0 && value.autonomyReason === void 0) {
|
|
37161
37213
|
context.addIssue({
|
|
37162
37214
|
code: external_exports.ZodIssueCode.custom,
|
|
@@ -37240,6 +37292,7 @@ var mrrlinWikiPageUpdateSchema = external_exports.object({
|
|
|
37240
37292
|
}).refine((value) => Object.keys(value).length > 0, { message: "WikiPageUpdate requires at least one field" });
|
|
37241
37293
|
var mrrlinWikiPageListFiltersSchema = external_exports.object({
|
|
37242
37294
|
folder: external_exports.string().min(1).optional(),
|
|
37295
|
+
id: mrrlinWikiPageIdSchema.optional(),
|
|
37243
37296
|
q: external_exports.string().min(1).optional(),
|
|
37244
37297
|
taskId: mrrlinTaskIdSchema.optional()
|
|
37245
37298
|
});
|
|
@@ -37816,7 +37869,6 @@ var mrrlinProjectSchedulerPolicyUpsertSchema = external_exports.object({
|
|
|
37816
37869
|
var projectSchedulerPolicyResponseSchema = external_exports.object({
|
|
37817
37870
|
data: mrrlinProjectSchedulerPolicySchema
|
|
37818
37871
|
});
|
|
37819
|
-
var mrrlinDeployWorkflowEnvSchema = external_exports.enum(["dev", "prod"]);
|
|
37820
37872
|
var mrrlinDeployWorkflowSourceSchema = external_exports.enum(["agent", "operator"]);
|
|
37821
37873
|
var mrrlinDeployWorkflowStateSchema = external_exports.enum(["resolved", "ambiguous"]);
|
|
37822
37874
|
var mrrlinProjectDeployWorkflowSchema = external_exports.object({
|
|
@@ -39301,6 +39353,29 @@ function createMrrlinClient(config2) {
|
|
|
39301
39353
|
throw error51;
|
|
39302
39354
|
}
|
|
39303
39355
|
},
|
|
39356
|
+
async getDeployTopology(projectSlug) {
|
|
39357
|
+
const body = await request(`${projectPath(projectSlug)}/deploy-topology`);
|
|
39358
|
+
return deployTopologyResponseSchema.parse(body).data;
|
|
39359
|
+
},
|
|
39360
|
+
async writeDeployTopology(projectSlug, input) {
|
|
39361
|
+
const body = await request(`${projectPath(projectSlug)}/deploy-topology`, {
|
|
39362
|
+
body: JSON.stringify(mrrlinDeployTopologyWriteSchema.parse(input)),
|
|
39363
|
+
headers: { "content-type": "application/json" },
|
|
39364
|
+
method: "PUT"
|
|
39365
|
+
});
|
|
39366
|
+
return deployTopologyResponseSchema.parse(body).data;
|
|
39367
|
+
},
|
|
39368
|
+
async getTaskDeployTarget(projectSlug, taskId, env) {
|
|
39369
|
+
try {
|
|
39370
|
+
const body = await request(`${projectPath(projectSlug)}/tasks/${taskId}/deploy-target/${env}`);
|
|
39371
|
+
return taskDeployTargetResponseSchema.parse(body).data;
|
|
39372
|
+
} catch (error51) {
|
|
39373
|
+
if (error51 instanceof MrrlinApiError && error51.status === 404 && error51.code === "DEPLOY_TARGET_UNSET") {
|
|
39374
|
+
return null;
|
|
39375
|
+
}
|
|
39376
|
+
throw error51;
|
|
39377
|
+
}
|
|
39378
|
+
},
|
|
39304
39379
|
async upsertProjectDeployWorkflow(projectSlug, env, input) {
|
|
39305
39380
|
const body = await request(`${projectPath(projectSlug)}/deploy-workflows/${env}`, {
|
|
39306
39381
|
body: JSON.stringify(input),
|
|
@@ -39975,7 +40050,7 @@ function startCodexProbe(deps = {}) {
|
|
|
39975
40050
|
}
|
|
39976
40051
|
|
|
39977
40052
|
// src/_generated/version.ts
|
|
39978
|
-
var PKG_VERSION = "0.3.
|
|
40053
|
+
var PKG_VERSION = "0.3.68";
|
|
39979
40054
|
|
|
39980
40055
|
// src/api-base-url-migrate.ts
|
|
39981
40056
|
var import_node_fs2 = __toESM(require("node:fs"), 1);
|
|
@@ -42355,6 +42430,111 @@ ${body}`;
|
|
|
42355
42430
|
}
|
|
42356
42431
|
return { markdown: body, tldrChars };
|
|
42357
42432
|
}
|
|
42433
|
+
function extractDeployTarget(markdown) {
|
|
42434
|
+
const section = /(## Deploy Target\s*\n)([\s\S]*?)(?=\n## |$)/i.exec(markdown);
|
|
42435
|
+
if (!section || section.index === void 0) {
|
|
42436
|
+
return { markdown, deployTarget: null };
|
|
42437
|
+
}
|
|
42438
|
+
const remaining = markdown.slice(0, section.index) + markdown.slice(section.index + section[0].length);
|
|
42439
|
+
const fenced = /```(?:json)?\s*\n([\s\S]*?)```/m.exec(section[2] ?? "");
|
|
42440
|
+
let deployTarget = null;
|
|
42441
|
+
if (fenced) {
|
|
42442
|
+
try {
|
|
42443
|
+
const raw = JSON.parse(fenced[1] ?? "");
|
|
42444
|
+
if (raw !== null && typeof raw === "object" && typeof raw.label === "string") {
|
|
42445
|
+
const parsed = { label: raw.label };
|
|
42446
|
+
if (Array.isArray(raw.derive)) {
|
|
42447
|
+
const derive = raw.derive.filter(isDeployTopologyEntry);
|
|
42448
|
+
if (derive.length === raw.derive.length) parsed.derive = derive;
|
|
42449
|
+
}
|
|
42450
|
+
deployTarget = parsed;
|
|
42451
|
+
}
|
|
42452
|
+
} catch {
|
|
42453
|
+
deployTarget = null;
|
|
42454
|
+
}
|
|
42455
|
+
}
|
|
42456
|
+
return { markdown: remaining, deployTarget };
|
|
42457
|
+
}
|
|
42458
|
+
function isDeployTopologyEntry(value) {
|
|
42459
|
+
if (value === null || typeof value !== "object") return false;
|
|
42460
|
+
const e = value;
|
|
42461
|
+
return typeof e.label === "string" && e.label.length > 0 && (e.env === "dev" || e.env === "prod") && typeof e.workflowId === "number" && Number.isInteger(e.workflowId) && e.workflowId > 0 && typeof e.workflowPath === "string" && e.workflowPath.length > 0 && typeof e.workflowSha === "string" && e.workflowSha.length > 0;
|
|
42462
|
+
}
|
|
42463
|
+
function buildDeployTargetPromptSection(ctx) {
|
|
42464
|
+
if (ctx.kind === "closed_menu") {
|
|
42465
|
+
const labels = [...new Set(ctx.entries.map((entry) => entry.label))];
|
|
42466
|
+
return [
|
|
42467
|
+
``,
|
|
42468
|
+
`--- DEPLOY TARGET (required final section) ---`,
|
|
42469
|
+
`This project's deploy topology is already resolved for its current workflow inventory. Pick the ONE label this task deploys under from this CLOSED menu \u2014 never invent a new label:`,
|
|
42470
|
+
...labels.map((label) => `- ${label}`),
|
|
42471
|
+
``,
|
|
42472
|
+
`Append a final section \`## Deploy Target\` whose ONLY content is a single fenced json block of exactly this shape:`,
|
|
42473
|
+
"```json",
|
|
42474
|
+
`{"label": "<one of the menu labels above>"}`,
|
|
42475
|
+
"```"
|
|
42476
|
+
].join("\n");
|
|
42477
|
+
}
|
|
42478
|
+
return [
|
|
42479
|
+
``,
|
|
42480
|
+
`--- DEPLOY TARGET (required final section) ---`,
|
|
42481
|
+
`This project's deploy topology is NOT yet resolved for its current workflow inventory. Below is the raw GitHub workflow list (workflowId / path / blob sha / state); every value used below must be copied VERBATIM from it \u2014 never invented:`,
|
|
42482
|
+
``,
|
|
42483
|
+
JSON.stringify(ctx.snapshot, null, 2),
|
|
42484
|
+
``,
|
|
42485
|
+
`First derive the FULL label map: identify every deploy target in the snapshot, give each a short kebab-case \`label\`, and classify each workflow into an \`env\` of exactly "dev" or "prod".`,
|
|
42486
|
+
`Heuristic for dev vs prod (there is no fixed naming rule): prefer an explicit \`dev-\`/\`prod-\` filename prefix, then a \`/dev/\` or \`/prod/\` directory segment; if neither applies, group the workflows serving the same target and classify from the pair's context.`,
|
|
42487
|
+
`Then pick the ONE \`label\` this task deploys under, from among the labels you just derived.`,
|
|
42488
|
+
``,
|
|
42489
|
+
`Append a final section \`## Deploy Target\` whose ONLY content is a single fenced json block of exactly this shape:`,
|
|
42490
|
+
"```json",
|
|
42491
|
+
`{"label": "<label this task deploys under>", "derive": [{"label":"<target label>","env":"dev"|"prod","workflowId":N,"workflowPath":"<path>","workflowSha":"<blob sha>"}, ...]}`,
|
|
42492
|
+
"```",
|
|
42493
|
+
``,
|
|
42494
|
+
`\`derive\` is the FULL map of every deploy target you can identify in the snapshot (not just this task's pick), so later tasks reuse this derivation instead of re-deriving. \`env\` must be exactly "dev" or "prod". \`workflowId\`/\`workflowPath\`/\`workflowSha\` must be copied verbatim from the snapshot above.`
|
|
42495
|
+
].join("\n");
|
|
42496
|
+
}
|
|
42497
|
+
function resolveDeployTargetRows(canonical, deployTarget, autoDeploy, fingerprint) {
|
|
42498
|
+
if (autoDeploy === "off") return {};
|
|
42499
|
+
const filtered = canonical.filter((entry) => entry.label === deployTarget.label);
|
|
42500
|
+
const requiredEnvs = autoDeploy === "prod" ? ["dev", "prod"] : ["dev"];
|
|
42501
|
+
const missingEnvs = [];
|
|
42502
|
+
const deployTargets = [];
|
|
42503
|
+
for (const env of requiredEnvs) {
|
|
42504
|
+
const match = filtered.find((entry) => entry.env === env);
|
|
42505
|
+
if (!match) {
|
|
42506
|
+
missingEnvs.push(env);
|
|
42507
|
+
continue;
|
|
42508
|
+
}
|
|
42509
|
+
deployTargets.push({
|
|
42510
|
+
env,
|
|
42511
|
+
targetLabel: deployTarget.label,
|
|
42512
|
+
workflowId: match.workflowId,
|
|
42513
|
+
workflowPath: match.workflowPath,
|
|
42514
|
+
workflowSha: match.workflowSha,
|
|
42515
|
+
topologyFingerprint: fingerprint
|
|
42516
|
+
});
|
|
42517
|
+
}
|
|
42518
|
+
if (missingEnvs.length > 0) return { skippedReason: "incomplete_label", missingEnvs };
|
|
42519
|
+
return { deployTargets };
|
|
42520
|
+
}
|
|
42521
|
+
function findDuplicateLabelEnvPairs(entries) {
|
|
42522
|
+
const seen = /* @__PURE__ */ new Set();
|
|
42523
|
+
const duplicateKeys = /* @__PURE__ */ new Set();
|
|
42524
|
+
for (const entry of entries) {
|
|
42525
|
+
const key = `${entry.label}\0${entry.env}`;
|
|
42526
|
+
if (seen.has(key)) duplicateKeys.add(key);
|
|
42527
|
+
seen.add(key);
|
|
42528
|
+
}
|
|
42529
|
+
const pairs = [];
|
|
42530
|
+
for (const key of duplicateKeys) {
|
|
42531
|
+
const parts = key.split("\0");
|
|
42532
|
+
const label = parts[0] ?? "";
|
|
42533
|
+
const env = parts[1] === "prod" ? "prod" : "dev";
|
|
42534
|
+
pairs.push({ label, env });
|
|
42535
|
+
}
|
|
42536
|
+
return pairs;
|
|
42537
|
+
}
|
|
42358
42538
|
function nextWikiPageId(pages) {
|
|
42359
42539
|
let max = 0;
|
|
42360
42540
|
for (const page of pages) {
|
|
@@ -42365,7 +42545,7 @@ function nextWikiPageId(pages) {
|
|
|
42365
42545
|
}
|
|
42366
42546
|
return `WK-${max + 1}`;
|
|
42367
42547
|
}
|
|
42368
|
-
function buildDraftSpecPrompt(task, constitution) {
|
|
42548
|
+
function buildDraftSpecPrompt(task, constitution, deployTarget = null) {
|
|
42369
42549
|
const budget = resolveSpecBudget(task.impact);
|
|
42370
42550
|
const lines2 = [
|
|
42371
42551
|
`Write a complete implementation spec for the task below.`,
|
|
@@ -42402,6 +42582,7 @@ function buildDraftSpecPrompt(task, constitution) {
|
|
|
42402
42582
|
`The \`## TL;DR\` section MUST be at most ${DRAFT_SPEC_TLDR_MAX_CHARS} characters.`,
|
|
42403
42583
|
`The spec MUST contain at most ${budget.maxRequirements} acceptance-criteria items and no more than ${budget.maxChars} characters total.`
|
|
42404
42584
|
);
|
|
42585
|
+
if (deployTarget) lines2.push(buildDeployTargetPromptSection(deployTarget));
|
|
42405
42586
|
return lines2.join("\n");
|
|
42406
42587
|
}
|
|
42407
42588
|
function skipped(reason, extra = {}) {
|
|
@@ -42450,6 +42631,22 @@ async function runDraftSpec(ctx, input, deps) {
|
|
|
42450
42631
|
}
|
|
42451
42632
|
if (getActiveBlockedByIds(task.blockedBy, blockers).length > 0) return skipped("blocked_by_active");
|
|
42452
42633
|
}
|
|
42634
|
+
const autoDeploy = task.autoDeploy ?? "off";
|
|
42635
|
+
let topology = null;
|
|
42636
|
+
let deploySkippedReason = null;
|
|
42637
|
+
let deploySkippedDetail = null;
|
|
42638
|
+
if (autoDeploy !== "off") {
|
|
42639
|
+
try {
|
|
42640
|
+
topology = await client.getDeployTopology(projectSlug);
|
|
42641
|
+
} catch {
|
|
42642
|
+
deploySkippedReason = "topology_unavailable";
|
|
42643
|
+
topology = null;
|
|
42644
|
+
}
|
|
42645
|
+
}
|
|
42646
|
+
if (topology && (topology.fingerprint === null || topology.entries.length === 0 && topology.snapshot === null)) {
|
|
42647
|
+
deploySkippedReason = topology.fingerprint === null ? "no_bound_deploy_target" : "topology_snapshot_unavailable";
|
|
42648
|
+
topology = null;
|
|
42649
|
+
}
|
|
42453
42650
|
let persona;
|
|
42454
42651
|
try {
|
|
42455
42652
|
persona = loadPersonaFn(DRAFT_SPEC_PERSONA);
|
|
@@ -42478,7 +42675,8 @@ async function runDraftSpec(ctx, input, deps) {
|
|
|
42478
42675
|
} catch {
|
|
42479
42676
|
constitution = null;
|
|
42480
42677
|
}
|
|
42481
|
-
const
|
|
42678
|
+
const deployPromptContext = autoDeploy !== "off" && topology ? topology.entries.length > 0 ? { kind: "closed_menu", entries: topology.entries } : { kind: "derive", snapshot: topology.snapshot ?? [] } : null;
|
|
42679
|
+
const prompt = buildDraftSpecPrompt(task, constitution, deployPromptContext);
|
|
42482
42680
|
const exec = await runExec({
|
|
42483
42681
|
prompt,
|
|
42484
42682
|
developerInstructions: persona,
|
|
@@ -42493,7 +42691,8 @@ async function runDraftSpec(ctx, input, deps) {
|
|
|
42493
42691
|
if (exec.text.trim().length === 0) {
|
|
42494
42692
|
return { kind: "retry", failureReason: "zero_output", error: "spec authoring produced empty output" };
|
|
42495
42693
|
}
|
|
42496
|
-
const { markdown,
|
|
42694
|
+
const { markdown: specMarkdown, deployTarget: parsedDeployTarget } = extractDeployTarget(exec.text);
|
|
42695
|
+
const { markdown, tldrChars } = enforceSpecOutputContract(specMarkdown, taskId, task.title);
|
|
42497
42696
|
let pageId;
|
|
42498
42697
|
if (adoptedId) {
|
|
42499
42698
|
try {
|
|
@@ -42553,6 +42752,45 @@ async function runDraftSpec(ctx, input, deps) {
|
|
|
42553
42752
|
}
|
|
42554
42753
|
pageId = created;
|
|
42555
42754
|
}
|
|
42755
|
+
let deployTargets;
|
|
42756
|
+
if (topology && parsedDeployTarget) {
|
|
42757
|
+
const fingerprint = topology.fingerprint;
|
|
42758
|
+
if (fingerprint === null) {
|
|
42759
|
+
deploySkippedReason = deploySkippedReason ?? "no_bound_deploy_target";
|
|
42760
|
+
} else if (topology.entries.length > 0) {
|
|
42761
|
+
const resolved = resolveDeployTargetRows(topology.entries, parsedDeployTarget, autoDeploy, fingerprint);
|
|
42762
|
+
deployTargets = resolved.deployTargets;
|
|
42763
|
+
if (resolved.skippedReason) {
|
|
42764
|
+
deploySkippedReason = resolved.skippedReason;
|
|
42765
|
+
if (resolved.skippedReason === "incomplete_label") {
|
|
42766
|
+
deploySkippedDetail = `label "${parsedDeployTarget.label}" has no entry for: ${(resolved.missingEnvs ?? []).join(", ")}`;
|
|
42767
|
+
}
|
|
42768
|
+
}
|
|
42769
|
+
} else {
|
|
42770
|
+
const derive = parsedDeployTarget.derive ?? [];
|
|
42771
|
+
const duplicateLabelEnvPairs = findDuplicateLabelEnvPairs(derive);
|
|
42772
|
+
if (derive.length === 0) {
|
|
42773
|
+
deploySkippedReason = "no_derivation";
|
|
42774
|
+
} else if (duplicateLabelEnvPairs.length > 0) {
|
|
42775
|
+
deploySkippedReason = "ambiguous_derivation";
|
|
42776
|
+
deploySkippedDetail = `duplicate label/env pair(s): ${duplicateLabelEnvPairs.map((pair) => `${pair.label}/${pair.env}`).join(", ")}`;
|
|
42777
|
+
} else {
|
|
42778
|
+
try {
|
|
42779
|
+
const written = await client.writeDeployTopology(projectSlug, { fingerprint, entries: derive });
|
|
42780
|
+
const resolved = resolveDeployTargetRows(written.entries, parsedDeployTarget, autoDeploy, fingerprint);
|
|
42781
|
+
deployTargets = resolved.deployTargets;
|
|
42782
|
+
if (resolved.skippedReason) {
|
|
42783
|
+
deploySkippedReason = resolved.skippedReason;
|
|
42784
|
+
if (resolved.skippedReason === "incomplete_label") {
|
|
42785
|
+
deploySkippedDetail = `label "${parsedDeployTarget.label}" has no entry for: ${(resolved.missingEnvs ?? []).join(", ")}`;
|
|
42786
|
+
}
|
|
42787
|
+
}
|
|
42788
|
+
} catch {
|
|
42789
|
+
deploySkippedReason = "topology_write_failed";
|
|
42790
|
+
}
|
|
42791
|
+
}
|
|
42792
|
+
}
|
|
42793
|
+
}
|
|
42556
42794
|
let fresh;
|
|
42557
42795
|
try {
|
|
42558
42796
|
fresh = await client.getTask(projectSlug, taskId);
|
|
@@ -42569,8 +42807,12 @@ async function runDraftSpec(ctx, input, deps) {
|
|
|
42569
42807
|
if (fresh.specWikiPageId) {
|
|
42570
42808
|
return skipped("lost_link_race", { pageId, linkedPageId: fresh.specWikiPageId });
|
|
42571
42809
|
}
|
|
42810
|
+
const linkPatch = { specWikiPageId: pageId };
|
|
42811
|
+
if (deployTargets !== void 0 && deployTargets.length > 0) {
|
|
42812
|
+
linkPatch.deployTargets = deployTargets;
|
|
42813
|
+
}
|
|
42572
42814
|
try {
|
|
42573
|
-
await client.updateTask(projectSlug, taskId,
|
|
42815
|
+
await client.updateTask(projectSlug, taskId, linkPatch);
|
|
42574
42816
|
} catch (error51) {
|
|
42575
42817
|
return {
|
|
42576
42818
|
kind: "retry",
|
|
@@ -42578,18 +42820,20 @@ async function runDraftSpec(ctx, input, deps) {
|
|
|
42578
42820
|
error: `failed to link spec page ${pageId} to task ${taskId}: ${errMessage2(error51)}`
|
|
42579
42821
|
};
|
|
42580
42822
|
}
|
|
42823
|
+
const drafted = {
|
|
42824
|
+
outcome: "DRAFTED",
|
|
42825
|
+
pageId,
|
|
42826
|
+
adopted: adoptedId !== null,
|
|
42827
|
+
constitution: constitution !== null ? "included" : "missing",
|
|
42828
|
+
tldrChars,
|
|
42829
|
+
relatedContext: "skipped_no_client_method"
|
|
42830
|
+
};
|
|
42831
|
+
if (deploySkippedReason !== null) drafted.deployTargetSkippedReason = deploySkippedReason;
|
|
42832
|
+
if (deploySkippedDetail !== null) drafted.deployTargetSkippedDetail = deploySkippedDetail;
|
|
42833
|
+
drafted.taskTitle = task.title;
|
|
42581
42834
|
return {
|
|
42582
42835
|
kind: "done",
|
|
42583
|
-
resultJson: JSON.stringify(
|
|
42584
|
-
outcome: "DRAFTED",
|
|
42585
|
-
pageId,
|
|
42586
|
-
adopted: adoptedId !== null,
|
|
42587
|
-
constitution: constitution !== null ? "included" : "missing",
|
|
42588
|
-
tldrChars,
|
|
42589
|
-
relatedContext: "skipped_no_client_method",
|
|
42590
|
-
// Carried so onComplete can name the task in the operator notification without a re-read.
|
|
42591
|
-
taskTitle: task.title
|
|
42592
|
-
}),
|
|
42836
|
+
resultJson: JSON.stringify(drafted),
|
|
42593
42837
|
finalReviewedHash: null
|
|
42594
42838
|
};
|
|
42595
42839
|
}
|
|
@@ -42607,6 +42851,14 @@ function onComplete(outcome, input) {
|
|
|
42607
42851
|
body: `Spec draft for ${input.taskId} skipped (${parsed.reason ?? "unknown"}); nothing changed.`
|
|
42608
42852
|
};
|
|
42609
42853
|
}
|
|
42854
|
+
if (parsed.deployTargetSkippedReason === "ambiguous_derivation" || parsed.deployTargetSkippedReason === "incomplete_label") {
|
|
42855
|
+
return {
|
|
42856
|
+
kind: "handoff",
|
|
42857
|
+
taskId: input.taskId,
|
|
42858
|
+
id: `II-deploy-target-${parsed.deployTargetSkippedReason}-${input.taskId}`,
|
|
42859
|
+
body: `Spec for ${input.taskId} was drafted (page ${parsed.pageId ?? "?"} in Specs/), but its deploy target needs a human: ${parsed.deployTargetSkippedDetail ?? parsed.deployTargetSkippedReason}. The spec itself proceeds to the normal consensus review; the deploy target stays unresolved until this is fixed.`
|
|
42860
|
+
};
|
|
42861
|
+
}
|
|
42610
42862
|
const taskRef = parsed.taskTitle ? `${input.taskId} \u2014 ${parsed.taskTitle}` : input.taskId;
|
|
42611
42863
|
return {
|
|
42612
42864
|
kind: "info",
|
|
@@ -42980,34 +43232,42 @@ async function tryProdAutoRevert(deps, projectSlug, run3, task) {
|
|
|
42980
43232
|
return null;
|
|
42981
43233
|
}
|
|
42982
43234
|
}
|
|
42983
|
-
function
|
|
42984
|
-
return workflows.map((workflow) => `${workflow.id}:${workflow.path}`).sort().join(",");
|
|
42985
|
-
}
|
|
42986
|
-
async function resolveWorkflowId(deps, projectSlug, run3, env) {
|
|
43235
|
+
async function resolveWorkflowId(deps, projectSlug, run3, task, env) {
|
|
42987
43236
|
const pinned = env === "dev" ? run3.devWorkflowId : run3.prodWorkflowId;
|
|
42988
|
-
if (pinned)
|
|
42989
|
-
|
|
42990
|
-
|
|
42991
|
-
|
|
42992
|
-
|
|
42993
|
-
|
|
42994
|
-
|
|
42995
|
-
|
|
42996
|
-
|
|
42997
|
-
|
|
42998
|
-
|
|
42999
|
-
|
|
43000
|
-
|
|
43001
|
-
|
|
43002
|
-
|
|
43003
|
-
|
|
43004
|
-
|
|
43005
|
-
|
|
43006
|
-
|
|
43007
|
-
|
|
43008
|
-
|
|
43237
|
+
if (pinned) {
|
|
43238
|
+
return { workflowId: pinned, workflowPath: null, blockReason: null, pinned: true, reverifyRequested: false };
|
|
43239
|
+
}
|
|
43240
|
+
const target = await deps.getTaskDeployTarget(projectSlug, task.id, env);
|
|
43241
|
+
if (!target) {
|
|
43242
|
+
return { workflowId: null, workflowPath: null, blockReason: "deploy_target_unset", pinned: false, reverifyRequested: false };
|
|
43243
|
+
}
|
|
43244
|
+
const snapshot = await deps.getRepoSnapshot(projectSlug);
|
|
43245
|
+
const activeWorkflows = (snapshot?.lastSnapshot ?? []).filter((w) => w.state === "active");
|
|
43246
|
+
const liveMatch = activeWorkflows.find((w) => w.id === target.workflowId);
|
|
43247
|
+
if (!liveMatch || liveMatch.path !== target.workflowPath) {
|
|
43248
|
+
return { workflowId: null, workflowPath: null, blockReason: "deploy_target_inactive", pinned: false, reverifyRequested: false };
|
|
43249
|
+
}
|
|
43250
|
+
if (liveMatch.sha !== target.workflowSha) {
|
|
43251
|
+
return { workflowId: null, workflowPath: null, blockReason: "deploy_target_modified", pinned: false, reverifyRequested: false };
|
|
43252
|
+
}
|
|
43253
|
+
const currentFingerprint = snapshot?.lastFingerprint ?? null;
|
|
43254
|
+
if (currentFingerprint !== null && currentFingerprint === target.topologyFingerprint) {
|
|
43255
|
+
return { workflowId: target.workflowId, workflowPath: target.workflowPath, blockReason: null, pinned: false, reverifyRequested: false };
|
|
43256
|
+
}
|
|
43257
|
+
if (currentFingerprint === null) {
|
|
43258
|
+
deps.recordDeployTargetTelemetry("deploy_target_cache_missing");
|
|
43259
|
+
return { workflowId: null, workflowPath: null, blockReason: "deploy_target_cache_missing", pinned: false, reverifyRequested: false };
|
|
43009
43260
|
}
|
|
43010
|
-
|
|
43261
|
+
const entry = await deps.lookupTopologyEntry(projectSlug, currentFingerprint, target.targetLabel, env);
|
|
43262
|
+
if (!entry) {
|
|
43263
|
+
deps.recordDeployTargetTelemetry("deploy_target_cache_missing");
|
|
43264
|
+
return { workflowId: null, workflowPath: null, blockReason: "deploy_target_cache_missing", pinned: false, reverifyRequested: false };
|
|
43265
|
+
}
|
|
43266
|
+
if (entry.workflowId !== target.workflowId) {
|
|
43267
|
+
deps.recordDeployTargetTelemetry("deploy_target_reassigned");
|
|
43268
|
+
return { workflowId: null, workflowPath: null, blockReason: "deploy_target_reassigned", pinned: false, reverifyRequested: false };
|
|
43269
|
+
}
|
|
43270
|
+
return { workflowId: target.workflowId, workflowPath: target.workflowPath, blockReason: null, pinned: false, reverifyRequested: true };
|
|
43011
43271
|
}
|
|
43012
43272
|
async function runDeployGateTick(deps, projectSlug, run3, task) {
|
|
43013
43273
|
const artifacts = await deps.client.listExecutionArtifacts(projectSlug, run3.id);
|
|
@@ -43029,7 +43289,10 @@ async function runDeployGateTick(deps, projectSlug, run3, task) {
|
|
|
43029
43289
|
return { kind: "done" };
|
|
43030
43290
|
}
|
|
43031
43291
|
const env = activeEnv;
|
|
43032
|
-
const resolution = await resolveWorkflowId(deps, projectSlug, run3, env);
|
|
43292
|
+
const resolution = await resolveWorkflowId(deps, projectSlug, run3, task, env);
|
|
43293
|
+
if (resolution.reverifyRequested) {
|
|
43294
|
+
await deps.setTaskReverifyRequested(projectSlug, task.id);
|
|
43295
|
+
}
|
|
43033
43296
|
const workflowId = resolution.workflowId;
|
|
43034
43297
|
const attempt = latestByEnv(artifacts, "deploy_attempt", env, { realAttemptsOnly: true });
|
|
43035
43298
|
if (!attempt) {
|
|
@@ -43056,9 +43319,13 @@ async function runDeployGateTick(deps, projectSlug, run3, task) {
|
|
|
43056
43319
|
await deps.handoff(projectSlug, task, run3, "verification_steps_missing");
|
|
43057
43320
|
return { kind: "handoff", reason: "verification_steps_missing" };
|
|
43058
43321
|
}
|
|
43322
|
+
if (resolution.blockReason) {
|
|
43323
|
+
await deps.handoff(projectSlug, task, run3, `${resolution.blockReason}:${env}`);
|
|
43324
|
+
return { kind: "handoff", reason: `${resolution.blockReason}:${env}` };
|
|
43325
|
+
}
|
|
43059
43326
|
if (!workflowId) {
|
|
43060
|
-
await deps.handoff(projectSlug, task, run3, `
|
|
43061
|
-
return { kind: "handoff", reason: `
|
|
43327
|
+
await deps.handoff(projectSlug, task, run3, `deploy_target_unset:${env}`);
|
|
43328
|
+
return { kind: "handoff", reason: `deploy_target_unset:${env}` };
|
|
43062
43329
|
}
|
|
43063
43330
|
if (!run3.branch) {
|
|
43064
43331
|
await deps.handoff(projectSlug, task, run3, "run_branch_missing");
|
|
@@ -43076,19 +43343,13 @@ async function runDeployGateTick(deps, projectSlug, run3, task) {
|
|
|
43076
43343
|
try {
|
|
43077
43344
|
await deps.client.dispatchTaskDeploy(projectSlug, task.id, { env, ref: run3.branch, workflowId: String(workflowId) });
|
|
43078
43345
|
} catch (error51) {
|
|
43079
|
-
if (resolution.
|
|
43080
|
-
|
|
43081
|
-
projectSlug,
|
|
43082
|
-
task,
|
|
43083
|
-
run3,
|
|
43084
|
-
`deploy_workflow_operator_choice_stale:${env}:${String(error51).slice(0, 120)}`
|
|
43085
|
-
);
|
|
43086
|
-
return { kind: "handoff", reason: `deploy_workflow_operator_choice_stale:${env}` };
|
|
43346
|
+
if (!resolution.pinned && !resolution.reverifyRequested) {
|
|
43347
|
+
deps.recordDeployTargetTelemetry("deploy_dispatch_failed_fingerprint_unchanged");
|
|
43087
43348
|
}
|
|
43088
43349
|
await deps.handoff(projectSlug, task, run3, `workflow_dispatch_failed:${env}:${String(error51).slice(0, 120)}`);
|
|
43089
43350
|
return { kind: "handoff", reason: `workflow_dispatch_failed:${env}` };
|
|
43090
43351
|
}
|
|
43091
|
-
if (resolution.
|
|
43352
|
+
if (!resolution.pinned) {
|
|
43092
43353
|
await deps.client.updateExecutionRun(projectSlug, run3.id, {
|
|
43093
43354
|
[env === "dev" ? "devWorkflowId" : "prodWorkflowId"]: workflowId
|
|
43094
43355
|
});
|
|
@@ -45180,7 +45441,35 @@ async function runBackstopSweep(deps, drive = (slug, id) => driveRun(deps, slug,
|
|
|
45180
45441
|
await deployGateHandoff(deps, projectSlug, t, r.id, reason, r.leaseId);
|
|
45181
45442
|
},
|
|
45182
45443
|
budgetSeconds: deps.deployGate.budgetSeconds ?? deployPollBudgetSeconds(),
|
|
45183
|
-
now: () => /* @__PURE__ */ new Date()
|
|
45444
|
+
now: () => /* @__PURE__ */ new Date(),
|
|
45445
|
+
getTaskDeployTarget: async (projectSlug, taskId, env) => deps.client.getTaskDeployTarget(projectSlug, taskId, env),
|
|
45446
|
+
getRepoSnapshot: async (projectSlug) => {
|
|
45447
|
+
try {
|
|
45448
|
+
const topology = await deps.client.getDeployTopology(projectSlug);
|
|
45449
|
+
return { lastFingerprint: topology.fingerprint, lastSnapshot: topology.snapshot };
|
|
45450
|
+
} catch {
|
|
45451
|
+
return null;
|
|
45452
|
+
}
|
|
45453
|
+
},
|
|
45454
|
+
lookupTopologyEntry: async (projectSlug, fingerprint, label, env) => {
|
|
45455
|
+
try {
|
|
45456
|
+
const topology = await deps.client.getDeployTopology(projectSlug);
|
|
45457
|
+
if (topology.fingerprint !== fingerprint) return null;
|
|
45458
|
+
const entry = topology.entries.find((e) => e.label === label && e.env === env);
|
|
45459
|
+
return entry ? { workflowId: entry.workflowId, workflowPath: entry.workflowPath, workflowSha: entry.workflowSha } : null;
|
|
45460
|
+
} catch {
|
|
45461
|
+
return null;
|
|
45462
|
+
}
|
|
45463
|
+
},
|
|
45464
|
+
setTaskReverifyRequested: async (projectSlug, taskId) => {
|
|
45465
|
+
await deps.client.updateTask(projectSlug, taskId, { reverifyRequested: true }).catch(() => {
|
|
45466
|
+
});
|
|
45467
|
+
},
|
|
45468
|
+
recordDeployTargetTelemetry: (counter) => {
|
|
45469
|
+
console.log(
|
|
45470
|
+
JSON.stringify({ event: "deploy_target_telemetry", counter, projectSlug: slug, taskId: task.id })
|
|
45471
|
+
);
|
|
45472
|
+
}
|
|
45184
45473
|
};
|
|
45185
45474
|
await runDeployGateTick(gateDeps, slug, run3, task);
|
|
45186
45475
|
} catch {
|
|
@@ -50366,12 +50655,13 @@ var DEFAULT_DIRECTOR_BRIDGE_ALLOWED_ORIGINS = [
|
|
|
50366
50655
|
"http://localhost:*",
|
|
50367
50656
|
"http://127.0.0.1:*",
|
|
50368
50657
|
"http://[::1]:*",
|
|
50369
|
-
// Staging
|
|
50370
|
-
//
|
|
50371
|
-
//
|
|
50372
|
-
//
|
|
50373
|
-
|
|
50374
|
-
"https://
|
|
50658
|
+
// Staging decoupled onto the stable custom domain staging.mrrlin.com on 2026-08-14 (Cloudflare DNS
|
|
50659
|
+
// in front of the mrrlin3 Vercel project). The Vercel-owned web alias (mrrlin-web-mrrlin3.vercel.app)
|
|
50660
|
+
// and the old app.mrrlin.dev origin are dropped from the defaults: the browser now reaches the
|
|
50661
|
+
// bridge from the owned domain, which is stable across future Vercel project/team moves.
|
|
50662
|
+
// See docs/reference/deployments.md.
|
|
50663
|
+
"https://staging.mrrlin.com",
|
|
50664
|
+
// Production web (unchanged).
|
|
50375
50665
|
"https://mrrlin-web.vercel.app"
|
|
50376
50666
|
];
|
|
50377
50667
|
function normalizeReasoningEffort(value) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrrlin-dev/mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.68",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mrrlin-mcp": "dist/bin.cjs"
|
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
"@types/ws": "^8.18.1",
|
|
22
22
|
"esbuild": "^0.24.0",
|
|
23
23
|
"tsx": "^4.22.3",
|
|
24
|
-
"@mrrlin/codex-client": "0.0.0",
|
|
25
|
-
"@mrrlin/director-e2e": "0.0.0",
|
|
26
|
-
"@mrrlin/client": "0.0.0",
|
|
27
|
-
"@mrrlin/schemas": "0.0.0",
|
|
28
24
|
"@mrrlin/wiki": "0.0.0",
|
|
29
|
-
"@mrrlin/
|
|
25
|
+
"@mrrlin/schemas": "0.0.0",
|
|
26
|
+
"@mrrlin/client": "0.0.0",
|
|
27
|
+
"@mrrlin/tsconfig": "0.0.0",
|
|
28
|
+
"@mrrlin/director-e2e": "0.0.0",
|
|
29
|
+
"@mrrlin/codex-client": "0.0.0"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@iarna/toml": "^2.2.5",
|