@selfagency/beans-mcp 0.5.0 → 0.6.1

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/index.js CHANGED
@@ -22801,7 +22801,7 @@ __export(backend_exports, {
22801
22801
  });
22802
22802
  import { execFile } from "child_process";
22803
22803
  import { createReadStream } from "fs";
22804
- import { mkdir, readFile, rm, writeFile } from "fs/promises";
22804
+ import { mkdir, readFile, realpath, rename, rm, writeFile } from "fs/promises";
22805
22805
  import { dirname, join, resolve as resolve2 } from "path";
22806
22806
  import { createInterface } from "readline";
22807
22807
  import { promisify } from "util";
@@ -22902,6 +22902,26 @@ Output: ${stdout.slice(0, 1e3)}`
22902
22902
  });
22903
22903
  return { initialized: true };
22904
22904
  }
22905
+ async archive() {
22906
+ const { stdout } = await execFileAsync(this.cliPath, ["archive", "--json"], {
22907
+ cwd: this.workspaceRoot,
22908
+ env: this.getSafeEnv(),
22909
+ maxBuffer: 10 * 1024 * 1024,
22910
+ timeout: 3e4
22911
+ });
22912
+ this.invalidateCache();
22913
+ if (!stdout.trim()) {
22914
+ return { archived: true };
22915
+ }
22916
+ try {
22917
+ return JSON.parse(stdout);
22918
+ } catch {
22919
+ return { archived: true, output: stdout.trim() };
22920
+ }
22921
+ }
22922
+ async queryGraphql(query, variables) {
22923
+ return this.executeGraphQL(query, variables);
22924
+ }
22905
22925
  async list(options) {
22906
22926
  const filter = {};
22907
22927
  if (options?.status && options.status.length > 0) {
@@ -23056,32 +23076,39 @@ Output: ${stdout.slice(0, 1e3)}`
23056
23076
  return { deleted: true, beanId };
23057
23077
  }
23058
23078
  async bulkCreate(beans, defaultParent) {
23059
- const results = [];
23060
- for (const item of beans) {
23061
- try {
23062
- const bean = await this.create({
23079
+ const settled = await Promise.allSettled(
23080
+ beans.map(
23081
+ async (item) => this.create({
23063
23082
  ...item,
23064
23083
  parent: item.parent ?? defaultParent
23065
- });
23066
- results.push({ bean });
23067
- } catch (error48) {
23068
- results.push({ error: error48.message });
23069
- }
23070
- }
23071
- return results;
23084
+ })
23085
+ )
23086
+ );
23087
+ return settled.map(
23088
+ (result) => result.status === "fulfilled" ? { bean: result.value } : { error: result.reason instanceof Error ? result.reason.message : String(result.reason) }
23089
+ );
23072
23090
  }
23073
23091
  async bulkUpdate(beans, defaultParent) {
23074
- const results = [];
23075
- for (const { beanId, ...updates } of beans) {
23076
- try {
23092
+ const settled = await Promise.allSettled(
23093
+ beans.map(async ({ beanId, ...updates }) => {
23077
23094
  const resolvedParent = updates.parent ?? (updates.clearParent ? void 0 : defaultParent);
23078
23095
  const bean = await this.update(beanId, { ...updates, parent: resolvedParent });
23079
- results.push({ beanId, bean });
23080
- } catch (error48) {
23081
- results.push({ beanId, error: error48.message });
23096
+ return { beanId, bean };
23097
+ })
23098
+ );
23099
+ return settled.map((result, index) => {
23100
+ const beanId = beans[index]?.beanId;
23101
+ if (!beanId) {
23102
+ return { beanId: "unknown", error: "Unknown bean id" };
23082
23103
  }
23083
- }
23084
- return results;
23104
+ if (result.status === "fulfilled") {
23105
+ return result.value;
23106
+ }
23107
+ return {
23108
+ beanId,
23109
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason)
23110
+ };
23111
+ });
23085
23112
  }
23086
23113
  async openConfig() {
23087
23114
  const configPath = join(this.workspaceRoot, ".beans.yml");
@@ -23116,9 +23143,12 @@ Output: ${stdout.slice(0, 1e3)}`
23116
23143
  const outputPath = resolve2(
23117
23144
  process.env.BEANS_VSCODE_OUTPUT_LOG || join(this.workspaceRoot, ".vscode", "logs", "beans-output.log")
23118
23145
  );
23119
- const isWithinWorkspace = isPathWithinRoot(this.workspaceRoot, outputPath);
23146
+ const canonicalOutputPath = await realpath(outputPath).catch(() => outputPath);
23147
+ const canonicalWorkspaceRoot = await realpath(this.workspaceRoot).catch(() => resolve2(this.workspaceRoot));
23148
+ const isWithinWorkspace = isPathWithinRoot(canonicalWorkspaceRoot, canonicalOutputPath);
23120
23149
  const vscodeLogDir = process.env.BEANS_VSCODE_LOG_DIR || this.logDir ? resolve2(process.env.BEANS_VSCODE_LOG_DIR || this.logDir || "") : void 0;
23121
- const isWithinVscodeLogDir = vscodeLogDir ? isPathWithinRoot(vscodeLogDir, outputPath) : false;
23150
+ const canonicalVscodeLogDir = vscodeLogDir ? await realpath(vscodeLogDir).catch(() => resolve2(vscodeLogDir)) : void 0;
23151
+ const isWithinVscodeLogDir = canonicalVscodeLogDir ? isPathWithinRoot(canonicalVscodeLogDir, canonicalOutputPath) : false;
23122
23152
  if (!isWithinWorkspace && !isWithinVscodeLogDir) {
23123
23153
  throw new Error("Output log path must stay within the workspace or VS Code log directory");
23124
23154
  }
@@ -23197,6 +23227,105 @@ Output: ${stdout.slice(0, 1e3)}`
23197
23227
  escapeForYamlDoubleQuoted(value) {
23198
23228
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
23199
23229
  }
23230
+ shouldQuoteFrontmatterValue(value) {
23231
+ return !/^[A-Za-z0-9._-]+$/.test(value);
23232
+ }
23233
+ parseFrontmatterLine(line) {
23234
+ const separatorIndex = line.indexOf(":");
23235
+ if (separatorIndex <= 0) {
23236
+ return null;
23237
+ }
23238
+ const key = line.slice(0, separatorIndex).trim();
23239
+ if (key.length === 0) {
23240
+ return null;
23241
+ }
23242
+ for (const character of key) {
23243
+ const isAlphaNumericUnderscore = character >= "a" && character <= "z" || character >= "A" && character <= "Z" || character >= "0" && character <= "9" || character === "_";
23244
+ if (!isAlphaNumericUnderscore) {
23245
+ return null;
23246
+ }
23247
+ }
23248
+ const rawValue = line.slice(separatorIndex + 1).trimStart();
23249
+ return { key, rawValue };
23250
+ }
23251
+ buildFrontmatterIndex(frontmatterLines) {
23252
+ const indexByKey = /* @__PURE__ */ new Map();
23253
+ frontmatterLines.forEach((line, index) => {
23254
+ const parsed = this.parseFrontmatterLine(line);
23255
+ if (!parsed) {
23256
+ return;
23257
+ }
23258
+ indexByKey.set(parsed.key, index);
23259
+ });
23260
+ return indexByKey;
23261
+ }
23262
+ serializeFrontmatterValue(key, value) {
23263
+ if (Array.isArray(value)) {
23264
+ return JSON.stringify(value);
23265
+ }
23266
+ if (key === "title") {
23267
+ return this.normalizeFrontmatterTitleValue(value);
23268
+ }
23269
+ if (this.shouldQuoteFrontmatterValue(value)) {
23270
+ return `"${this.escapeForYamlDoubleQuoted(value)}"`;
23271
+ }
23272
+ return value;
23273
+ }
23274
+ deserializeFrontmatterValue(value) {
23275
+ const trimmed = value.trim();
23276
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
23277
+ try {
23278
+ const parsed = JSON.parse(trimmed);
23279
+ if (Array.isArray(parsed) && parsed.every((item) => typeof item === "string")) {
23280
+ return parsed;
23281
+ }
23282
+ } catch {
23283
+ }
23284
+ }
23285
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
23286
+ return trimmed.slice(1, -1).replaceAll('\\"', '"').replaceAll("\\\\", "\\").replaceAll("''", "'");
23287
+ }
23288
+ return trimmed;
23289
+ }
23290
+ splitFrontmatterDocument(content) {
23291
+ const crlfOpen = content.startsWith("---\r\n");
23292
+ const lfOpen = content.startsWith("---\n");
23293
+ const eol = crlfOpen ? "\r\n" : "\n";
23294
+ if (!crlfOpen && !lfOpen) {
23295
+ return { eol: "\n", hasFrontmatter: false, frontmatterLines: [], body: content };
23296
+ }
23297
+ const openEnd = `---${eol}`.length;
23298
+ const closeMarker = `${eol}---`;
23299
+ const closeIdx = content.indexOf(closeMarker, openEnd);
23300
+ if (closeIdx === -1) {
23301
+ return { eol, hasFrontmatter: false, frontmatterLines: [], body: content };
23302
+ }
23303
+ const frontmatter = content.slice(openEnd, closeIdx);
23304
+ const body = content.slice(closeIdx + closeMarker.length);
23305
+ return {
23306
+ eol,
23307
+ hasFrontmatter: true,
23308
+ frontmatterLines: frontmatter.length > 0 ? frontmatter.split(eol) : [],
23309
+ body
23310
+ };
23311
+ }
23312
+ parseFrontmatterFields(frontmatterLines) {
23313
+ const fields = {};
23314
+ for (const line of frontmatterLines) {
23315
+ const parsed = this.parseFrontmatterLine(line);
23316
+ if (!parsed) {
23317
+ continue;
23318
+ }
23319
+ const { valuePart } = this.splitYamlInlineComment(parsed.rawValue);
23320
+ fields[parsed.key] = this.deserializeFrontmatterValue(valuePart);
23321
+ }
23322
+ return fields;
23323
+ }
23324
+ async writeFileAtomically(absolutePath, content) {
23325
+ const tempPath = `${absolutePath}.tmp-${process.pid}-${Date.now()}`;
23326
+ await writeFile(tempPath, content, "utf8");
23327
+ await rename(tempPath, absolutePath);
23328
+ }
23200
23329
  /**
23201
23330
  * Normalise a raw YAML title value to a double-quoted scalar.
23202
23331
  * Handles: empty, already double-quoted, single-quoted (unescaping `''`),
@@ -23267,14 +23396,66 @@ Output: ${stdout.slice(0, 1e3)}`
23267
23396
  await writeFile(absolutePath, fixed, "utf8");
23268
23397
  return { path: absolutePath, bytes: Buffer.byteLength(fixed, "utf8") };
23269
23398
  }
23399
+ async updateBeanFrontmatter(relativePath, updates) {
23400
+ const absolutePath = this.resolveBeanFilePath(relativePath);
23401
+ const content = await readFile(absolutePath, "utf8");
23402
+ const { eol, hasFrontmatter, frontmatterLines, body } = this.splitFrontmatterDocument(content);
23403
+ const updatedFields = Object.entries(updates).filter(([, value]) => value !== void 0).map(([key]) => key);
23404
+ if (updatedFields.length === 0) {
23405
+ throw new Error("At least one frontmatter field update is required");
23406
+ }
23407
+ const nextLines = [...frontmatterLines];
23408
+ let indexByKey = this.buildFrontmatterIndex(nextLines);
23409
+ for (const [key, value] of Object.entries(updates)) {
23410
+ if (value === void 0) {
23411
+ continue;
23412
+ }
23413
+ const existingIndex = indexByKey.get(key);
23414
+ if (value === null) {
23415
+ if (existingIndex !== void 0) {
23416
+ nextLines.splice(existingIndex, 1);
23417
+ indexByKey = this.buildFrontmatterIndex(nextLines);
23418
+ }
23419
+ continue;
23420
+ }
23421
+ const serialized = `${key}: ${this.serializeFrontmatterValue(key, value)}`;
23422
+ if (existingIndex !== void 0) {
23423
+ const existingLine = nextLines[existingIndex] ?? "";
23424
+ const existingParsed = this.parseFrontmatterLine(existingLine);
23425
+ const commentPart = existingParsed ? this.splitYamlInlineComment(existingParsed.rawValue).commentPart : "";
23426
+ nextLines[existingIndex] = `${serialized}${commentPart}`;
23427
+ } else {
23428
+ nextLines.push(serialized);
23429
+ indexByKey.set(key, nextLines.length - 1);
23430
+ }
23431
+ }
23432
+ const frontmatterBlock = nextLines.length > 0 ? nextLines.join(eol) : "";
23433
+ const nextContent = hasFrontmatter ? `---${eol}${frontmatterBlock}${eol}---${body}` : `---${eol}${frontmatterBlock}${eol}---${eol}${body}`;
23434
+ const fixed = this.quoteFrontmatterTitles(nextContent);
23435
+ await this.writeFileAtomically(absolutePath, fixed);
23436
+ return {
23437
+ path: absolutePath,
23438
+ bytes: Buffer.byteLength(fixed, "utf8"),
23439
+ updatedFields,
23440
+ frontmatter: this.parseFrontmatterFields(this.splitFrontmatterDocument(fixed).frontmatterLines)
23441
+ };
23442
+ }
23270
23443
  async createBeanFile(relativePath, content, options) {
23271
23444
  const absolutePath = this.resolveBeanFilePath(relativePath);
23272
23445
  const fixed = this.quoteFrontmatterTitles(content);
23273
23446
  await mkdir(dirname(absolutePath), { recursive: true });
23274
- await writeFile(absolutePath, fixed, {
23275
- encoding: "utf8",
23276
- flag: options?.overwrite ? "w" : "wx"
23277
- });
23447
+ try {
23448
+ await writeFile(absolutePath, fixed, {
23449
+ encoding: "utf8",
23450
+ flag: options?.overwrite ? "w" : "wx"
23451
+ });
23452
+ } catch (error48) {
23453
+ const maybeNodeError = error48;
23454
+ if (maybeNodeError.code === "EEXIST" && !options?.overwrite) {
23455
+ throw new Error("Bean file already exists. Pass overwrite=true to replace it.");
23456
+ }
23457
+ throw error48;
23458
+ }
23278
23459
  return {
23279
23460
  path: absolutePath,
23280
23461
  bytes: Buffer.byteLength(fixed, "utf8"),
@@ -31365,14 +31546,16 @@ import { promisify as promisify2 } from "util";
31365
31546
  // package.json
31366
31547
  var package_default = {
31367
31548
  name: "@selfagency/beans-mcp",
31368
- version: "0.5.0",
31549
+ version: "0.6.1",
31369
31550
  private: false,
31370
31551
  description: "MCP (Model Context Protocol) server for Beans issue tracker",
31371
- author: {
31372
- name: "Daniel Sieradski",
31373
- email: "daniel@self.agency",
31374
- url: "https://self.agency"
31375
- },
31552
+ keywords: [
31553
+ "ai",
31554
+ "beans",
31555
+ "issue-tracker",
31556
+ "mcp",
31557
+ "model-context-protocol"
31558
+ ],
31376
31559
  homepage: "https://github.com/selfagency/beans-mcp",
31377
31560
  bugs: {
31378
31561
  url: "https://github.com/selfagency/beans-mcp/issues"
@@ -31381,15 +31564,12 @@ var package_default = {
31381
31564
  type: "git",
31382
31565
  url: "git+https://github.com/selfagency/beans-mcp.git"
31383
31566
  },
31384
- mcpName: "io.github.selfagency/beans-mcp",
31385
- keywords: [
31386
- "beans",
31387
- "mcp",
31388
- "model-context-protocol",
31389
- "issue-tracker",
31390
- "ai"
31391
- ],
31392
31567
  license: "MIT",
31568
+ author: {
31569
+ name: "Daniel Sieradski",
31570
+ email: "daniel@self.agency",
31571
+ url: "https://self.agency"
31572
+ },
31393
31573
  type: "module",
31394
31574
  exports: {
31395
31575
  ".": {
@@ -31404,46 +31584,57 @@ var package_default = {
31404
31584
  bin: {
31405
31585
  "beans-mcp": "dist/beans-mcp-server.cjs"
31406
31586
  },
31587
+ files: [
31588
+ "dist",
31589
+ "skills",
31590
+ "README.md",
31591
+ "LICENSE.txt"
31592
+ ],
31407
31593
  scripts: {
31408
31594
  build: "tsup",
31595
+ postbuild: "node ./scripts/write-dist-package.js",
31596
+ "docs:build": "vitepress build docs",
31597
+ "docs:dev": "vitepress dev docs",
31598
+ "docs:preview": "vitepress preview docs",
31409
31599
  format: "oxfmt",
31410
- "lint:fix": "oxlint --fix",
31411
31600
  lint: "oxlint",
31412
- postbuild: "node ./scripts/write-dist-package.js",
31601
+ "lint:fix": "oxlint --fix",
31413
31602
  prepare: "husky",
31414
31603
  release: "zx ./scripts/release.js",
31604
+ test: "vitest run",
31415
31605
  "test:coverage": "vitest run --coverage",
31416
31606
  "test:watch": "vitest",
31417
- test: "vitest run",
31418
31607
  "type-check": "tsc --noEmit"
31419
31608
  },
31609
+ "lint-staged": {
31610
+ "src/**/*.ts": [
31611
+ "pnpm run lint:fix",
31612
+ "pnpm run format"
31613
+ ]
31614
+ },
31420
31615
  devDependencies: {
31421
31616
  "@modelcontextprotocol/sdk": "^1.29.0",
31422
31617
  "@octokit/rest": "^22.0.1",
31423
- "@types/node": "25.5.2",
31424
- "@vitest/coverage-v8": "^4.1.2",
31425
- "@vitest/ui": "4.1.2",
31618
+ "@types/node": "25.6.0",
31619
+ "@vitest/coverage-v8": "^4.1.4",
31620
+ "@vitest/ui": "4.1.4",
31426
31621
  husky: "^9.1.7",
31427
31622
  "lint-staged": "^16.4.0",
31428
31623
  ora: "^9.3.0",
31429
- oxfmt: "^0.43.0",
31430
- oxlint: "^1.58.0",
31431
- "oxlint-tsgolint": "^0.20.0",
31624
+ oxfmt: "^0.45.0",
31625
+ oxlint: "^1.60.0",
31626
+ "oxlint-tsgolint": "^0.21.1",
31432
31627
  tsup: "8.5.1",
31433
- typescript: "6.0.2",
31434
- vitest: "4.1.2",
31628
+ typescript: "6.0.3",
31629
+ vitepress: "^1.6.4",
31630
+ vitest: "4.1.4",
31435
31631
  zod: "4.3.6",
31436
31632
  zx: "^8.8.5"
31437
31633
  },
31438
31634
  engines: {
31439
31635
  node: ">=18"
31440
31636
  },
31441
- "lint-staged": {
31442
- "src/**/*.ts": [
31443
- "pnpm run lint:fix",
31444
- "pnpm run format"
31445
- ]
31446
- }
31637
+ mcpName: "io.github.selfagency/beans-mcp"
31447
31638
  };
31448
31639
 
31449
31640
  // src/internal/queryHelpers.ts
@@ -31608,6 +31799,8 @@ var MAX_PATH_LENGTH = 1024;
31608
31799
  init_utils();
31609
31800
  var execFileAsync2 = promisify2(execFile2);
31610
31801
  var PACKAGE_VERSION = package_default.version ?? "0.0.0-dev";
31802
+ var CLOSED_STATUSES = /* @__PURE__ */ new Set(["completed", "scrapped"]);
31803
+ var BEAN_ID_HINT = "Missing required field `beanId`. Did you mean `beanId`?";
31611
31804
  function getSafeCliEnv(env) {
31612
31805
  const whitelist = ["PATH", "HOME", "USER", "LANG", "LC_ALL", "LC_CTYPE", "SHELL"];
31613
31806
  const safeEnv = {};
@@ -31628,7 +31821,8 @@ function extractVersionFromOutput(output) {
31628
31821
  if (!trimmed) {
31629
31822
  return null;
31630
31823
  }
31631
- const match = trimmed.match(/(?:^|[^\d])v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/);
31824
+ const versionRegex = /(?:^|[^\d])v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/;
31825
+ const match = versionRegex.exec(trimmed);
31632
31826
  return match?.[1] ?? null;
31633
31827
  }
31634
31828
  async function detectBeansCliVersion(cliPath, workspaceRoot) {
@@ -31645,10 +31839,10 @@ ${stderr}`);
31645
31839
  return null;
31646
31840
  }
31647
31841
  }
31648
- async function getBeanById(backend, beanId) {
31842
+ async function getBeanById(backend, beanId, beans) {
31649
31843
  try {
31650
- const beans = await backend.list();
31651
- const found = beans.find((b) => b.id === beanId);
31844
+ const allBeans = beans ?? await backend.list();
31845
+ const found = allBeans.find((b) => b.id === beanId);
31652
31846
  if (!found) {
31653
31847
  throw new Error(`Bean not found: ${beanId}`);
31654
31848
  }
@@ -31657,12 +31851,111 @@ async function getBeanById(backend, beanId) {
31657
31851
  throw new Error(`Failed to fetch bean ${beanId}: ${error48.message}`);
31658
31852
  }
31659
31853
  }
31854
+ function collectDescendantBeans(beans, rootBeanId) {
31855
+ const byParent = /* @__PURE__ */ new Map();
31856
+ const byId = new Map(beans.map((bean) => [bean.id, bean]));
31857
+ for (const bean of beans) {
31858
+ if (!bean.parentId) {
31859
+ continue;
31860
+ }
31861
+ const children = byParent.get(bean.parentId) ?? [];
31862
+ children.push(bean.id);
31863
+ byParent.set(bean.parentId, children);
31864
+ }
31865
+ const queue = [...byParent.get(rootBeanId) ?? []];
31866
+ const visited = /* @__PURE__ */ new Set();
31867
+ const descendants = [];
31868
+ while (queue.length > 0) {
31869
+ const currentId = queue.shift();
31870
+ if (!currentId || visited.has(currentId)) {
31871
+ continue;
31872
+ }
31873
+ visited.add(currentId);
31874
+ const currentBean = byId.get(currentId);
31875
+ if (!currentBean) {
31876
+ continue;
31877
+ }
31878
+ descendants.push(currentBean);
31879
+ const children = byParent.get(currentId);
31880
+ if (children && children.length > 0) {
31881
+ queue.push(...children);
31882
+ }
31883
+ }
31884
+ return descendants;
31885
+ }
31886
+ async function cascadeStatusToDescendants(backend, rootBeanId, targetStatus, options) {
31887
+ const beans = options?.beans ?? await backend.list();
31888
+ const descendants = collectDescendantBeans(beans, rootBeanId);
31889
+ const updatedBeanIds = [];
31890
+ const skippedBeanIds = [];
31891
+ const errors = [];
31892
+ const toUpdate = [];
31893
+ for (const bean of descendants) {
31894
+ if (options?.onlyCurrentStatuses && !options.onlyCurrentStatuses.has(bean.status)) {
31895
+ skippedBeanIds.push(bean.id);
31896
+ continue;
31897
+ }
31898
+ toUpdate.push(bean);
31899
+ }
31900
+ const settled = await Promise.allSettled(
31901
+ toUpdate.map(async (bean) => backend.update(bean.id, { status: targetStatus }))
31902
+ );
31903
+ settled.forEach((result, index) => {
31904
+ const bean = toUpdate[index];
31905
+ if (!bean) {
31906
+ return;
31907
+ }
31908
+ if (result.status === "fulfilled") {
31909
+ updatedBeanIds.push(bean.id);
31910
+ return;
31911
+ }
31912
+ errors.push({
31913
+ beanId: bean.id,
31914
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason)
31915
+ });
31916
+ });
31917
+ return {
31918
+ totalDescendants: descendants.length,
31919
+ updatedBeanIds,
31920
+ skippedBeanIds,
31921
+ errors
31922
+ };
31923
+ }
31924
+ function completeMarkdownTasks(body) {
31925
+ const lines = body.split(/\r?\n/);
31926
+ let totalTaskCount = 0;
31927
+ let updatedTaskCount = 0;
31928
+ const taskLinePattern = /^\s*(?:[-*+]|\d+\.)\s+\[[ xX]\]/;
31929
+ const uncheckedTaskLinePattern = /^(\s*(?:[-*+]|\d+\.)\s+\[)\s(\].*)$/;
31930
+ const nextLines = lines.map((line) => {
31931
+ if (!taskLinePattern.test(line)) {
31932
+ return line;
31933
+ }
31934
+ totalTaskCount += 1;
31935
+ const uncheckedMatch = uncheckedTaskLinePattern.exec(line);
31936
+ if (!uncheckedMatch) {
31937
+ return line;
31938
+ }
31939
+ updatedTaskCount += 1;
31940
+ return `${uncheckedMatch[1]}x${uncheckedMatch[2]}`;
31941
+ });
31942
+ const nextBody = nextLines.join("\n");
31943
+ return { nextBody, totalTaskCount, updatedTaskCount };
31944
+ }
31660
31945
  function initHandler(backend) {
31661
31946
  return async ({ prefix }) => {
31662
31947
  const result = await backend.init(prefix);
31663
31948
  return makeTextAndStructured(result);
31664
31949
  };
31665
31950
  }
31951
+ function archiveHandler(backend) {
31952
+ return async () => {
31953
+ if (typeof backend.archive !== "function") {
31954
+ throw new TypeError("Archive is not supported by the current backend");
31955
+ }
31956
+ return makeTextAndStructured(await backend.archive());
31957
+ };
31958
+ }
31666
31959
  function viewHandler(backend) {
31667
31960
  return async ({ beanId, beanIds }) => {
31668
31961
  const ids = Array.isArray(beanIds) && beanIds.length > 0 ? beanIds : beanId ? [beanId] : [];
@@ -31700,7 +31993,14 @@ async function checkVersionCompatibility(cliPath, workspaceRoot, detector) {
31700
31993
  }
31701
31994
  }
31702
31995
  function createHandler(backend) {
31703
- return async (input) => makeTextAndStructured({ bean: await backend.create(input) });
31996
+ return async (input) => {
31997
+ const bean = await backend.create(input);
31998
+ const warnings = input.description !== void 0 ? ["`description` is deprecated; use `body` instead."] : void 0;
31999
+ return makeTextAndStructured({
32000
+ bean,
32001
+ ...warnings ? { warnings } : {}
32002
+ });
32003
+ };
31704
32004
  }
31705
32005
  function editHandler(backend) {
31706
32006
  return async ({
@@ -31714,18 +32014,30 @@ function reopenHandler(backend) {
31714
32014
  requiredCurrentStatus,
31715
32015
  targetStatus
31716
32016
  }) => {
31717
- const bean = await getBeanById(backend, beanId);
31718
- if (bean.status !== requiredCurrentStatus) {
31719
- throw new Error(`Bean ${beanId} is not ${requiredCurrentStatus}`);
32017
+ const beans = await backend.list();
32018
+ const bean = await getBeanById(backend, beanId, beans);
32019
+ if (bean.status === requiredCurrentStatus) {
32020
+ const updatedParentBean = await backend.update(beanId, { status: targetStatus });
32021
+ const cascade = await cascadeStatusToDescendants(backend, beanId, targetStatus, {
32022
+ onlyCurrentStatuses: CLOSED_STATUSES,
32023
+ beans
32024
+ });
32025
+ return makeTextAndStructured({
32026
+ bean: updatedParentBean,
32027
+ cascade: {
32028
+ totalDescendants: cascade.totalDescendants,
32029
+ updatedBeanIds: cascade.updatedBeanIds,
32030
+ skippedBeanIds: cascade.skippedBeanIds,
32031
+ errors: cascade.errors
32032
+ }
32033
+ });
31720
32034
  }
31721
- return makeTextAndStructured({
31722
- bean: await backend.update(beanId, { status: targetStatus })
31723
- });
32035
+ throw new Error(`Bean ${beanId} is not ${requiredCurrentStatus}`);
31724
32036
  };
31725
32037
  }
31726
32038
  function updateHandler(backend) {
31727
- return async (input) => makeTextAndStructured({
31728
- bean: await backend.update(input.beanId, {
32039
+ return async (input) => {
32040
+ const updatedBean = await backend.update(input.beanId, {
31729
32041
  status: input.status,
31730
32042
  type: input.type,
31731
32043
  priority: input.priority,
@@ -31737,8 +32049,37 @@ function updateHandler(backend) {
31737
32049
  bodyAppend: input.bodyAppend,
31738
32050
  bodyReplace: input.bodyReplace,
31739
32051
  ifMatch: input.ifMatch
31740
- })
31741
- });
32052
+ });
32053
+ const closeStatus = input.status;
32054
+ const shouldCascadeClose = Boolean(closeStatus && CLOSED_STATUSES.has(closeStatus));
32055
+ const cascade = shouldCascadeClose ? await cascadeStatusToDescendants(backend, input.beanId, closeStatus, {
32056
+ beans: await backend.list()
32057
+ }) : null;
32058
+ return makeTextAndStructured({
32059
+ bean: updatedBean,
32060
+ ...cascade ? {
32061
+ cascade: {
32062
+ totalDescendants: cascade.totalDescendants,
32063
+ updatedBeanIds: cascade.updatedBeanIds,
32064
+ skippedBeanIds: cascade.skippedBeanIds,
32065
+ errors: cascade.errors
32066
+ }
32067
+ } : {}
32068
+ });
32069
+ };
32070
+ }
32071
+ function completeTasksHandler(backend) {
32072
+ return async ({ beanId }) => {
32073
+ const bean = await getBeanById(backend, beanId);
32074
+ const { nextBody, totalTaskCount, updatedTaskCount } = completeMarkdownTasks(bean.body || "");
32075
+ const updatedBean = updatedTaskCount > 0 ? await backend.update(beanId, { body: nextBody }) : bean;
32076
+ return makeTextAndStructured({
32077
+ bean: updatedBean,
32078
+ totalTaskCount,
32079
+ updatedTaskCount,
32080
+ unchangedTaskCount: totalTaskCount - updatedTaskCount
32081
+ });
32082
+ };
31742
32083
  }
31743
32084
  function deleteHandler(backend) {
31744
32085
  return async ({ beanId, beanIds, force }) => {
@@ -31792,11 +32133,17 @@ function deleteHandler(backend) {
31792
32133
  function bulkCreateHandler(backend) {
31793
32134
  return async (input) => {
31794
32135
  const results = await backend.bulkCreate(input.beans, input.parent);
32136
+ const deprecatedDescriptionCount = input.beans.filter((bean) => bean.description !== void 0).length;
31795
32137
  return makeTextAndStructured({
31796
32138
  results,
31797
32139
  requestedCount: input.beans.length,
31798
32140
  successCount: results.filter((r) => r.bean).length,
31799
- failedCount: results.filter((r) => r.error).length
32141
+ failedCount: results.filter((r) => r.error).length,
32142
+ ...deprecatedDescriptionCount > 0 ? {
32143
+ warnings: [
32144
+ `Found ${deprecatedDescriptionCount} bean(s) using deprecated field \`description\`; use \`body\` instead.`
32145
+ ]
32146
+ } : {}
31800
32147
  });
31801
32148
  };
31802
32149
  }
@@ -31812,14 +32159,24 @@ function bulkUpdateHandler(backend) {
31812
32159
  };
31813
32160
  }
31814
32161
  function queryHandler(backend) {
31815
- return async (opts) => handleQueryOperation(backend, opts);
32162
+ return async (opts) => {
32163
+ if (opts.operation === "graphql") {
32164
+ if (typeof backend.queryGraphql !== "function") {
32165
+ throw new TypeError("GraphQL passthrough is not supported by the current backend");
32166
+ }
32167
+ const result = await backend.queryGraphql(opts.graphql || "", opts.variables);
32168
+ return makeTextAndStructured({ data: result.data, errors: result.errors ?? [] });
32169
+ }
32170
+ return handleQueryOperation(backend, opts);
32171
+ };
31816
32172
  }
31817
32173
  function beanFileHandler(backend) {
31818
32174
  return async ({
31819
32175
  operation,
31820
32176
  path,
31821
32177
  content,
31822
- overwrite
32178
+ overwrite,
32179
+ fields
31823
32180
  }) => {
31824
32181
  if (operation === "read") {
31825
32182
  return makeTextAndStructured(await backend.readBeanFile(path));
@@ -31830,6 +32187,9 @@ function beanFileHandler(backend) {
31830
32187
  if (operation === "create") {
31831
32188
  return makeTextAndStructured(await backend.createBeanFile(path, content || "", { overwrite }));
31832
32189
  }
32190
+ if (operation === "update_frontmatter") {
32191
+ return makeTextAndStructured(await backend.updateBeanFrontmatter(path, fields || {}));
32192
+ }
31833
32193
  if (operation === "delete") {
31834
32194
  return makeTextAndStructured(await backend.deleteBeanFile(path));
31835
32195
  }
@@ -31864,6 +32224,21 @@ function registerTools(server, backend) {
31864
32224
  },
31865
32225
  initHandler(backend)
31866
32226
  );
32227
+ server.registerTool(
32228
+ "beans_archive",
32229
+ {
32230
+ title: "Archive Beans",
32231
+ description: "Archive completed or scrapped beans, equivalent to the beans CLI archive command.",
32232
+ inputSchema: external_exports3.object({}),
32233
+ annotations: {
32234
+ readOnlyHint: false,
32235
+ destructiveHint: false,
32236
+ idempotentHint: false,
32237
+ openWorldHint: false
32238
+ }
32239
+ },
32240
+ archiveHandler(backend)
32241
+ );
31867
32242
  server.registerTool(
31868
32243
  "beans_view",
31869
32244
  {
@@ -31873,7 +32248,7 @@ function registerTools(server, backend) {
31873
32248
  beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
31874
32249
  beanIds: external_exports3.array(external_exports3.string().min(1).max(MAX_ID_LENGTH)).optional()
31875
32250
  }).refine((input) => Boolean(input.beanId) || Array.isArray(input.beanIds) && input.beanIds.length > 0, {
31876
- message: "Either beanId or beanIds must be provided"
32251
+ message: `Either beanId or beanIds must be provided. ${BEAN_ID_HINT}`
31877
32252
  }),
31878
32253
  annotations: {
31879
32254
  readOnlyHint: true,
@@ -31913,7 +32288,7 @@ function registerTools(server, backend) {
31913
32288
  title: "Edit Bean Metadata",
31914
32289
  description: "Update bean metadata fields (status/type/priority/parent/blocking).",
31915
32290
  inputSchema: external_exports3.object({
31916
- beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH),
32291
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
31917
32292
  status: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
31918
32293
  type: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
31919
32294
  priority: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
@@ -31921,7 +32296,11 @@ function registerTools(server, backend) {
31921
32296
  clearParent: external_exports3.boolean().optional(),
31922
32297
  blocking: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
31923
32298
  blockedBy: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional()
31924
- }),
32299
+ }).superRefine((input, ctx) => {
32300
+ if (!input.beanId) {
32301
+ ctx.addIssue({ code: external_exports3.ZodIssueCode.custom, path: ["beanId"], message: BEAN_ID_HINT });
32302
+ }
32303
+ }).transform((input) => ({ ...input, beanId: input.beanId })),
31925
32304
  annotations: {
31926
32305
  readOnlyHint: false,
31927
32306
  destructiveHint: false,
@@ -31937,10 +32316,14 @@ function registerTools(server, backend) {
31937
32316
  title: "Reopen Bean",
31938
32317
  description: "Reopen a completed or scrapped bean into a non-closed status.",
31939
32318
  inputSchema: external_exports3.object({
31940
- beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH),
32319
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
31941
32320
  requiredCurrentStatus: external_exports3.enum(["completed", "scrapped"]),
31942
32321
  targetStatus: external_exports3.string().max(MAX_METADATA_LENGTH).default("todo")
31943
- }),
32322
+ }).superRefine((input, ctx) => {
32323
+ if (!input.beanId) {
32324
+ ctx.addIssue({ code: external_exports3.ZodIssueCode.custom, path: ["beanId"], message: BEAN_ID_HINT });
32325
+ }
32326
+ }).transform((input) => ({ ...input, beanId: input.beanId })),
31944
32327
  annotations: {
31945
32328
  readOnlyHint: false,
31946
32329
  destructiveHint: false,
@@ -31956,7 +32339,7 @@ function registerTools(server, backend) {
31956
32339
  title: "Update Bean",
31957
32340
  description: "Update bean metadata fields (status/type/priority/parent/blocking). Consolidated replacement for per-field update tools.",
31958
32341
  inputSchema: external_exports3.object({
31959
- beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH),
32342
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
31960
32343
  status: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
31961
32344
  type: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
31962
32345
  priority: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
@@ -31973,12 +32356,16 @@ function registerTools(server, backend) {
31973
32356
  })
31974
32357
  ).optional(),
31975
32358
  ifMatch: external_exports3.string().max(MAX_METADATA_LENGTH).optional()
32359
+ }).superRefine((input, ctx) => {
32360
+ if (!input.beanId) {
32361
+ ctx.addIssue({ code: external_exports3.ZodIssueCode.custom, path: ["beanId"], message: BEAN_ID_HINT });
32362
+ }
31976
32363
  }).refine(
31977
32364
  (input) => !(input.body !== void 0 && (input.bodyAppend !== void 0 || input.bodyReplace !== void 0)),
31978
32365
  {
31979
32366
  message: "body cannot be combined with bodyAppend/bodyReplace"
31980
32367
  }
31981
- ),
32368
+ ).transform((input) => ({ ...input, beanId: input.beanId })),
31982
32369
  annotations: {
31983
32370
  readOnlyHint: false,
31984
32371
  destructiveHint: false,
@@ -31998,7 +32385,7 @@ function registerTools(server, backend) {
31998
32385
  beanIds: external_exports3.array(external_exports3.string().min(1).max(MAX_ID_LENGTH)).optional(),
31999
32386
  force: external_exports3.boolean().default(false)
32000
32387
  }).refine((input) => Boolean(input.beanId) || Array.isArray(input.beanIds) && input.beanIds.length > 0, {
32001
- message: "Either beanId or beanIds must be provided"
32388
+ message: `Either beanId or beanIds must be provided. ${BEAN_ID_HINT}`
32002
32389
  }),
32003
32390
  annotations: {
32004
32391
  readOnlyHint: false,
@@ -32037,7 +32424,7 @@ function registerTools(server, backend) {
32037
32424
  bulkCreateHandler(backend)
32038
32425
  );
32039
32426
  const beanUpdateItemSchema = external_exports3.object({
32040
- beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH),
32427
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
32041
32428
  status: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32042
32429
  type: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32043
32430
  priority: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
@@ -32049,10 +32436,14 @@ function registerTools(server, backend) {
32049
32436
  bodyAppend: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
32050
32437
  bodyReplace: external_exports3.array(external_exports3.object({ old: external_exports3.string().max(MAX_DESCRIPTION_LENGTH), new: external_exports3.string().max(MAX_DESCRIPTION_LENGTH) })).optional(),
32051
32438
  ifMatch: external_exports3.string().max(MAX_METADATA_LENGTH).optional()
32439
+ }).superRefine((input, ctx) => {
32440
+ if (!input.beanId) {
32441
+ ctx.addIssue({ code: external_exports3.ZodIssueCode.custom, path: ["beanId"], message: BEAN_ID_HINT });
32442
+ }
32052
32443
  }).refine(
32053
32444
  (input) => !(input.body !== void 0 && (input.bodyAppend !== void 0 || input.bodyReplace !== void 0)),
32054
32445
  { message: "body cannot be combined with bodyAppend/bodyReplace" }
32055
- );
32446
+ ).transform((input) => ({ ...input, beanId: input.beanId }));
32056
32447
  server.registerTool(
32057
32448
  "beans_bulk_update",
32058
32449
  {
@@ -32071,25 +32462,56 @@ function registerTools(server, backend) {
32071
32462
  },
32072
32463
  bulkUpdateHandler(backend)
32073
32464
  );
32465
+ server.registerTool(
32466
+ "beans_complete_tasks",
32467
+ {
32468
+ title: "Complete Markdown Tasks",
32469
+ description: "Mark all markdown checklist tasks within a bean as completed.",
32470
+ inputSchema: external_exports3.object({
32471
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional()
32472
+ }).superRefine((input, ctx) => {
32473
+ if (!input.beanId) {
32474
+ ctx.addIssue({ code: external_exports3.ZodIssueCode.custom, path: ["beanId"], message: BEAN_ID_HINT });
32475
+ }
32476
+ }).transform((input) => ({ ...input, beanId: input.beanId })),
32477
+ annotations: {
32478
+ readOnlyHint: false,
32479
+ destructiveHint: false,
32480
+ idempotentHint: true,
32481
+ openWorldHint: false
32482
+ }
32483
+ },
32484
+ completeTasksHandler(backend)
32485
+ );
32074
32486
  server.registerTool(
32075
32487
  "beans_query",
32076
32488
  {
32077
32489
  title: "Query Beans",
32078
32490
  description: "Unified query tool for refresh, filter, search, and sort operations.",
32079
32491
  inputSchema: external_exports3.object({
32080
- operation: external_exports3.enum(["refresh", "filter", "search", "sort", "ready", "llm_context", "open_config"]).default("refresh"),
32492
+ operation: external_exports3.enum(["refresh", "filter", "search", "sort", "ready", "llm_context", "open_config", "graphql"]).default("refresh"),
32081
32493
  mode: external_exports3.enum(["status-priority-type-title", "updated", "created", "id"]).optional(),
32082
32494
  statuses: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
32083
32495
  types: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
32084
32496
  search: external_exports3.string().max(MAX_TITLE_LENGTH).optional(),
32085
32497
  includeClosed: external_exports3.boolean().optional(),
32086
32498
  tags: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
32499
+ graphql: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
32500
+ variables: external_exports3.record(external_exports3.string(), external_exports3.unknown()).optional(),
32087
32501
  writeToWorkspaceInstructions: external_exports3.boolean().optional()
32502
+ }).superRefine((input, ctx) => {
32503
+ if (input.operation === "graphql" && (!input.graphql || input.graphql.trim().length === 0)) {
32504
+ ctx.addIssue({
32505
+ code: external_exports3.ZodIssueCode.custom,
32506
+ path: ["graphql"],
32507
+ message: "graphql query string is required when operation is graphql"
32508
+ });
32509
+ }
32088
32510
  }),
32089
32511
  annotations: {
32090
- readOnlyHint: true,
32512
+ readOnlyHint: false,
32091
32513
  destructiveHint: false,
32092
- idempotentHint: true,
32514
+ idempotentHint: false,
32093
32515
  openWorldHint: false
32094
32516
  }
32095
32517
  },
@@ -32101,10 +32523,33 @@ function registerTools(server, backend) {
32101
32523
  title: "Bean File Operations",
32102
32524
  description: "Read, create, edit, or delete files under .beans (operation param).",
32103
32525
  inputSchema: external_exports3.object({
32104
- operation: external_exports3.enum(["read", "edit", "create", "delete"]),
32526
+ operation: external_exports3.enum(["read", "edit", "create", "delete", "update_frontmatter"]),
32105
32527
  path: external_exports3.string().min(1).max(MAX_PATH_LENGTH),
32106
32528
  content: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
32107
- overwrite: external_exports3.boolean().optional()
32529
+ overwrite: external_exports3.boolean().optional(),
32530
+ fields: external_exports3.object({
32531
+ title: external_exports3.string().max(MAX_TITLE_LENGTH).optional(),
32532
+ status: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32533
+ type: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32534
+ priority: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32535
+ parent_id: external_exports3.string().max(MAX_ID_LENGTH).nullable().optional(),
32536
+ tags: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
32537
+ blocking_ids: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).nullable().optional(),
32538
+ blocked_by_ids: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).nullable().optional(),
32539
+ pr: external_exports3.string().max(MAX_TITLE_LENGTH).nullable().optional(),
32540
+ branch: external_exports3.string().max(MAX_TITLE_LENGTH).nullable().optional()
32541
+ }).optional()
32542
+ }).superRefine((input, ctx) => {
32543
+ if (input.operation === "update_frontmatter") {
32544
+ const fieldCount = Object.values(input.fields || {}).filter((value) => value !== void 0).length;
32545
+ if (fieldCount === 0) {
32546
+ ctx.addIssue({
32547
+ code: external_exports3.ZodIssueCode.custom,
32548
+ path: ["fields"],
32549
+ message: "At least one frontmatter field update is required"
32550
+ });
32551
+ }
32552
+ }
32108
32553
  }),
32109
32554
  annotations: {
32110
32555
  readOnlyHint: false,
@@ -32144,6 +32589,18 @@ var MutableBackend = class {
32144
32589
  init(prefix) {
32145
32590
  return this.inner.init(prefix);
32146
32591
  }
32592
+ archive() {
32593
+ if (typeof this.inner.archive === "function") {
32594
+ return this.inner.archive();
32595
+ }
32596
+ throw new TypeError("Archive is not supported by backend");
32597
+ }
32598
+ queryGraphql(query, variables) {
32599
+ if (typeof this.inner.queryGraphql === "function") {
32600
+ return this.inner.queryGraphql(query, variables);
32601
+ }
32602
+ throw new TypeError("GraphQL passthrough is not supported by backend");
32603
+ }
32147
32604
  list(opts) {
32148
32605
  return this.inner.list(opts);
32149
32606
  }
@@ -32183,6 +32640,9 @@ var MutableBackend = class {
32183
32640
  editBeanFile(path, content) {
32184
32641
  return this.inner.editBeanFile(path, content);
32185
32642
  }
32643
+ updateBeanFrontmatter(path, updates) {
32644
+ return this.inner.updateBeanFrontmatter(path, updates);
32645
+ }
32186
32646
  createBeanFile(path, content, opts) {
32187
32647
  return this.inner.createBeanFile(path, content, opts);
32188
32648
  }
@@ -32249,6 +32709,16 @@ function parseCliArgs(argv) {
32249
32709
  const envPort = Number.parseInt(process.env.BEANS_VSCODE_MCP_PORT || process.env.BEANS_MCP_PORT || "", 10);
32250
32710
  let port = Number.isInteger(envPort) && envPort > 0 ? envPort : DEFAULT_MCP_PORT;
32251
32711
  let logDir;
32712
+ const parseStrictPositiveInt = (raw, flagName) => {
32713
+ if (!/^\d+$/.test(raw)) {
32714
+ throw new Error(`Invalid value for ${flagName}: ${raw}`);
32715
+ }
32716
+ const parsed = Number.parseInt(raw, 10);
32717
+ if (!Number.isInteger(parsed) || parsed <= 0) {
32718
+ throw new Error(`Invalid value for ${flagName}: ${raw}`);
32719
+ }
32720
+ return parsed;
32721
+ };
32252
32722
  for (let i = 0; i < argv.length; i += 1) {
32253
32723
  const arg = argv[i];
32254
32724
  if ((arg === "--workspace" || arg === "--workspace-root") && argv[i + 1]) {
@@ -32262,10 +32732,7 @@ function parseCliArgs(argv) {
32262
32732
  }
32263
32733
  i += 1;
32264
32734
  } else if (arg === "--port" && argv[i + 1]) {
32265
- const parsedPort = Number.parseInt(argv[i + 1], 10);
32266
- if (Number.isInteger(parsedPort) && parsedPort > 0) {
32267
- port = parsedPort;
32268
- }
32735
+ port = parseStrictPositiveInt(argv[i + 1], "--port");
32269
32736
  i += 1;
32270
32737
  } else if (arg === "--log-dir" && argv[i + 1]) {
32271
32738
  logDir = argv[i + 1];
@@ -32328,6 +32795,7 @@ export {
32328
32795
  DEFAULT_MCP_PORT,
32329
32796
  MAX_ID_LENGTH,
32330
32797
  MAX_METADATA_LENGTH,
32798
+ MAX_PATH_LENGTH,
32331
32799
  MAX_TITLE_LENGTH,
32332
32800
  createBeansMcpServer,
32333
32801
  isPathWithinRoot,