@hanna84/mcp-writing 3.6.2 → 3.7.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/CHANGELOG.md CHANGED
@@ -4,9 +4,23 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ #### [v3.7.1](https://github.com/hannasdev/mcp-writing/compare/v3.7.0...v3.7.1)
8
+
9
+ - Docs/chapter epigraph migration gates [`#192`](https://github.com/hannasdev/mcp-writing/pull/192)
10
+
11
+ #### [v3.7.0](https://github.com/hannasdev/mcp-writing/compare/v3.6.2...v3.7.0)
12
+
13
+ > 14 May 2026
14
+
15
+ - feat(review-bundles): redesign outline_discussion PDF profile [`#191`](https://github.com/hannasdev/mcp-writing/pull/191)
16
+ - Release 3.7.0 [`434c8c7`](https://github.com/hannasdev/mcp-writing/commit/434c8c75bb2ebae4d46052587d0e516e484f064f)
17
+
7
18
  #### [v3.6.2](https://github.com/hannasdev/mcp-writing/compare/v3.6.1...v3.6.2)
8
19
 
20
+ > 14 May 2026
21
+
9
22
  - docs(prd): close out beta-reader accountability feature [`#190`](https://github.com/hannasdev/mcp-writing/pull/190)
23
+ - Release 3.6.2 [`7953817`](https://github.com/hannasdev/mcp-writing/commit/79538176f55f06e61b28ed7152edfe798b06d3da)
10
24
 
11
25
  #### [v3.6.1](https://github.com/hannasdev/mcp-writing/compare/v3.6.0...v3.6.1)
12
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanna84/mcp-writing",
3
- "version": "3.6.2",
3
+ "version": "3.7.1",
4
4
  "description": "MCP service for AI-assisted reasoning and editing on long-form fiction projects",
5
5
  "homepage": "https://hannasdev.github.io/mcp-writing/",
6
6
  "type": "module",
@@ -129,11 +129,13 @@ export function buildReviewBundlePlan(dbHandle, {
129
129
  tag,
130
130
  scene_ids,
131
131
  strictness = "warn",
132
- include_scene_ids = true,
132
+ include_scene_ids,
133
133
  include_metadata_sidebar = false,
134
134
  include_paragraph_anchors = false,
135
135
  beta_accountability,
136
136
  bundle_name,
137
+ bundle_title,
138
+ author_name,
137
139
  recipient_name,
138
140
  format = "pdf",
139
141
  } = {}) {
@@ -333,6 +335,8 @@ export function buildReviewBundlePlan(dbHandle, {
333
335
  const resolvedRecipientName = profile === "beta_reader_personalized"
334
336
  ? normalizeRecipientDisplayName(recipient_name)
335
337
  : undefined;
338
+ const normalizedBundleTitle = bundle_title == null ? undefined : String(bundle_title).trim() || undefined;
339
+ const normalizedAuthorName = author_name == null ? undefined : String(author_name).trim() || undefined;
336
340
 
337
341
  const safeBundleName = slugifyBundleName(bundle_name || `${project_id}-${profile}`);
338
342
  const normalizedSceneIds = Array.isArray(scene_ids)
@@ -349,7 +353,12 @@ export function buildReviewBundlePlan(dbHandle, {
349
353
  ? Boolean(beta_accountability ?? true)
350
354
  : false;
351
355
  const isBetaProfile = profile === "beta_reader_personalized";
352
- const resolvedIncludeSceneIds = isBetaProfile ? false : Boolean(include_scene_ids);
356
+ const isOutlineProfile = profile === "outline_discussion";
357
+ // Beta always suppresses scene IDs; outline_discussion defaults to false (structural overview)
358
+ // but can be explicitly enabled. editor_detailed defaults to true.
359
+ const resolvedIncludeSceneIds = isBetaProfile
360
+ ? false
361
+ : Boolean(include_scene_ids ?? (isOutlineProfile ? false : true));
353
362
  const resolvedIncludeMetadataSidebar = isBetaProfile ? false : Boolean(include_metadata_sidebar);
354
363
  const resolvedIncludeParagraphAnchors = isBetaProfile ? false : Boolean(include_paragraph_anchors);
355
364
 
@@ -365,6 +374,8 @@ export function buildReviewBundlePlan(dbHandle, {
365
374
  include_paragraph_anchors: resolvedIncludeParagraphAnchors,
366
375
  beta_accountability: resolvedBetaAccountability,
367
376
  ...(resolvedRecipientName ? { recipient_name: resolvedRecipientName } : {}),
377
+ ...(isOutlineProfile && normalizedBundleTitle ? { bundle_title: normalizedBundleTitle } : {}),
378
+ ...(isOutlineProfile && normalizedAuthorName ? { author_name: normalizedAuthorName } : {}),
368
379
  },
369
380
  },
370
381
  ordering: rows.map(row => ({
@@ -5,6 +5,12 @@ import matter from "gray-matter";
5
5
  import PDFDocument from "pdfkit";
6
6
  import { ReviewBundlePlanError, normalizeRecipientDisplayName } from "./review-bundles-planner.js";
7
7
 
8
+ function prettifyProjectId(projectId) {
9
+ // "universe-1/book-1-the-lamb" -> "Book 1 The Lamb"
10
+ const slug = String(projectId ?? "").split("/").pop() ?? "";
11
+ return slug.split("-").map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
12
+ }
13
+
8
14
  function escapeMarkdown(text) {
9
15
  return String(text ?? "")
10
16
  .replace(/\\/g, "\\\\")
@@ -604,14 +610,16 @@ export function renderReviewBundlePdfWithMetadata(dbHandle, plan, { generatedAt,
604
610
  : Boolean(plan.resolved_scope?.options?.include_scene_ids);
605
611
  const syncDir = syncDirOpt ?? process.env.WRITING_SYNC_DIR ?? null;
606
612
  const isBetaProfile = profile === "beta_reader_personalized";
613
+ const isOutlineProfile = profile === "outline_discussion";
607
614
  const proseFontSize = isBetaProfile ? 8 : 10;
608
615
  const proseLineGap = isBetaProfile ? 1.6 : 3;
609
- const bodyFont = profile === "beta_reader_personalized" ? "Times-Roman" : "Helvetica";
610
- const coverHeadingFont = profile === "beta_reader_personalized" ? "Times-Bold" : "Helvetica-Bold";
611
- // Beta scene headings intentionally use body font (non-bold) per product direction.
612
- const sceneHeadingFont = isBetaProfile ? bodyFont : coverHeadingFont;
613
- const italicFont = profile === "beta_reader_personalized" ? "Times-Italic" : "Helvetica-Oblique";
616
+ // outline_discussion and beta_reader_personalized both use Times for a professional editorial feel.
617
+ const bodyFont = (isBetaProfile || isOutlineProfile) ? "Times-Roman" : "Helvetica";
618
+ const coverHeadingFont = (isBetaProfile || isOutlineProfile) ? "Times-Bold" : "Helvetica-Bold";
619
+ const italicFont = (isBetaProfile || isOutlineProfile) ? "Times-Italic" : "Helvetica-Oblique";
614
620
  const metaFont = italicFont;
621
+ // Beta uses bodyFont (non-bold); outline uses italicFont (elegant, lighter weight); editor uses Bold.
622
+ const sceneHeadingFont = isBetaProfile ? bodyFont : (isOutlineProfile ? italicFont : coverHeadingFont);
615
623
 
616
624
  const sceneIds = plan.ordering.map(row => row.scene_id);
617
625
  const rows = loadBundleSceneRowsWithTags(dbHandle, plan.resolved_scope.project_id, sceneIds);
@@ -627,6 +635,8 @@ export function renderReviewBundlePdfWithMetadata(dbHandle, plan, { generatedAt,
627
635
  const fingerprintSeedHash = fingerprintSeed ? buildFingerprintSeedHash(fingerprintSeed) : null;
628
636
  const pageTokens = [];
629
637
  let pageNumber = 0;
638
+ let outlineCoverCompleted = false;
639
+ let outlineContentPageNumber = 0;
630
640
 
631
641
  const pdfOptions = profile === "beta_reader_personalized"
632
642
  ? {
@@ -635,6 +645,13 @@ export function renderReviewBundlePdfWithMetadata(dbHandle, plan, { generatedAt,
635
645
  margins: { top: 64, right: 58, bottom: 96, left: 58 },
636
646
  autoFirstPage: false,
637
647
  }
648
+ : profile === "outline_discussion"
649
+ ? {
650
+ size: "Letter",
651
+ // Extra bottom margin reserves space for centered page number footer.
652
+ margins: { top: 72, right: 72, bottom: 90, left: 72 },
653
+ autoFirstPage: false,
654
+ }
638
655
  : {
639
656
  size: "Letter",
640
657
  margin: 50,
@@ -671,7 +688,46 @@ export function renderReviewBundlePdfWithMetadata(dbHandle, plan, { generatedAt,
671
688
  doc.x = previousX;
672
689
  doc.y = previousY;
673
690
  };
674
- doc.on("pageAdded", drawAccountabilityFooter);
691
+
692
+ const drawOutlineHeaderFooter = () => {
693
+ if (!isOutlineProfile) return;
694
+ // Do not render running chrome while the cover is still laying out.
695
+ // Very long title/author text can auto-add pages before explicit content starts.
696
+ if (!outlineCoverCompleted) return;
697
+ outlineContentPageNumber += 1;
698
+ const contentPageNum = outlineContentPageNumber;
699
+ const previousX = doc.x;
700
+ const previousY = doc.y;
701
+ // Capture the full text-rendering state before drawing. When a doc.text()
702
+ // call triggers a page break mid-flow, PDFKit re-applies its internal
703
+ // _font/_fontSize to the new page after pageAdded fires. Without restoring
704
+ // these, any title or heading that spans the break renders in the wrong font.
705
+ const prevFontName = doc._font?.name ?? bodyFont;
706
+ const prevFontSize = doc._fontSize ?? proseFontSize;
707
+ const prevFillColor = doc._fillColor ?? "#000000";
708
+ doc.save();
709
+ // Running header: "Outline Overview" — centered, small italic, muted
710
+ doc.font("Times-Italic").fontSize(8).fillColor("#888888");
711
+ const headerText = "Outline Overview";
712
+ const headerX = (doc.page.width - doc.widthOfString(headerText)) / 2;
713
+ doc.text(headerText, headerX, 34, { lineBreak: false });
714
+ // Centered page number in footer
715
+ doc.font("Times-Roman").fontSize(9).fillColor("#888888");
716
+ const pageText = String(contentPageNum);
717
+ const pageX = (doc.page.width - doc.widthOfString(pageText)) / 2;
718
+ doc.text(pageText, pageX, doc.page.height - 48, { lineBreak: false });
719
+ doc.restore();
720
+ // Restore font, size, and fill color. doc.restore() syncs the PDF graphics
721
+ // state operator stack but not PDFKit's internal JS tracking variables.
722
+ doc.font(prevFontName).fontSize(prevFontSize).fillColor(prevFillColor);
723
+ doc.x = previousX;
724
+ doc.y = previousY;
725
+ };
726
+
727
+ doc.on("pageAdded", () => {
728
+ drawAccountabilityFooter();
729
+ drawOutlineHeaderFooter();
730
+ });
675
731
 
676
732
  // Register listeners before any content is written so render-time errors
677
733
  // always reject the returned Promise.
@@ -703,20 +759,60 @@ export function renderReviewBundlePdfWithMetadata(dbHandle, plan, { generatedAt,
703
759
 
704
760
  try {
705
761
  doc.addPage();
706
- const coverLabel = `Review Bundle: ${plan.resolved_scope.project_id}`;
707
- doc.fontSize(isBetaProfile ? 11 : 24).font(coverHeadingFont).text(coverLabel, { align: "left" });
708
- doc.moveDown(isBetaProfile ? 0.2 : 0.5);
709
- doc.fontSize(11).font(bodyFont);
710
- if (profile !== "beta_reader_personalized") {
711
- doc.text(`Profile: ${profile}`, { align: "left" });
712
- }
713
- if (profile === "beta_reader_personalized") {
714
- doc.text(`Recipient: ${recipientDisplayName}`, { align: "left" });
762
+
763
+ if (isOutlineProfile) {
764
+ // Clean editorial cover: title, optional author, rule, document type label.
765
+ const textWidth = doc.page.width - doc.page.margins.left - doc.page.margins.right;
766
+ const bundleTitle = plan.resolved_scope.options.bundle_title
767
+ ?? prettifyProjectId(plan.resolved_scope.project_id);
768
+ const authorName = plan.resolved_scope.options.author_name ?? null;
769
+ // Position title roughly 35% down the page for a balanced layout.
770
+ const coverTitleY = doc.page.height * 0.35;
771
+ doc.fontSize(28).font("Times-Bold").fillColor("#000000");
772
+ doc.text(bundleTitle, doc.page.margins.left, coverTitleY, { width: textWidth, align: "center" });
773
+ doc.moveDown(1.0);
774
+ if (authorName) {
775
+ doc.fontSize(14).font("Times-Roman");
776
+ doc.text(authorName, { width: textWidth, align: "center" });
777
+ doc.moveDown(1.0);
778
+ } else {
779
+ doc.moveDown(0.9);
780
+ }
781
+ // Hairline rule
782
+ const ruleY = doc.y;
783
+ doc.moveTo(doc.page.margins.left, ruleY)
784
+ .lineTo(doc.page.margins.left + textWidth, ruleY)
785
+ .strokeColor("#888888")
786
+ .lineWidth(0.5)
787
+ .stroke();
788
+ doc.moveDown(0.8);
789
+ // Document type label
790
+ doc.fontSize(11).font("Times-Italic").fillColor("#888888");
791
+ doc.text("Outline Overview", { width: textWidth, align: "center" });
792
+ doc.moveDown(0.3);
793
+ doc.fontSize(9).font("Times-Roman").fillColor("#777777");
794
+ doc.text(`Generated: ${effectiveGeneratedAt}`, { width: textWidth, align: "center" });
795
+ doc.fillColor("#000000");
796
+ outlineCoverCompleted = true;
797
+ // Start scene content on a fresh page so the cover is always standalone
798
+ // and the pageAdded event fires to draw the running header + footer.
799
+ doc.addPage();
715
800
  } else {
716
- doc.text(`Generated: ${effectiveGeneratedAt}`, { align: "left" });
717
- doc.text(`Scenes: ${plan.summary.scene_count}`, { align: "left" });
801
+ const coverLabel = `Review Bundle: ${plan.resolved_scope.project_id}`;
802
+ doc.fontSize(isBetaProfile ? 11 : 24).font(coverHeadingFont).text(coverLabel, { align: "left" });
803
+ doc.moveDown(isBetaProfile ? 0.2 : 0.5);
804
+ doc.fontSize(11).font(bodyFont);
805
+ if (profile !== "beta_reader_personalized") {
806
+ doc.text(`Profile: ${profile}`, { align: "left" });
807
+ }
808
+ if (profile === "beta_reader_personalized") {
809
+ doc.text(`Recipient: ${recipientDisplayName}`, { align: "left" });
810
+ } else {
811
+ doc.text(`Generated: ${effectiveGeneratedAt}`, { align: "left" });
812
+ doc.text(`Scenes: ${plan.summary.scene_count}`, { align: "left" });
813
+ }
814
+ doc.moveDown();
718
815
  }
719
- doc.moveDown();
720
816
 
721
817
  if (profile === "beta_reader_personalized") {
722
818
  doc.fontSize(12).font("Times-Bold").text("Usage Notice", { align: "left" });
@@ -744,20 +840,38 @@ export function renderReviewBundlePdfWithMetadata(dbHandle, plan, { generatedAt,
744
840
  doc.moveDown(1.0);
745
841
  }
746
842
 
747
- // Skip title rendering for epigraphs in beta profile
748
- const isEpigraph = isBetaProfile && isEpigraphScene(scene);
843
+ // For outline_discussion: chapter dividers when the chapter changes.
844
+ if (isOutlineProfile && scene.chapter != null) {
845
+ if (!prevScene || prevScene.chapter !== scene.chapter) {
846
+ if (sceneIndex > 0) doc.moveDown(1.2);
847
+ const textWidth = doc.page.width - doc.page.margins.left - doc.page.margins.right;
848
+ const chapterLabel = `Chapter ${scene.chapter}`;
849
+ doc.fontSize(10).font("Times-Bold").fillColor("#000000");
850
+ doc.text(chapterLabel, { width: textWidth });
851
+ doc.moveDown(0.25);
852
+ doc.moveTo(doc.page.margins.left, doc.y)
853
+ .lineTo(doc.page.margins.left + textWidth, doc.y)
854
+ .strokeColor("#cccccc").lineWidth(0.5).stroke();
855
+ doc.moveDown(0.6);
856
+ } else if (sceneIndex > 0) {
857
+ doc.moveDown(1.2);
858
+ }
859
+ }
860
+
861
+ // Skip title for epigraphs in beta and outline profiles.
862
+ const isEpigraph = (isBetaProfile || isOutlineProfile) && isEpigraphScene(scene);
749
863
  if (!isEpigraph) {
750
- doc.fontSize(isBetaProfile ? 13 : 14).font(sceneHeadingFont);
864
+ doc.fontSize(isBetaProfile ? 13 : isOutlineProfile ? 12 : 14).font(sceneHeadingFont);
751
865
  let heading = scene.title || scene.scene_id;
752
866
  if (includeSceneIds) {
753
867
  heading += ` [${scene.scene_id}]`;
754
868
  }
755
869
  doc.text(heading, { align: isBetaProfile ? "center" : "left" });
756
- doc.moveDown(isBetaProfile ? 1.6 : 0.2);
870
+ doc.moveDown(isBetaProfile ? 1.6 : isOutlineProfile ? 0.5 : 0.2);
757
871
  }
758
872
 
759
873
  const metaParts = [];
760
- if (profile !== "beta_reader_personalized") {
874
+ if (profile !== "beta_reader_personalized" && !isEpigraph) {
761
875
  if (scene.pov) metaParts.push(`POV: ${scene.pov}`);
762
876
  if (scene.save_the_cat_beat) metaParts.push(`Beat: ${scene.save_the_cat_beat}`);
763
877
  }
@@ -766,17 +880,17 @@ export function renderReviewBundlePdfWithMetadata(dbHandle, plan, { generatedAt,
766
880
  doc.fontSize(9).font(metaFont);
767
881
  doc.text(metaParts.join(" • "), { align: "left", width: metaWidth });
768
882
  doc.font(bodyFont);
769
- doc.moveDown(0.2);
883
+ doc.moveDown(isOutlineProfile ? 0.4 : 0.2);
770
884
  }
771
885
 
772
- if (profile === "outline_discussion" && scene.logline) {
773
- doc.fontSize(10).font("Helvetica-Oblique");
886
+ if (profile === "outline_discussion" && !isEpigraph && scene.logline) {
887
+ doc.fontSize(10).font(bodyFont);
774
888
  const textWidth = doc.page.width - doc.page.margins.left - doc.page.margins.right;
775
- doc.text(`"${scene.logline}"`, { align: "left", width: textWidth });
776
- doc.moveDown(0.3);
889
+ doc.text(scene.logline, { align: "left", width: textWidth, lineGap: 2 });
890
+ doc.moveDown(0.6);
777
891
  }
778
892
 
779
- if (profile === "editor_detailed" || profile === "beta_reader_personalized") {
893
+ if (profile === "editor_detailed" || profile === "beta_reader_personalized" || (isOutlineProfile && isEpigraph)) {
780
894
  let prose = "";
781
895
  const resolved = readProse(scene.file_path, { syncDir });
782
896
  if (resolved === null) {
@@ -810,16 +924,37 @@ export function renderReviewBundlePdfWithMetadata(dbHandle, plan, { generatedAt,
810
924
  }
811
925
 
812
926
  const textWidth = doc.page.width - doc.page.margins.left - doc.page.margins.right;
813
- renderProseWithInlineEmphasis(doc, prose, {
814
- bodyFont,
815
- italicFont,
816
- fontSize: proseFontSize,
817
- align: "left",
818
- width: textWidth,
819
- lineGap: proseLineGap,
820
- paragraphGap: 0,
821
- blankLineMoveDown: isBetaProfile ? 0.15 : 0.65,
822
- });
927
+ if (isOutlineProfile && isEpigraph) {
928
+ // Epigraphs: centered, italic, narrower column with extra breathing room.
929
+ const epigraphWidth = Math.round(textWidth * 0.68);
930
+ const epigraphIndent = Math.round((textWidth - epigraphWidth) / 2);
931
+ doc.moveDown(1.5);
932
+ const savedX = doc.x;
933
+ doc.x = doc.page.margins.left + epigraphIndent;
934
+ renderProseWithInlineEmphasis(doc, prose, {
935
+ bodyFont: italicFont,
936
+ italicFont: bodyFont,
937
+ fontSize: proseFontSize,
938
+ align: "left",
939
+ width: epigraphWidth,
940
+ lineGap: proseLineGap,
941
+ paragraphGap: 0,
942
+ blankLineMoveDown: 0.65,
943
+ });
944
+ doc.x = savedX;
945
+ doc.moveDown(1.5);
946
+ } else {
947
+ renderProseWithInlineEmphasis(doc, prose, {
948
+ bodyFont,
949
+ italicFont,
950
+ fontSize: proseFontSize,
951
+ align: "left",
952
+ width: textWidth,
953
+ lineGap: proseLineGap,
954
+ paragraphGap: 0,
955
+ blankLineMoveDown: isBetaProfile ? 0.15 : 0.65,
956
+ });
957
+ }
823
958
  }
824
959
 
825
960
  doc.moveDown(0.5);
@@ -32,12 +32,14 @@ export function registerReviewBundleTools(s, {
32
32
  tag: z.string().optional().describe("Optional tag filter (exact match)."),
33
33
  scene_ids: z.array(z.string()).optional().describe("Optional explicit scene_id allowlist. Intersects with other filters."),
34
34
  strictness: z.enum(REVIEW_BUNDLE_STRICTNESS).optional().describe("Strictness mode: warn (default) or fail."),
35
- include_scene_ids: z.boolean().optional().describe("Rendering option (default true). Echoed in resolved_scope.options for downstream rendering; does not change planning results."),
35
+ include_scene_ids: z.boolean().optional().describe("Rendering option (default true for editor_detailed; false for outline_discussion). beta_reader_personalized always resolves this to false, even when true is passed. Echoed in resolved_scope.options for downstream rendering; does not change planning results."),
36
36
  include_metadata_sidebar: z.boolean().optional().describe("Rendering option (default false). Echoed in resolved_scope.options for downstream rendering; does not change planning results."),
37
37
  include_paragraph_anchors: z.boolean().optional().describe("Rendering option (default false). Echoed in resolved_scope.options for downstream rendering; does not change planning results."),
38
38
  recipient_name: z.string().optional().describe("Optional recipient display name for beta_reader_personalized profile."),
39
39
  beta_accountability: z.boolean().optional().describe("Enable accountability footer + fingerprint metadata for beta_reader_personalized output (default true for beta profile)."),
40
40
  bundle_name: z.string().optional().describe("Optional output bundle base name override (slugified in planned outputs)."),
41
+ bundle_title: z.string().optional().describe("Optional book or document title for the cover page (outline_discussion PDF only)."),
42
+ author_name: z.string().optional().describe("Optional author name for the cover page (outline_discussion PDF only)."),
41
43
  format: z.enum(["pdf", "markdown", "both"]).optional().describe("Planned output format: pdf (default), markdown, or both. Affects planned_outputs filenames only; preview_review_bundle does not render artifacts."),
42
44
  },
43
45
  async ({
@@ -49,12 +51,14 @@ export function registerReviewBundleTools(s, {
49
51
  tag,
50
52
  scene_ids,
51
53
  strictness = "warn",
52
- include_scene_ids = true,
54
+ include_scene_ids,
53
55
  include_metadata_sidebar = false,
54
56
  include_paragraph_anchors = false,
55
57
  recipient_name,
56
58
  beta_accountability,
57
59
  bundle_name,
60
+ bundle_title,
61
+ author_name,
58
62
  format = "pdf",
59
63
  }) => {
60
64
  const projectIdCheck = validateProjectId(project_id);
@@ -78,6 +82,8 @@ export function registerReviewBundleTools(s, {
78
82
  recipient_name,
79
83
  beta_accountability,
80
84
  bundle_name,
85
+ bundle_title,
86
+ author_name,
81
87
  format,
82
88
  });
83
89
  return jsonResponse({
@@ -120,12 +126,14 @@ export function registerReviewBundleTools(s, {
120
126
  tag: z.string().optional().describe("Optional tag filter (exact match)."),
121
127
  scene_ids: z.array(z.string()).optional().describe("Optional explicit scene_id allowlist. Intersects with other filters."),
122
128
  strictness: z.enum(REVIEW_BUNDLE_STRICTNESS).optional().describe("Strictness mode: warn (default) or fail."),
123
- include_scene_ids: z.boolean().optional().describe("Include scene IDs in headings (default true). Applies to both PDF and markdown."),
129
+ include_scene_ids: z.boolean().optional().describe("Include scene IDs in headings (default true for editor_detailed; false for outline_discussion). beta_reader_personalized always resolves this to false, even when true is passed. Applies to both PDF and markdown."),
124
130
  include_metadata_sidebar: z.boolean().optional().describe("Include metadata sidebar in markdown output (default false). Markdown only — no effect on PDF."),
125
131
  include_paragraph_anchors: z.boolean().optional().describe("Include paragraph anchors in markdown output (default false). Markdown only — no effect on PDF."),
126
132
  recipient_name: z.string().optional().describe("Optional recipient display name for beta_reader_personalized profile."),
127
133
  beta_accountability: z.boolean().optional().describe("Enable accountability footer + fingerprint metadata for beta_reader_personalized output (default true for beta profile)."),
128
134
  bundle_name: z.string().optional().describe("Optional output bundle base name override (slugified in filenames)."),
135
+ bundle_title: z.string().optional().describe("Optional book or document title for the cover page (outline_discussion PDF only)."),
136
+ author_name: z.string().optional().describe("Optional author name for the cover page (outline_discussion PDF only)."),
129
137
  source_commit: z.string().optional().describe("Optional explicit source commit for provenance. Defaults to current HEAD when available."),
130
138
  format: z.enum(["pdf", "markdown", "both"]).optional().describe("Output format: pdf (default), markdown, or both."),
131
139
  },
@@ -139,12 +147,14 @@ export function registerReviewBundleTools(s, {
139
147
  tag,
140
148
  scene_ids,
141
149
  strictness = "warn",
142
- include_scene_ids = true,
150
+ include_scene_ids,
143
151
  include_metadata_sidebar = false,
144
152
  include_paragraph_anchors = false,
145
153
  recipient_name,
146
154
  beta_accountability,
147
155
  bundle_name,
156
+ bundle_title,
157
+ author_name,
148
158
  source_commit,
149
159
  format = "pdf",
150
160
  }) => {
@@ -182,6 +192,8 @@ export function registerReviewBundleTools(s, {
182
192
  recipient_name,
183
193
  beta_accountability,
184
194
  bundle_name,
195
+ bundle_title,
196
+ author_name,
185
197
  format,
186
198
  });
187
199