brains-mcp 1.71.2 → 1.72.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"httpServer.d.ts","sourceRoot":"","sources":["../src/httpServer.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAQxB,OAAO,EAAsD,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AA+G7G,wBAAgB,8BAA8B,IAAI,IAAI,CAErD;AA04BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAI9D;AAgsBD,wBAAsB,eAAe,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CA+nF9F"}
1
+ {"version":3,"file":"httpServer.d.ts","sourceRoot":"","sources":["../src/httpServer.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAQxB,OAAO,EAAsD,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAkH7G,wBAAgB,8BAA8B,IAAI,IAAI,CAErD;AAi6BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAI9D;AAgsBD,wBAAsB,eAAe,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CA+oF9F"}
@@ -24,7 +24,7 @@ import { parsePage } from "./wiki/frontmatter.js";
24
24
  import { pullSessionBundle } from "./tools/pullSessionBundle.js";
25
25
  import { pushSessionBundle } from "./tools/pushSessionBundle.js";
26
26
  import { buildExportZip } from "./tools/exportWiki.js";
27
- import { runImportFromBuffer, isValidWikiFilename, computeDiscoveredFolders, buildInstructionRefreshHint, } from "./tools/importWikiZip.js";
27
+ import { runImportFromBuffer, isValidWikiFilename, computeDiscoveredFolders, buildInstructionRefreshHint, deriveReplaceScope, readRecordedFolder, isInFolderScope, } from "./tools/importWikiZip.js";
28
28
  import { purgeWiki } from "./tools/purgeWiki.js";
29
29
  import { AsyncJobRegistry } from "./jobs/asyncJobRegistry.js";
30
30
  import { exportDownloadStore } from "./jobs/exportDownloadStore.js";
@@ -498,6 +498,17 @@ async function readBody(req, maxBytes = MAX_BODY_BYTES) {
498
498
  async function scanImportDiffFromBuffer(drive, buffer, opts) {
499
499
  const zip = new AdmZip(buffer);
500
500
  const entries = zip.getEntries().filter((e) => !e.isDirectory);
501
+ // GH #695 phase 4 / GH #738: same scope derivation and whole-wiki refusal as
502
+ // the import_wiki tool — this upload route must not be a way to bypass it.
503
+ let folderScope;
504
+ if (opts.mode === "replace") {
505
+ const recordedFolder = readRecordedFolder(entries);
506
+ const scope = deriveReplaceScope(recordedFolder, opts.folder, opts.confirmWholeWiki ?? false);
507
+ if (!scope.ok) {
508
+ throw new ImportScopeRefusedError(scope.error);
509
+ }
510
+ folderScope = scope.folderScope;
511
+ }
501
512
  const diff = [];
502
513
  let newCount = 0;
503
514
  let modifiedCount = 0;
@@ -563,13 +574,18 @@ async function scanImportDiffFromBuffer(drive, buffer, opts) {
563
574
  if (!opts.includeSystem && systemFiles.has(name)) {
564
575
  continue;
565
576
  }
577
+ if (!isInFolderScope(name, folderScope))
578
+ continue;
566
579
  if (!zipNames.has(name)) {
567
580
  diff.push({ name, status: "deleted" });
568
581
  deletedCount++;
569
582
  }
570
583
  }
571
584
  }
572
- return { totalEntries: entries.length, newCount, modifiedCount, unchangedCount, skippedCount, deletedCount, diff };
585
+ return { totalEntries: entries.length, newCount, modifiedCount, unchangedCount, skippedCount, deletedCount, diff, folderScope };
586
+ }
587
+ /** Thrown by scanImportDiffFromBuffer when an unscoped replace is refused (GH #695 phase 4). */
588
+ class ImportScopeRefusedError extends Error {
573
589
  }
574
590
  async function readJsonBody(req) {
575
591
  const raw = (await readBody(req)).trim();
@@ -670,7 +686,10 @@ const exportWikiHttpSchema = z.object({
670
686
  const importWikiZipHttpSchema = z.object({
671
687
  zipPath: z.string().min(1),
672
688
  execute: z.boolean().optional(),
689
+ mode: z.enum(["additive", "replace"]).optional(),
673
690
  conflictStrategy: z.enum(["skip", "overwrite", "merge"]).optional(),
691
+ folder: z.string().optional(),
692
+ confirmWholeWiki: z.boolean().optional(),
674
693
  includeSystem: z.boolean().optional(),
675
694
  logTiming: z.boolean().optional(),
676
695
  });
@@ -3386,6 +3405,17 @@ export async function startHttpServer(drive, port) {
3386
3405
  return;
3387
3406
  }
3388
3407
  const effectiveConflictStrategy = mode === "replace" ? "overwrite" : conflictStrategy;
3408
+ // GH #695 phase 4 / GH #738: `folder` scopes mode=replace deletion the same
3409
+ // way the import_wiki tool does. Explicit query param wins; otherwise the
3410
+ // scan derives it from the uploaded archive's own export-index.json — this
3411
+ // is how a manifest-driven push supplies scope without a hand-typed flag.
3412
+ const folder = parsedUrl.searchParams.get("folder")?.trim() || undefined;
3413
+ const confirmWholeWikiRaw = parsedUrl.searchParams.get("confirmWholeWiki");
3414
+ const confirmWholeWiki = confirmWholeWikiRaw ? confirmWholeWikiRaw.toLowerCase() === "true" : false;
3415
+ if (confirmWholeWikiRaw && confirmWholeWikiRaw.toLowerCase() !== "true" && confirmWholeWikiRaw.toLowerCase() !== "false") {
3416
+ json(req, res, 400, { ok: false, error: "Invalid confirmWholeWiki query param; expected true or false" });
3417
+ return;
3418
+ }
3389
3419
  const zipBuffer = await readBodyBuffer(req, MAX_IMPORT_ZIP_UPLOAD_BYTES);
3390
3420
  const startedAt = new Date().toISOString();
3391
3421
  const startMs = Date.now();
@@ -3406,6 +3436,8 @@ export async function startHttpServer(drive, port) {
3406
3436
  conflictStrategy: effectiveConflictStrategy,
3407
3437
  includeSystem,
3408
3438
  mode: mode,
3439
+ folder,
3440
+ confirmWholeWiki,
3409
3441
  onProgress: (processed, total) => {
3410
3442
  asyncJobRegistry.update(job.id, { progress: { processed, total, phase: "scanning" } });
3411
3443
  },
@@ -3458,6 +3490,8 @@ export async function startHttpServer(drive, port) {
3458
3490
  conflictStrategy: effectiveConflictStrategy,
3459
3491
  includeSystem,
3460
3492
  mode: mode,
3493
+ folder,
3494
+ confirmWholeWiki,
3461
3495
  onProgress: (processed, total) => {
3462
3496
  asyncJobRegistry.update(job.id, { progress: { processed, total, phase: "scanning" } });
3463
3497
  },