@mrrlin-dev/mcp 0.4.117 → 0.4.118

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin.cjs +153 -5
  2. package/package.json +1 -1
package/dist/bin.cjs CHANGED
@@ -57873,6 +57873,70 @@ function registerLocalOnlyCheckout(registry3, input, machineLabel, options = {},
57873
57873
  registry3.confirm(input.projectSlug, validated.realPath, now);
57874
57874
  return { ok: true, data: { projectSlug: input.projectSlug, path: validated.realPath, confirmedAt: now, machineLabel } };
57875
57875
  }
57876
+ var DEFAULT_GITIGNORE = ["node_modules/", "dist/", "build/", ".DS_Store", ".env", ".env.*", "*.log", ""].join("\n");
57877
+ function runGit(realPath, args) {
57878
+ return (0, import_node_child_process14.execFileSync)("git", ["-C", realPath, ...args], {
57879
+ encoding: "utf8",
57880
+ stdio: ["ignore", "pipe", "pipe"],
57881
+ timeout: 6e4,
57882
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
57883
+ }).trim();
57884
+ }
57885
+ function initLocalRepo(registry3, input, machineLabel, options = {}, now = (/* @__PURE__ */ new Date()).toISOString()) {
57886
+ if (input.operatorConsent !== true) {
57887
+ return {
57888
+ ok: false,
57889
+ code: "consent_required",
57890
+ detail: "Ask the operator in chat, in plain words, whether their project folder may be turned into a git repository, and call this again only after they say yes."
57891
+ };
57892
+ }
57893
+ const codeHost = input.project.codeHost ?? null;
57894
+ if (!(codeHost === "local" || codeHost === null && !input.project.githubRepo)) {
57895
+ return { ok: false, code: "not_local_project", detail: `Project ${input.projectSlug} keeps its code on ${codeHost ?? "GitHub"}.` };
57896
+ }
57897
+ const checked = checkLocalPathRules(input.path, options);
57898
+ if (!checked.ok) return checked;
57899
+ if (gitTopLevel(checked.realPath) !== null) {
57900
+ return {
57901
+ ok: false,
57902
+ code: "inside_git_repo",
57903
+ detail: `${checked.realPath} is already inside a git repository; register that repository with register_local_checkout instead.`
57904
+ };
57905
+ }
57906
+ const recheck = checkLocalPathRules(checked.realPath, options);
57907
+ if (!recheck.ok || recheck.realPath !== checked.realPath) {
57908
+ return { ok: false, code: "path_changed", detail: `${checked.realPath} changed while it was being checked; nothing was done.` };
57909
+ }
57910
+ const realPath = recheck.realPath;
57911
+ let wroteGitignore = false;
57912
+ try {
57913
+ runGit(realPath, ["init", "-q", "-b", "main"]);
57914
+ const gitignore = import_node_path16.default.join(realPath, ".gitignore");
57915
+ try {
57916
+ (0, import_node_fs9.writeFileSync)(gitignore, DEFAULT_GITIGNORE, { flag: "wx" });
57917
+ wroteGitignore = true;
57918
+ } catch {
57919
+ }
57920
+ runGit(realPath, ["add", "-A"]);
57921
+ runGit(realPath, [
57922
+ "-c",
57923
+ "user.name=Mrrlin",
57924
+ "-c",
57925
+ "user.email=bridge@mrrlin.local",
57926
+ "commit",
57927
+ "-q",
57928
+ "--no-verify",
57929
+ "--allow-empty",
57930
+ "-m",
57931
+ "Initial commit (Mrrlin)"
57932
+ ]);
57933
+ } catch (error51) {
57934
+ return { ok: false, code: "init_failed", detail: error51 instanceof Error ? error51.message : String(error51) };
57935
+ }
57936
+ const initialCommit = runGit(realPath, ["rev-parse", "HEAD"]);
57937
+ registry3.confirm(input.projectSlug, realPath, now);
57938
+ return { ok: true, data: { projectSlug: input.projectSlug, path: realPath, confirmedAt: now, machineLabel, initialCommit, wroteGitignore } };
57939
+ }
57876
57940
 
57877
57941
  // src/operator-token.ts
57878
57942
  var import_node_fs10 = require("node:fs");
@@ -58021,6 +58085,8 @@ var mcpToolNames = {
58021
58085
  recordProducedUrl: "record_produced_url",
58022
58086
  updateTaskState: "update_task_state",
58023
58087
  registerLocalCheckout: "register_local_checkout",
58088
+ initLocalRepo: "init_local_repo",
58089
+ setProjectCodeHost: "set_project_code_host",
58024
58090
  /** Compact-context retrieval: rehydrate a mrrlin:// handle into the canonical full record. */
58025
58091
  retrieveContext: "retrieve_context"
58026
58092
  };
@@ -58729,6 +58795,13 @@ var registerLocalCheckoutInputSchema = projectScopedSchema.extend({
58729
58795
  "The project's bound GitHub repo full name in `owner/repo` form. Omit it for a project with no GitHub (its code lives only on this computer): the folder then only has to be a git repository the operator owns."
58730
58796
  )
58731
58797
  });
58798
+ var initLocalRepoInputSchema = projectScopedSchema.extend({
58799
+ path: external_exports.string().min(1).describe("Absolute path to the operator's project folder (not inside any git repository)."),
58800
+ operatorConsent: external_exports.literal(true).describe("true only after the operator explicitly agreed in chat that this folder may become a git repository.")
58801
+ });
58802
+ var setProjectCodeHostInputSchema = projectScopedSchema.extend({
58803
+ codeHost: mrrlinProjectCodeHostSchema.nullable().describe("local = keep code only on this computer; github / gitlab = the operator wants to push there (connect it next); null = not decided.")
58804
+ });
58732
58805
  var retrieveContextInputSchema = external_exports.object({
58733
58806
  handle: external_exports.string().min(1).describe(
58734
58807
  "A mrrlin:// handle emitted in a compact envelope. Examples: mrrlin://task/{slug}/GT-123, mrrlin://wiki/{slug}/WK-1, mrrlin://project/{slug}/snapshot, mrrlin://run/{slug}/ER-9, mrrlin://run/{slug}/ER-9/artifacts."
@@ -58800,6 +58873,8 @@ var mcpToolInputSchemas = {
58800
58873
  [mcpToolNames.recordProducedUrl]: recordProducedUrlInputSchema,
58801
58874
  [mcpToolNames.updateTaskState]: updateTaskStateInputSchema,
58802
58875
  [mcpToolNames.registerLocalCheckout]: registerLocalCheckoutInputSchema,
58876
+ [mcpToolNames.initLocalRepo]: initLocalRepoInputSchema,
58877
+ [mcpToolNames.setProjectCodeHost]: setProjectCodeHostInputSchema,
58803
58878
  [mcpToolNames.retrieveContext]: retrieveContextInputSchema
58804
58879
  };
58805
58880
  var mcpToolOperationIds = {
@@ -58923,6 +58998,8 @@ var mcpToolDescriptions = {
58923
58998
  [mcpToolNames.stopExecutionRun]: "Operator stop: park an in-flight execution run at handoff_requested with blockingReason operator_stopped. Parks rather than cancels, so restart_execution_run can resume it later. Refuses (409) a run already at a terminal status.",
58924
58999
  [mcpToolNames.resolveHandoff]: "Resolve a tool_assisted handoff for an execution run (outcome success|failure). Success returns the task to the board; failure opens a new handoff cycle. Idempotent.",
58925
59000
  [mcpToolNames.registerLocalCheckout]: "Register the operator-local git checkout path for `projectSlug` so execution-run workers run code on this machine instead of the cloud. Validates the path is a git repo whose `origin` remote matches `repo`, then writes the (slug -> absolute path) entry to the operator-local checkout registry. For a project with no GitHub (code kept only on this computer), omit `repo`: the path must then be the top of a git repository owned by the operator, not the home folder or a system folder. Call this after locating the checkout on disk (e.g. via filesystem search) and confirming the choice with the user in chat \u2014 they should NOT have to paste the path into Settings. Returns `{ projectSlug, path, confirmedAt }`. Idempotent: re-registering overwrites the prior entry.",
59001
+ [mcpToolNames.initLocalRepo]: "For a project with no GitHub whose folder is not a git repository yet: run `git init` and one initial commit in the operator's own folder, then register it. Call ONLY after asking the operator in chat, in plain words, and getting an explicit yes; pass operatorConsent: true. Refuses a folder already inside a git repository, the home folder, a filesystem root or a system folder. Adds a default .gitignore (node_modules, build output, .env files) only when the folder has none.",
59002
+ [mcpToolNames.setProjectCodeHost]: "Record where this project's code lives, after the operator answered the code-host question: local (keep it on this computer), github or gitlab (they want to push there; connect it next). Does not move any code.",
58926
59003
  [mcpToolNames.retrieveContext]: "Rehydrate a mrrlin:// handle emitted by the compact-context layer into the full canonical Mrrlin record (task/wiki page/project snapshot/execution run/artifacts). Use whenever you need the full source \u2014 irreversible decisions (closing tasks, dispatching deploys, writing specs) MUST call this first; never act on a compact envelope alone. Returns `{ kind, data }`. Invalid or unsupported handle \u2192 structured `HANDLE_INVALID` error."
58927
59004
  };
58928
59005
  var ARTIFACT_EXTENSION_TYPES = {
@@ -60073,6 +60150,53 @@ function createMrrlinTools(options) {
60073
60150
  return result.data;
60074
60151
  }
60075
60152
  ),
60153
+ [mcpToolNames.initLocalRepo]: makeTool(
60154
+ client,
60155
+ mcpToolNames.initLocalRepo,
60156
+ "LOCAL_REPO_INIT_FAILED",
60157
+ "Unable to initialise the local repository.",
60158
+ async (c, { projectSlug, path: folder, operatorConsent }) => {
60159
+ await checkTenantBoundary(projectSlug, "local repository");
60160
+ const snapshot = await c.getProjectSnapshot(projectSlug);
60161
+ const result = initLocalRepo(
60162
+ new CheckoutRegistry(resolveStateDir()),
60163
+ {
60164
+ projectSlug,
60165
+ path: folder,
60166
+ operatorConsent,
60167
+ project: { codeHost: snapshot.project.codeHost ?? null, githubRepo: snapshot.github?.repo ?? null }
60168
+ },
60169
+ import_node_os11.default.hostname()
60170
+ );
60171
+ if (!result.ok) {
60172
+ const codeMap = {
60173
+ not_absolute: "LOCAL_CHECKOUT_PATH_NOT_ABSOLUTE",
60174
+ not_found: "LOCAL_CHECKOUT_PATH_NOT_FOUND",
60175
+ not_a_directory: "LOCAL_CHECKOUT_PATH_NOT_FOUND",
60176
+ not_owned: "LOCAL_CHECKOUT_PATH_FORBIDDEN",
60177
+ forbidden_path: "LOCAL_CHECKOUT_PATH_FORBIDDEN",
60178
+ path_changed: "LOCAL_CHECKOUT_PATH_FORBIDDEN",
60179
+ not_a_git_repo: "LOCAL_CHECKOUT_NOT_A_GIT_REPO",
60180
+ inside_git_repo: "LOCAL_REPO_ALREADY_EXISTS",
60181
+ not_local_project: "LOCAL_CHECKOUT_REPO_REQUIRED",
60182
+ consent_required: "LOCAL_REPO_CONSENT_REQUIRED",
60183
+ init_failed: "LOCAL_REPO_INIT_FAILED"
60184
+ };
60185
+ throw new McpToolCodedError(codeMap[result.code], result.detail);
60186
+ }
60187
+ return result.data;
60188
+ }
60189
+ ),
60190
+ [mcpToolNames.setProjectCodeHost]: makeTool(
60191
+ client,
60192
+ mcpToolNames.setProjectCodeHost,
60193
+ "PROJECT_CODE_HOST_FAILED",
60194
+ "Unable to record where the project's code lives.",
60195
+ async (c, { projectSlug, codeHost }) => {
60196
+ await checkTenantBoundary(projectSlug, "project code host");
60197
+ return await c.renameProject(projectSlug, { codeHost });
60198
+ }
60199
+ ),
60076
60200
  [mcpToolNames.retrieveContext]: makeTool(
60077
60201
  client,
60078
60202
  mcpToolNames.retrieveContext,
@@ -61948,7 +62072,7 @@ Do NOT derail the current task - if the operator has an in-flight question, answ
61948
62072
  }
61949
62073
 
61950
62074
  // src/_generated/version.ts
61951
- var PKG_VERSION = "0.4.117";
62075
+ var PKG_VERSION = "0.4.118";
61952
62076
 
61953
62077
  // src/receipt-signing.ts
61954
62078
  var import_node_crypto13 = __toESM(require("node:crypto"), 1);
@@ -66790,6 +66914,9 @@ ${renderTaskStateSection(taskState)}`;
66790
66914
  localGitMode = false;
66791
66915
  }
66792
66916
  }
66917
+ if (claimed.codeExecution && localGitMode) {
66918
+ await askCodeHostOnce(deps, projectSlug, task.id);
66919
+ }
66793
66920
  if (claimed.codeExecution && localGitMode && !checkout) {
66794
66921
  await failRun(
66795
66922
  deps,
@@ -67871,6 +67998,27 @@ async function resetWedgedThread(deps, projectSlug, taskId, interruptFailed) {
67871
67998
  await deps.client.resetDirectorThread(projectSlug, taskId).catch(() => {
67872
67999
  });
67873
68000
  }
68001
+ function codeHostQuestionId(projectSlug) {
68002
+ return `II-code-host-${projectSlug.replace(/[^A-Za-z0-9._-]/g, "-")}`;
68003
+ }
68004
+ async function askCodeHostOnce(deps, projectSlug, taskId) {
68005
+ try {
68006
+ const snapshot = await deps.client.getProjectSnapshot(projectSlug);
68007
+ if (snapshot.project.codeHost != null) return;
68008
+ await deps.client.createInboxItem(projectSlug, {
68009
+ id: codeHostQuestionId(projectSlug),
68010
+ kind: "question",
68011
+ taskId,
68012
+ body: "Your project's code is being saved on this computer, in your project folder. Should it stay only here, or would you also like a copy on GitHub or GitLab (useful for backups and teammates)?",
68013
+ questionOptions: [
68014
+ { label: "Keep it on this computer", answer: "local", description: "Nothing is uploaded anywhere.", recommended: true },
68015
+ { label: "Also put it on GitHub", answer: "github", description: "You will be asked to connect GitHub." },
68016
+ { label: "Also put it on GitLab", answer: "gitlab", description: "You will be asked to connect GitLab." }
68017
+ ]
68018
+ });
68019
+ } catch {
68020
+ }
68021
+ }
67874
68022
  async function deliverLocalRun(deps, input) {
67875
68023
  const { projectSlug, task, runId, leaseId } = input;
67876
68024
  if (!input.baseSha) {
@@ -74951,7 +75099,7 @@ function buildThreadStartParams(msg, config2, desiredModel) {
74951
75099
  "- Reply in the operator's language for the whole turn (questions, task scopes, summaries).",
74952
75100
  "- Keep chat short and human. No code blocks, DDL, terminal output, local filesystem paths, internal job/inbox UUIDs or alert markers in chat \u2014 put long content into a wiki page and link it.",
74953
75101
  "- Spec pages in folder Specs must contain `## Acceptance criteria` followed by `- [ ]` items.",
74954
- "- AGNOSTIC DELIVERY MANDATE: Mrrlin is an Agnostic Delivery Platform decoupled from mandatory GitHub. Delivery is routed strictly by artifact kind: (1) document -> wiki (specs, policies, legal compliance playbooks, project documentation); (2) page -> publish (web pages, landing pages, interactive HTML previews); (3) file_set -> artifacts (file uploads, bundles, build zips); (4) message -> send (outbound webhooks, Slack, notifications); (5) code_change -> repo_host (git / GitHub ONLY when modifying source repository code). NEVER stall or refuse non-code tasks (such as marketing landing pages, GDPR checklists, or legal compliance playbooks) by demanding a Git repository. Non-code tasks deliver directly via page (Publish) or document (Wiki).",
75102
+ "- AGNOSTIC DELIVERY MANDATE: Mrrlin is an Agnostic Delivery Platform decoupled from mandatory GitHub. Delivery is routed strictly by artifact kind: (1) document -> wiki; (2) page -> publish (web pages, landing pages, interactive HTML previews); (3) file_set -> artifacts (file uploads, bundles, build zips); (4) message -> send (outbound webhooks, Slack, notifications); (5) code_change -> repo_host (GitHub; with no GitHub, a local git repo on the operator's computer: register_local_checkout without repo, init_local_repo only after an explicit yes, record their push choice with set_project_code_host). NEVER stall or refuse non-code tasks by demanding a Git repository. Non-code tasks deliver directly via page (Publish) or document (Wiki).",
74955
75103
  "- 3-TIER COMMERCIAL SCOPE GOVERNANCE: Treat tasks by domain governance tier. (1) Tier 1 Core Tech Delivery (backend, frontend, SRE, databases): decompose autonomously into repository code changes (when a repo is bound) or code artifacts. (2) Tier 2 Planning & Content (landing pages, SEO, marketing): create actionable drafts; when details such as a phone number, brand name or copy are missing, do not loop on questions \u2014 create the task, record `## Placeholders: [NEED_INPUT: <item>]` in the Wiki spec, and set `autonomyLevel: 'human_review'`. (3) Tier 3 Guardrailed Advisory (high risk: privacy/GDPR, security/SOC 2, billing, production database migrations, infrastructure): always set `autonomyLevel: 'human_review'`, put the domain's standard safety controls into the spec, and state that irreversible actions need human sign-off.",
74956
75104
  "- INBOX QUESTION RETIREMENT MANDATE \u2014 Retiring answered questions: Before creating a task/plan, retire only pending Inbox questions the operator actually answered. Call `decide_inbox_item(itemId: <exact id>, decidedBy: 'director', verdict: 'acknowledged', answer: <actual answer>)`. Use `verdict: 'rejected', reason: ...` only if moot. Never close an unrelated item.",
74957
75105
  "- CHAT CLARIFICATIONS ARE IN CHAT ONLY: During synchronous interactive Director chat, ask clarifying questions directly in your chat response. DO NOT call `create_inbox_item(kind: 'question')` for routine conversational clarifications in Turn 1 \u2014 Inbox questions are reserved for autonomous background workers or asynchronous gate pauses that occur while the operator is away.",
@@ -75439,7 +75587,7 @@ async function runCollectedAgentTurn(handle, prompt, cwd, baseInstructions) {
75439
75587
  }
75440
75588
  function resolveDefaultBranch(checkoutPath) {
75441
75589
  if (!checkoutPath) return "main";
75442
- const runGit = (args) => {
75590
+ const runGit2 = (args) => {
75443
75591
  try {
75444
75592
  return (0, import_node_child_process25.execFileSync)("git", ["-C", checkoutPath, ...args], {
75445
75593
  encoding: "utf8",
@@ -75451,13 +75599,13 @@ function resolveDefaultBranch(checkoutPath) {
75451
75599
  return null;
75452
75600
  }
75453
75601
  };
75454
- const symRef = runGit(["symbolic-ref", "refs/remotes/origin/HEAD"]);
75602
+ const symRef = runGit2(["symbolic-ref", "refs/remotes/origin/HEAD"]);
75455
75603
  if (symRef) {
75456
75604
  const prefix = "refs/remotes/origin/";
75457
75605
  const branch = symRef.startsWith(prefix) ? symRef.slice(prefix.length) : symRef;
75458
75606
  if (branch) return branch;
75459
75607
  }
75460
- const remoteShow = runGit(["remote", "show", "origin"]);
75608
+ const remoteShow = runGit2(["remote", "show", "origin"]);
75461
75609
  if (remoteShow) {
75462
75610
  const match = /HEAD branch:\s*(\S+)/.exec(remoteShow);
75463
75611
  const branch = match?.[1];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrlin-dev/mcp",
3
- "version": "0.4.117",
3
+ "version": "0.4.118",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mrrlin-mcp": "dist/bin.cjs"