@attentionform/astra-mcp-server 0.3.0 → 0.4.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/LICENSE +132 -84
- package/README.md +5 -3
- package/dist/index.js +1001 -418
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/mcp/baseline-gate.d.ts +24 -0
- package/dist/lib/mcp/diff.d.ts +4 -0
- package/dist/lib/mcp/errors.d.ts +6 -0
- package/dist/lib/mcp/handlers/parity.d.ts +2 -8
- package/dist/lib/mcp/handlers/save-close.d.ts +18 -0
- package/dist/lib/mcp/schemas.d.ts +1 -1
- package/dist/lib/session-store.d.ts +6 -0
- package/dist/tools/compose-document.d.ts +7 -0
- package/dist/tools/export.d.ts +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -5566,6 +5566,23 @@ function writeCorePropertiesPart(adapter, xml) {
|
|
|
5566
5566
|
} catch {
|
|
5567
5567
|
}
|
|
5568
5568
|
}
|
|
5569
|
+
function registerExistingCorePropertiesPart(adapter) {
|
|
5570
|
+
let present = false;
|
|
5571
|
+
try {
|
|
5572
|
+
present = adapter.readTextOrNull(CORE_PROPERTIES_PART) !== null;
|
|
5573
|
+
} catch {
|
|
5574
|
+
return;
|
|
5575
|
+
}
|
|
5576
|
+
if (!present) return;
|
|
5577
|
+
try {
|
|
5578
|
+
ensureContentTypeOverride(adapter);
|
|
5579
|
+
} catch {
|
|
5580
|
+
}
|
|
5581
|
+
try {
|
|
5582
|
+
ensurePackageRel(adapter);
|
|
5583
|
+
} catch {
|
|
5584
|
+
}
|
|
5585
|
+
}
|
|
5569
5586
|
function patchToMetadata(patch) {
|
|
5570
5587
|
const out = {};
|
|
5571
5588
|
if (patch.title !== void 0) out.title = patch.title;
|
|
@@ -9811,6 +9828,41 @@ function hasLiveNumberingForSync(ast) {
|
|
|
9811
9828
|
}
|
|
9812
9829
|
}
|
|
9813
9830
|
|
|
9831
|
+
// ../engine/src/ooxml/quote-style.ts
|
|
9832
|
+
var QUOTE_STYLE_ID = "Quote";
|
|
9833
|
+
var QUOTE_INDENT_TWIPS = 720;
|
|
9834
|
+
var QUOTE_STYLE_BODY = `<w:style w:type="paragraph" w:styleId="Quote">
|
|
9835
|
+
<w:name w:val="Quote"/>
|
|
9836
|
+
<w:basedOn w:val="Normal"/>
|
|
9837
|
+
<w:next w:val="Normal"/>
|
|
9838
|
+
<w:uiPriority w:val="29"/>
|
|
9839
|
+
<w:qFormat/>
|
|
9840
|
+
<w:pPr>
|
|
9841
|
+
<w:pBdr>
|
|
9842
|
+
<w:left w:val="single" w:sz="18" w:space="10" w:color="94A3B8"/>
|
|
9843
|
+
</w:pBdr>
|
|
9844
|
+
<w:spacing w:before="120" w:after="120"/>
|
|
9845
|
+
<w:ind w:left="${QUOTE_INDENT_TWIPS}"/>
|
|
9846
|
+
</w:pPr>
|
|
9847
|
+
<w:rPr>
|
|
9848
|
+
<w:i/>
|
|
9849
|
+
<w:color w:val="475569"/>
|
|
9850
|
+
</w:rPr>
|
|
9851
|
+
</w:style>`;
|
|
9852
|
+
var QUOTE_STYLE_XML = QUOTE_STYLE_BODY.replace("<w:style ", '<w:style xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" ');
|
|
9853
|
+
function ensureQuoteStyle(stylesDom) {
|
|
9854
|
+
if (!stylesDom) return false;
|
|
9855
|
+
const root = stylesDom.getElementsByTagName("w:styles")[0];
|
|
9856
|
+
if (!root) return false;
|
|
9857
|
+
for (const style of Array.from(stylesDom.getElementsByTagName("w:style"))) {
|
|
9858
|
+
if (style.getAttribute("w:styleId") === QUOTE_STYLE_ID) return false;
|
|
9859
|
+
}
|
|
9860
|
+
const fragment = new DOMParser().parseFromString(QUOTE_STYLE_XML, "text/xml").documentElement;
|
|
9861
|
+
if (!fragment) return false;
|
|
9862
|
+
root.appendChild(stylesDom.importNode(fragment, true));
|
|
9863
|
+
return true;
|
|
9864
|
+
}
|
|
9865
|
+
|
|
9814
9866
|
// ../engine/src/parser.ts
|
|
9815
9867
|
function hasAncestorTag(el, tag) {
|
|
9816
9868
|
for (let n = el.parentNode; n; n = n.parentNode) {
|
|
@@ -10475,6 +10527,7 @@ var DocxParser = class _DocxParser {
|
|
|
10475
10527
|
} else {
|
|
10476
10528
|
this.stylesDom = null;
|
|
10477
10529
|
}
|
|
10530
|
+
registerExistingCorePropertiesPart(this.getZipAdapter());
|
|
10478
10531
|
this.invalidateMediaCache();
|
|
10479
10532
|
this.assignStableIds();
|
|
10480
10533
|
this._bumpNodePagesRevision();
|
|
@@ -11040,6 +11093,27 @@ var DocxParser = class _DocxParser {
|
|
|
11040
11093
|
return tracked;
|
|
11041
11094
|
}
|
|
11042
11095
|
// --- AST layer: stable id assignment ---
|
|
11096
|
+
/** Orders AST blocks by where their element sits in the body; blocks inside one body child keep their order. */
|
|
11097
|
+
sortBlocksInDocumentOrder(nodes) {
|
|
11098
|
+
const body = this.documentDom.getElementsByTagName("w:body")[0];
|
|
11099
|
+
if (!body || nodes.length < 2) return;
|
|
11100
|
+
const topIndex = /* @__PURE__ */ new Map();
|
|
11101
|
+
let position2 = 0;
|
|
11102
|
+
for (let child = body.firstChild; child; child = child.nextSibling) {
|
|
11103
|
+
if (child.nodeType === 1) topIndex.set(child, position2++);
|
|
11104
|
+
}
|
|
11105
|
+
const keyOf = (element) => {
|
|
11106
|
+
let current = element;
|
|
11107
|
+
while (current && current.parentNode !== body) current = current.parentNode;
|
|
11108
|
+
return current ? topIndex.get(current) ?? Number.MAX_SAFE_INTEGER : Number.MAX_SAFE_INTEGER;
|
|
11109
|
+
};
|
|
11110
|
+
const keyed = nodes.map((node, index) => ({ node, index, key: keyOf(node.xml) }));
|
|
11111
|
+
if (keyed.every((entry, i) => i === 0 || keyed[i - 1].key <= entry.key)) return;
|
|
11112
|
+
keyed.sort((a, b) => a.key - b.key || a.index - b.index);
|
|
11113
|
+
keyed.forEach((entry, i) => {
|
|
11114
|
+
nodes[i] = entry.node;
|
|
11115
|
+
});
|
|
11116
|
+
}
|
|
11043
11117
|
async getAST() {
|
|
11044
11118
|
const ast = {
|
|
11045
11119
|
metadata: {},
|
|
@@ -11417,6 +11491,7 @@ var DocxParser = class _DocxParser {
|
|
|
11417
11491
|
ast.sectPrFlags = { titlePg: false, evenAndOddHeaders: false };
|
|
11418
11492
|
}
|
|
11419
11493
|
ast.nextChangeId = firstFreeRevisionId(this.maxPackageRevisionId());
|
|
11494
|
+
this.sortBlocksInDocumentOrder(rootNodes);
|
|
11420
11495
|
ast.sections.push(...splitIntoSections(rootNodes, fileSections));
|
|
11421
11496
|
const prevNumberingAst = this.lastAstForNumbering;
|
|
11422
11497
|
if (prevNumberingAst && prevNumberingAst !== ast) {
|
|
@@ -13191,6 +13266,7 @@ ${validation.errors.join("\n")}`, "repair the part XML or re-save the document i
|
|
|
13191
13266
|
<w:spacing w:before="120" w:after="120" w:line="240" w:lineRule="auto"/>
|
|
13192
13267
|
</w:pPr>
|
|
13193
13268
|
</w:style>
|
|
13269
|
+
${QUOTE_STYLE_BODY}
|
|
13194
13270
|
<w:style w:type="character" w:styleId="Code">
|
|
13195
13271
|
<w:name w:val="Code"/>
|
|
13196
13272
|
<w:rPr>
|
|
@@ -13573,15 +13649,26 @@ var import_isomorphic_git = __toESM(require("isomorphic-git"));
|
|
|
13573
13649
|
var import_fs3 = __toESM(require("fs"));
|
|
13574
13650
|
var import_promises = __toESM(require("fs/promises"));
|
|
13575
13651
|
var import_path2 = __toESM(require("path"));
|
|
13652
|
+
function hasUriScheme(ref) {
|
|
13653
|
+
if (/^[a-zA-Z]:[\\/]/.test(ref)) return false;
|
|
13654
|
+
return /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(ref);
|
|
13655
|
+
}
|
|
13656
|
+
function withoutTrailingSeparator(p, normalized) {
|
|
13657
|
+
const root = p.parse(normalized).root;
|
|
13658
|
+
let out = normalized;
|
|
13659
|
+
while (out.length > root.length && out.endsWith(p.sep)) out = out.slice(0, -1);
|
|
13660
|
+
return out;
|
|
13661
|
+
}
|
|
13576
13662
|
function isPathInsideDir(candidate, dir, platform = process.platform) {
|
|
13577
|
-
const
|
|
13578
|
-
|
|
13663
|
+
const p = platform === "win32" ? import_path2.default.win32 : import_path2.default.posix;
|
|
13664
|
+
let c = withoutTrailingSeparator(p, p.normalize(candidate));
|
|
13665
|
+
let d = withoutTrailingSeparator(p, p.normalize(dir));
|
|
13579
13666
|
if (platform === "win32") {
|
|
13580
|
-
|
|
13581
|
-
|
|
13582
|
-
return lc === ld || lc.startsWith(ld + import_path2.default.sep.toLowerCase());
|
|
13667
|
+
c = c.toLowerCase();
|
|
13668
|
+
d = d.toLowerCase();
|
|
13583
13669
|
}
|
|
13584
|
-
|
|
13670
|
+
if (c === d) return true;
|
|
13671
|
+
return c.startsWith(d.endsWith(p.sep) ? d : d + p.sep);
|
|
13585
13672
|
}
|
|
13586
13673
|
function canonicalizeForAsset(resolved) {
|
|
13587
13674
|
try {
|
|
@@ -14860,6 +14947,19 @@ function warnIfLowContrast(warnings, seen, fill, textColor, where) {
|
|
|
14860
14947
|
warnings.push(msg);
|
|
14861
14948
|
}
|
|
14862
14949
|
}
|
|
14950
|
+
function autoTextForFill(fill, current) {
|
|
14951
|
+
if (!/^[0-9A-Fa-f]{6}$/.test(fill)) return void 0;
|
|
14952
|
+
if (contrastRatio((current ?? "000000").toUpperCase(), fill.toUpperCase()) >= TABLE_CONTRAST_MIN_RATIO) return void 0;
|
|
14953
|
+
return contrastRatio("FFFFFF", fill.toUpperCase()) > contrastRatio("000000", fill.toUpperCase()) ? "FFFFFF" : "000000";
|
|
14954
|
+
}
|
|
14955
|
+
function noteAutoText(warnings, seen, fill, text2, where) {
|
|
14956
|
+
const key = `auto:${fill}:${text2}`;
|
|
14957
|
+
if (seen.has(key)) return;
|
|
14958
|
+
seen.add(key);
|
|
14959
|
+
warnings.push(
|
|
14960
|
+
`Table note: text set to #${text2} at ${where} because black text is not readable on fill #${fill} (contrast ${contrastRatio(text2, fill).toFixed(2)}:1). Pass textColor to choose another colour.`
|
|
14961
|
+
);
|
|
14962
|
+
}
|
|
14863
14963
|
function paintCellTextColor(tc, doc, normColor) {
|
|
14864
14964
|
let ps = directChildren5(tc, "w:p");
|
|
14865
14965
|
if (ps.length === 0) {
|
|
@@ -15486,6 +15586,13 @@ async function executeTable(opIn, ast, guide) {
|
|
|
15486
15586
|
let effectiveText;
|
|
15487
15587
|
if (rIdx === 0) {
|
|
15488
15588
|
effectiveText = explicitTextColor ?? guideTextForFill(rowFill);
|
|
15589
|
+
if (effectiveText === void 0) {
|
|
15590
|
+
const auto = autoTextForFill(rowFill, firstExplicitCellTextColor(tc));
|
|
15591
|
+
if (auto) {
|
|
15592
|
+
effectiveText = auto;
|
|
15593
|
+
noteAutoText(warnings, warnedContrastPairs, rowFill, auto, `header cell (${rIdx + 1},${cIdx + 1}) of table '${tableNode.id}'`);
|
|
15594
|
+
}
|
|
15595
|
+
}
|
|
15489
15596
|
if (effectiveText && tblEl.ownerDocument) {
|
|
15490
15597
|
paintCellTextColor(tc, tblEl.ownerDocument, effectiveText);
|
|
15491
15598
|
}
|
|
@@ -16012,7 +16119,14 @@ async function executeTable(opIn, ast, guide) {
|
|
|
16012
16119
|
shd.setAttribute("w:color", "auto");
|
|
16013
16120
|
shd.setAttribute("w:fill", normFill);
|
|
16014
16121
|
sortChildrenBySchema(tcPr, TCPR_CHILD_ORDER);
|
|
16015
|
-
|
|
16122
|
+
let effectiveText = explicitTextColor ?? guideTextForFill(normFill);
|
|
16123
|
+
if (effectiveText === void 0) {
|
|
16124
|
+
const auto = autoTextForFill(normFill, firstExplicitCellTextColor(tc));
|
|
16125
|
+
if (auto) {
|
|
16126
|
+
effectiveText = auto;
|
|
16127
|
+
noteAutoText(warnings, warnedContrastPairs, normFill, auto, `cell (${shown(rowIndex)},${shown(colIndex)}) of table '${tableNode.id}'`);
|
|
16128
|
+
}
|
|
16129
|
+
}
|
|
16016
16130
|
if (effectiveText && tblEl.ownerDocument) {
|
|
16017
16131
|
paintCellTextColor(tc, tblEl.ownerDocument, effectiveText);
|
|
16018
16132
|
}
|
|
@@ -16296,7 +16410,7 @@ async function executeTable(opIn, ast, guide) {
|
|
|
16296
16410
|
break;
|
|
16297
16411
|
}
|
|
16298
16412
|
case "setCellTextColor": {
|
|
16299
|
-
const colorInput = raw.color ?? op.color;
|
|
16413
|
+
const colorInput = raw.color ?? op.color ?? raw.textColor ?? op.textColor;
|
|
16300
16414
|
if (colorInput === void 0 || colorInput === null || String(colorInput).trim() === "") {
|
|
16301
16415
|
throw new Error(
|
|
16302
16416
|
`E_VALIDATION: setCellTextColor requires color (got no color). Remedy: pass color as 6 hex digits without '#', e.g. color:'FFFFFF' for white header text.`
|
|
@@ -16752,6 +16866,13 @@ function sanitizeXml(xmlStr) {
|
|
|
16752
16866
|
// ../engine/src/operation-schema.ts
|
|
16753
16867
|
var import_zod2 = require("zod");
|
|
16754
16868
|
|
|
16869
|
+
// ../engine/src/anchor-alias.ts
|
|
16870
|
+
function anchorAliasOf(value) {
|
|
16871
|
+
if (value === "start" || value === "@start") return "start";
|
|
16872
|
+
if (value === "end" || value === "@end") return "end";
|
|
16873
|
+
return null;
|
|
16874
|
+
}
|
|
16875
|
+
|
|
16755
16876
|
// ../engine/src/operation-params.ts
|
|
16756
16877
|
var STRUCTURAL = /* @__PURE__ */ new Set(["action", "target", "tableId", "operation", "op", "params", "trackChanges", "expectedVersion"]);
|
|
16757
16878
|
var ROW = ["rowIndex", "row"];
|
|
@@ -16777,9 +16898,17 @@ var TABLE_SUBOP_KEYS = {
|
|
|
16777
16898
|
setCellAlignment: ["align", "alignment", ...ROW, ...COL],
|
|
16778
16899
|
setTableAlignment: ["align", "alignment"],
|
|
16779
16900
|
setCellPadding: ["padding", ...ROW, ...COL],
|
|
16780
|
-
setCellTextColor: ["color", ...ROW, ...COL],
|
|
16901
|
+
setCellTextColor: ["color", "textColor", ...ROW, ...COL],
|
|
16781
16902
|
setKeepTogether: []
|
|
16782
16903
|
};
|
|
16904
|
+
function describeTableSubOpKeys() {
|
|
16905
|
+
const aliases = /* @__PURE__ */ new Set(["rowIndex", "colIndex", "columnIndex"]);
|
|
16906
|
+
const parts = Object.entries(TABLE_SUBOP_KEYS).map(([sub, keys]) => {
|
|
16907
|
+
const shown2 = keys.filter((k) => !aliases.has(k));
|
|
16908
|
+
return shown2.length > 0 ? `${sub}(${shown2.join(",")})` : sub;
|
|
16909
|
+
});
|
|
16910
|
+
return `Parameters each operation reads (row and col count from 1): ${parts.join("; ")}.`;
|
|
16911
|
+
}
|
|
16783
16912
|
function isRecord(value) {
|
|
16784
16913
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
16785
16914
|
}
|
|
@@ -20733,6 +20862,8 @@ var expectedVersion = import_zod2.z.number().int().nonnegative().optional();
|
|
|
20733
20862
|
var nestedParams = import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional();
|
|
20734
20863
|
var sectionNumber = import_zod2.z.number().int().positive("sections count from 1 (1 is the first section)").describe("Section number, counting from 1 (1 is the first section).");
|
|
20735
20864
|
var position = import_zod2.z.enum(["before", "after"]);
|
|
20865
|
+
var ANCHOR_REF_DESCRIPTION = "A node id from inspect_document, or 'start' / 'end' for the first / last block of the document (no inspect needed; '@start' and '@end' work too). With 'start', position defaults to 'before'; with 'end', to 'after'.";
|
|
20866
|
+
var ANCHOR_REF_SHORT = "A node id, or 'start' / 'end' for the first / last block.";
|
|
20736
20867
|
var operationInsertPosition = import_zod2.z.enum(["before", "after"]).optional();
|
|
20737
20868
|
var FONT_SIZE_PT_REGEX = /^\s*(\d+(?:\.\d+)?)\s*pt\s*$/i;
|
|
20738
20869
|
function parseFontSize(sz) {
|
|
@@ -20835,7 +20966,7 @@ var insertOp = import_zod2.z.object({
|
|
|
20835
20966
|
// engine appends in call order without ever reading them) need no dummy
|
|
20836
20967
|
// anchor. Body inserts still require both — enforced by the strict
|
|
20837
20968
|
// validators below, not by this shape.
|
|
20838
|
-
anchor: import_zod2.z.string().min(1).max(256).optional(),
|
|
20969
|
+
anchor: import_zod2.z.string().min(1).max(256).describe(ANCHOR_REF_DESCRIPTION).optional(),
|
|
20839
20970
|
position: operationInsertPosition,
|
|
20840
20971
|
text: import_zod2.z.string().max(5e5).optional(),
|
|
20841
20972
|
xml: import_zod2.z.string().max(5e5).optional(),
|
|
@@ -20957,7 +21088,7 @@ var clearFormatOp = import_zod2.z.object({
|
|
|
20957
21088
|
var moveOp = import_zod2.z.object({
|
|
20958
21089
|
action: import_zod2.z.literal("move"),
|
|
20959
21090
|
target: nodeIdOrList,
|
|
20960
|
-
anchor: import_zod2.z.string().min(1).max(256),
|
|
21091
|
+
anchor: import_zod2.z.string().min(1).max(256).describe(ANCHOR_REF_SHORT),
|
|
20961
21092
|
position,
|
|
20962
21093
|
expectedVersion,
|
|
20963
21094
|
params: nestedParams
|
|
@@ -20994,8 +21125,8 @@ var tableOp = import_zod2.z.object({
|
|
|
20994
21125
|
action: import_zod2.z.literal("table"),
|
|
20995
21126
|
target: import_zod2.z.string().min(1).max(256).optional(),
|
|
20996
21127
|
tableId: import_zod2.z.string().min(1).max(256).optional(),
|
|
20997
|
-
operation: tableSubOp.optional(),
|
|
20998
|
-
op: tableSubOp.optional(),
|
|
21128
|
+
operation: tableSubOp.describe(describeTableSubOpKeys()).optional(),
|
|
21129
|
+
op: tableSubOp.describe("Alias of operation.").optional(),
|
|
20999
21130
|
trackChanges: import_zod2.z.boolean().optional(),
|
|
21000
21131
|
author: revisionAuthor,
|
|
21001
21132
|
date: revisionDate,
|
|
@@ -21025,7 +21156,6 @@ var tableOp = import_zod2.z.object({
|
|
|
21025
21156
|
border: import_zod2.z.string().max(32).optional(),
|
|
21026
21157
|
color: import_zod2.z.string().max(16).optional(),
|
|
21027
21158
|
sz: fontSizeOrSzSchema.optional(),
|
|
21028
|
-
fontSize: fontSizeOrSzSchema.optional(),
|
|
21029
21159
|
vAlign: import_zod2.z.string().max(16).optional(),
|
|
21030
21160
|
align: tableAlignmentSchema.optional(),
|
|
21031
21161
|
alignment: tableAlignmentSchema.optional(),
|
|
@@ -21087,7 +21217,7 @@ var namedStyleOp = import_zod2.z.object({
|
|
|
21087
21217
|
var fieldOp = import_zod2.z.object({
|
|
21088
21218
|
action: import_zod2.z.literal("field"),
|
|
21089
21219
|
operation: import_zod2.z.enum(["insertTOC", "insertPageNumber", "insertWordCount", "updateFields"]),
|
|
21090
|
-
anchor: import_zod2.z.string().min(1).max(256).optional(),
|
|
21220
|
+
anchor: import_zod2.z.string().min(1).max(256).describe(ANCHOR_REF_SHORT).optional(),
|
|
21091
21221
|
position: operationInsertPosition,
|
|
21092
21222
|
title: import_zod2.z.string().max(1e3).optional(),
|
|
21093
21223
|
levels: import_zod2.z.number().int().min(1).max(9).optional(),
|
|
@@ -21103,7 +21233,7 @@ var equationOp = import_zod2.z.object({
|
|
|
21103
21233
|
action: import_zod2.z.literal("equation"),
|
|
21104
21234
|
operation: import_zod2.z.enum(["insert", "replace"]),
|
|
21105
21235
|
latex: import_zod2.z.string().min(1).max(5e4),
|
|
21106
|
-
anchor: import_zod2.z.string().min(1).max(256).optional(),
|
|
21236
|
+
anchor: import_zod2.z.string().min(1).max(256).describe(ANCHOR_REF_SHORT).optional(),
|
|
21107
21237
|
position: operationInsertPosition,
|
|
21108
21238
|
target: import_zod2.z.string().min(1).max(256).optional(),
|
|
21109
21239
|
id: import_zod2.z.string().min(1).max(256).optional(),
|
|
@@ -21217,7 +21347,7 @@ var chartOp = import_zod2.z.object({
|
|
|
21217
21347
|
categories: import_zod2.z.array(import_zod2.z.string().max(500)).max(1e3).optional(),
|
|
21218
21348
|
series: import_zod2.z.array(chartSeries).max(100).optional(),
|
|
21219
21349
|
style: chartStyle.optional(),
|
|
21220
|
-
anchor: import_zod2.z.string().min(1).max(256).optional(),
|
|
21350
|
+
anchor: import_zod2.z.string().min(1).max(256).describe(ANCHOR_REF_SHORT).optional(),
|
|
21221
21351
|
id: import_zod2.z.string().min(1).max(256).optional(),
|
|
21222
21352
|
widthEmu: import_zod2.z.number().positive().max(1e8).optional(),
|
|
21223
21353
|
heightEmu: import_zod2.z.number().positive().max(1e8).optional(),
|
|
@@ -21227,7 +21357,7 @@ var chartOp = import_zod2.z.object({
|
|
|
21227
21357
|
var imageOp = import_zod2.z.object({
|
|
21228
21358
|
action: import_zod2.z.literal("image"),
|
|
21229
21359
|
operation: import_zod2.z.enum(["insert", "resize", "reposition", "setWrap", "setCaption", "delete", "setAltText"]),
|
|
21230
|
-
anchor: import_zod2.z.string().min(1).max(256).optional(),
|
|
21360
|
+
anchor: import_zod2.z.string().min(1).max(256).describe(ANCHOR_REF_SHORT).optional(),
|
|
21231
21361
|
target: import_zod2.z.string().min(1).max(256).optional(),
|
|
21232
21362
|
fileData: import_zod2.z.string().max(2e7).optional(),
|
|
21233
21363
|
filePath: import_zod2.z.string().min(1).max(1024).optional(),
|
|
@@ -21241,7 +21371,8 @@ var imageOp = import_zod2.z.object({
|
|
|
21241
21371
|
var calloutOp = import_zod2.z.object({
|
|
21242
21372
|
action: import_zod2.z.literal("callout"),
|
|
21243
21373
|
text: import_zod2.z.string().min(1).max(1e5),
|
|
21244
|
-
anchor: import_zod2.z.string().min(1).max(256).optional(),
|
|
21374
|
+
anchor: import_zod2.z.string().min(1).max(256).describe(ANCHOR_REF_SHORT).optional(),
|
|
21375
|
+
id: import_zod2.z.string().min(1).max(256).optional(),
|
|
21245
21376
|
title: import_zod2.z.string().max(1e3).optional(),
|
|
21246
21377
|
theme: import_zod2.z.string().max(32).optional(),
|
|
21247
21378
|
position: operationInsertPosition,
|
|
@@ -21251,7 +21382,7 @@ var calloutOp = import_zod2.z.object({
|
|
|
21251
21382
|
var footnoteOp = import_zod2.z.object({
|
|
21252
21383
|
action: import_zod2.z.literal("footnote"),
|
|
21253
21384
|
operation: import_zod2.z.enum(["insert"]).optional(),
|
|
21254
|
-
anchor: import_zod2.z.string().min(1).max(256),
|
|
21385
|
+
anchor: import_zod2.z.string().min(1).max(256).describe(ANCHOR_REF_SHORT),
|
|
21255
21386
|
text: import_zod2.z.string().min(1).max(5e4),
|
|
21256
21387
|
id: import_zod2.z.string().min(1).max(256).optional(),
|
|
21257
21388
|
offset: import_zod2.z.number().int().nonnegative().optional(),
|
|
@@ -21260,7 +21391,7 @@ var footnoteOp = import_zod2.z.object({
|
|
|
21260
21391
|
var endnoteOp = import_zod2.z.object({
|
|
21261
21392
|
action: import_zod2.z.literal("endnote"),
|
|
21262
21393
|
operation: import_zod2.z.enum(["insert"]).optional(),
|
|
21263
|
-
anchor: import_zod2.z.string().min(1).max(256),
|
|
21394
|
+
anchor: import_zod2.z.string().min(1).max(256).describe(ANCHOR_REF_SHORT),
|
|
21264
21395
|
text: import_zod2.z.string().min(1).max(5e4),
|
|
21265
21396
|
id: import_zod2.z.string().min(1).max(256).optional(),
|
|
21266
21397
|
offset: import_zod2.z.number().int().nonnegative().optional(),
|
|
@@ -21439,11 +21570,11 @@ function insertBodyAnchorDetail(op) {
|
|
|
21439
21570
|
if (effectiveTarget === "header" || effectiveTarget === "footer") return null;
|
|
21440
21571
|
const missing = [];
|
|
21441
21572
|
if (typeof rec["anchor"] !== "string" || rec["anchor"].length < 1) {
|
|
21442
|
-
missing.push("anchor: anchor is required for body inserts [invalid_type]");
|
|
21573
|
+
missing.push("anchor: anchor is required for body inserts: a node id from inspect_document, or 'start' / 'end' for the first / last block [invalid_type]");
|
|
21443
21574
|
}
|
|
21444
21575
|
const pos = rec["position"];
|
|
21445
|
-
if (pos !== "before" && pos !== "after") {
|
|
21446
|
-
missing.push("position: position is required for body inserts [invalid_type]");
|
|
21576
|
+
if (pos !== "before" && pos !== "after" && anchorAliasOf(rec["anchor"]) === null) {
|
|
21577
|
+
missing.push("position: position is required for body inserts (or use anchor 'start' / 'end', which imply it) [invalid_type]");
|
|
21447
21578
|
}
|
|
21448
21579
|
return missing.length > 0 ? missing.join("; ") : null;
|
|
21449
21580
|
}
|
|
@@ -28157,7 +28288,7 @@ function resolveImageFilePath(allowedDir, filePath) {
|
|
|
28157
28288
|
"use a plain relative file path inside the allowed directory"
|
|
28158
28289
|
);
|
|
28159
28290
|
}
|
|
28160
|
-
if (
|
|
28291
|
+
if (hasUriScheme(trimmed)) {
|
|
28161
28292
|
throw toError(
|
|
28162
28293
|
"E_SANDBOX",
|
|
28163
28294
|
"image filePath points outside the allowed directory",
|
|
@@ -28328,7 +28459,7 @@ function resolveAssetPath(allowedDir, refPath, line2, what) {
|
|
|
28328
28459
|
if (trimmed.includes("\0") || /%00/i.test(trimmed) || trimmed.includes("\n") || trimmed.includes("\r")) {
|
|
28329
28460
|
fail(line2, `${what} reference path is not usable`, "use a plain relative file path inside the allowed directory");
|
|
28330
28461
|
}
|
|
28331
|
-
if (
|
|
28462
|
+
if (hasUriScheme(trimmed)) {
|
|
28332
28463
|
fail(line2, `${what} reference points outside the allowed directory`, "keep referenced files inside the allowed directory and use a relative path");
|
|
28333
28464
|
}
|
|
28334
28465
|
const candidate = import_path5.default.isAbsolute(trimmed) ? import_path5.default.resolve(trimmed) : import_path5.default.resolve(allowedDir, trimmed);
|
|
@@ -29598,7 +29729,8 @@ async function executeCallout(op, ast, parser2, guide) {
|
|
|
29598
29729
|
if (!targetSection) throw new Error("No section available for callout box insertion");
|
|
29599
29730
|
const ownerDoc = targetSection.nodes[0]?.xml?.ownerDocument || ownerDocumentOf(ast.sections);
|
|
29600
29731
|
if (!ownerDoc) throw new Error("Cannot resolve XML DOM document");
|
|
29601
|
-
const
|
|
29732
|
+
const clientId = pick("id", op.id);
|
|
29733
|
+
const calloutId = typeof clientId === "string" && clientId.length > 0 ? clientId : isCalloutReproducible() ? (() => {
|
|
29602
29734
|
const base = `callout_${calloutHash8(`${resolvedAnchor ?? ""}:${resolvedPosition}:${themeKey}:${resolvedTitle ?? ""}:${resolvedText}`)}`;
|
|
29603
29735
|
const live = /* @__PURE__ */ new Set();
|
|
29604
29736
|
try {
|
|
@@ -30909,6 +31041,208 @@ function executePageBorder(op, ast, parser2) {
|
|
|
30909
31041
|
section.version += 1;
|
|
30910
31042
|
}
|
|
30911
31043
|
|
|
31044
|
+
// ../engine/src/compose/pristine-seed.ts
|
|
31045
|
+
function pristineTemplateSeedIds(parser2, requestedTemplate) {
|
|
31046
|
+
if (!parser2.hasPristineBuiltinSeed()) return null;
|
|
31047
|
+
let summary;
|
|
31048
|
+
try {
|
|
31049
|
+
summary = parser2.getStructuralSummary();
|
|
31050
|
+
} catch {
|
|
31051
|
+
return null;
|
|
31052
|
+
}
|
|
31053
|
+
const nodes = summary.nodes ?? [];
|
|
31054
|
+
if (![1, 2, 4, 5, 6].includes(nodes.length)) return null;
|
|
31055
|
+
if (summary.hasTrackedChanges === true) return null;
|
|
31056
|
+
if (Object.keys(summary.mediaFiles ?? {}).length > 0) return null;
|
|
31057
|
+
if ((summary.headers ?? []).length > 0) return null;
|
|
31058
|
+
if ((summary.footers ?? []).length > 0) return null;
|
|
31059
|
+
if ((summary.charts ?? []).length > 0) return null;
|
|
31060
|
+
try {
|
|
31061
|
+
const zip = parser2.getZipAdapter?.();
|
|
31062
|
+
if (typeof zip?.hasFile === "function") {
|
|
31063
|
+
for (const part of [
|
|
31064
|
+
"word/footnotes.xml",
|
|
31065
|
+
"word/endnotes.xml",
|
|
31066
|
+
"word/comments.xml",
|
|
31067
|
+
"word/commentsExtended.xml"
|
|
31068
|
+
]) {
|
|
31069
|
+
try {
|
|
31070
|
+
if (zip.hasFile(part)) return null;
|
|
31071
|
+
} catch {
|
|
31072
|
+
return null;
|
|
31073
|
+
}
|
|
31074
|
+
}
|
|
31075
|
+
} else {
|
|
31076
|
+
return null;
|
|
31077
|
+
}
|
|
31078
|
+
} catch {
|
|
31079
|
+
return null;
|
|
31080
|
+
}
|
|
31081
|
+
for (const n of nodes) {
|
|
31082
|
+
if (n.hasTrackedChanges === true) return null;
|
|
31083
|
+
const raw = n.rawXml ?? "";
|
|
31084
|
+
if (raw.includes("w:footnoteReference") || raw.includes("w:endnoteReference") || raw.includes("w:commentReference") || raw.includes("w:commentRangeStart") || raw.includes("w:commentRangeEnd")) {
|
|
31085
|
+
return null;
|
|
31086
|
+
}
|
|
31087
|
+
}
|
|
31088
|
+
const ids = nodes.map((n) => n.id ?? "").filter((id) => id.length > 0);
|
|
31089
|
+
if (ids.length !== nodes.length) return null;
|
|
31090
|
+
if (nodes.length === 1) {
|
|
31091
|
+
const node = nodes[0];
|
|
31092
|
+
if (node.type !== "paragraph") return null;
|
|
31093
|
+
if ((node.text ?? "").trim() !== "") return null;
|
|
31094
|
+
return ids;
|
|
31095
|
+
}
|
|
31096
|
+
if (nodes.length === 2) {
|
|
31097
|
+
const [first, second] = nodes;
|
|
31098
|
+
const t0 = (first.text ?? "").trim();
|
|
31099
|
+
const t1 = (second.text ?? "").trim();
|
|
31100
|
+
if (t0 === BLANK_SEED_TITLE && t1 === BLANK_SEED_HINT) {
|
|
31101
|
+
const firstRaw = first.rawXml ?? "";
|
|
31102
|
+
const secondRaw = second.rawXml ?? "";
|
|
31103
|
+
if (!firstRaw.includes('w:val="Title"') || !secondRaw.includes('w:val="Subtitle"')) return null;
|
|
31104
|
+
return ids;
|
|
31105
|
+
}
|
|
31106
|
+
const tmpl = requestedTemplate?.toLowerCase();
|
|
31107
|
+
if (t0 === "MEMORANDUM" && tmpl === "memo") return ids;
|
|
31108
|
+
if (t0 === "Letter" && tmpl === "letter") return ids;
|
|
31109
|
+
if (t0 === "Proposal" && tmpl === "proposal") return ids;
|
|
31110
|
+
if (t0 === "INVOICE" && tmpl === "invoice") return ids;
|
|
31111
|
+
return null;
|
|
31112
|
+
}
|
|
31113
|
+
if (nodes.length === 4) {
|
|
31114
|
+
const t0 = (nodes[0].text ?? "").trim();
|
|
31115
|
+
const tmpl = requestedTemplate?.toLowerCase();
|
|
31116
|
+
if (t0 === "Research Benchmark Report" && tmpl === "research") return ids;
|
|
31117
|
+
if (t0 === "Corporate Strategy Roadmap" && tmpl === "corporate") return ids;
|
|
31118
|
+
if (t0 === "Velocity Audit & Performance" && tmpl === "bar-chart-3d") return ids;
|
|
31119
|
+
return null;
|
|
31120
|
+
}
|
|
31121
|
+
if (nodes.length === 5) {
|
|
31122
|
+
const t0 = (nodes[0].text ?? "").trim();
|
|
31123
|
+
const tmpl = requestedTemplate?.toLowerCase();
|
|
31124
|
+
if (t0 === "Financial Report & Audit" && tmpl === "financial") return ids;
|
|
31125
|
+
return null;
|
|
31126
|
+
}
|
|
31127
|
+
if (nodes.length === 6) {
|
|
31128
|
+
const t0 = (nodes[0].text ?? "").trim();
|
|
31129
|
+
const t2 = (nodes[2].text ?? "").trim();
|
|
31130
|
+
const tmpl = requestedTemplate?.toLowerCase();
|
|
31131
|
+
if (t0 === "Q3 Executive Financial Performance Report" && t2 === "1. Executive Summary") {
|
|
31132
|
+
if (tmpl === "report") return ids;
|
|
31133
|
+
return null;
|
|
31134
|
+
}
|
|
31135
|
+
if (t0 === "Manuscript Title" && t2 === "Chapter 1: The Beginning" && tmpl === "novel") {
|
|
31136
|
+
return ids;
|
|
31137
|
+
}
|
|
31138
|
+
if (t0 === "Neural Document Architectures: A Comparative Benchmark" && tmpl === "academic") {
|
|
31139
|
+
return ids;
|
|
31140
|
+
}
|
|
31141
|
+
if (t0 === "The ASTra Dispatch \u2014 Monthly Engineering Gazette" && tmpl === "newsletter") return ids;
|
|
31142
|
+
if (t0 === "ASTra Document Engine API Specification" && (tmpl === "api" || tmpl === "api-spec")) return ids;
|
|
31143
|
+
return null;
|
|
31144
|
+
}
|
|
31145
|
+
return null;
|
|
31146
|
+
}
|
|
31147
|
+
var SEED_TEMPLATE_NAMES = [
|
|
31148
|
+
"memo",
|
|
31149
|
+
"letter",
|
|
31150
|
+
"proposal",
|
|
31151
|
+
"invoice",
|
|
31152
|
+
"research",
|
|
31153
|
+
"corporate",
|
|
31154
|
+
"bar-chart-3d",
|
|
31155
|
+
"financial",
|
|
31156
|
+
"report",
|
|
31157
|
+
"novel",
|
|
31158
|
+
"academic",
|
|
31159
|
+
"newsletter",
|
|
31160
|
+
"api-spec"
|
|
31161
|
+
];
|
|
31162
|
+
function pristineSeedIdsOfAnyTemplate(parser2) {
|
|
31163
|
+
const blank = pristineTemplateSeedIds(parser2);
|
|
31164
|
+
if (blank !== null) return blank;
|
|
31165
|
+
for (const name2 of SEED_TEMPLATE_NAMES) {
|
|
31166
|
+
const ids = pristineTemplateSeedIds(parser2, name2);
|
|
31167
|
+
if (ids !== null) return ids;
|
|
31168
|
+
}
|
|
31169
|
+
return null;
|
|
31170
|
+
}
|
|
31171
|
+
|
|
31172
|
+
// ../engine/src/document-title.ts
|
|
31173
|
+
var TEMPLATE_NAMES = [
|
|
31174
|
+
"blank",
|
|
31175
|
+
"memo",
|
|
31176
|
+
"report",
|
|
31177
|
+
"letter",
|
|
31178
|
+
"proposal",
|
|
31179
|
+
"invoice",
|
|
31180
|
+
"research",
|
|
31181
|
+
"corporate",
|
|
31182
|
+
"financial",
|
|
31183
|
+
"novel",
|
|
31184
|
+
"academic",
|
|
31185
|
+
"newsletter",
|
|
31186
|
+
"api-spec",
|
|
31187
|
+
"bar-chart-3d"
|
|
31188
|
+
];
|
|
31189
|
+
var PLACEHOLDER_TITLES = new Set(TEMPLATE_NAMES.map((name2) => newDocumentTitle(name2).toLowerCase()));
|
|
31190
|
+
var DERIVED_TITLE_MAX = 200;
|
|
31191
|
+
var DEFAULT_CREATOR = "ASTra";
|
|
31192
|
+
function isPlaceholderTitle(title) {
|
|
31193
|
+
if (typeof title !== "string") return true;
|
|
31194
|
+
const clean = title.replace(/\s+/g, " ").trim().toLowerCase();
|
|
31195
|
+
return clean === "" || PLACEHOLDER_TITLES.has(clean);
|
|
31196
|
+
}
|
|
31197
|
+
function cleanTitle(text2) {
|
|
31198
|
+
const plain = typeof text2 === "string" ? Array.from(text2, (ch) => ch.charCodeAt(0) < 32 || ch.charCodeAt(0) === 127 ? " " : ch).join("").replace(/\s+/g, " ").trim() : "";
|
|
31199
|
+
return plain.length > DERIVED_TITLE_MAX ? plain.slice(0, DERIVED_TITLE_MAX).trimEnd() : plain;
|
|
31200
|
+
}
|
|
31201
|
+
function deriveTitleFromContent(ast) {
|
|
31202
|
+
const nodes = ast.sections.flatMap((section) => section.nodes);
|
|
31203
|
+
const styleOf = (node) => {
|
|
31204
|
+
const pPr = Array.from(node.xml.childNodes).find((c) => c.nodeType === 1 && c.tagName === "w:pPr");
|
|
31205
|
+
const pStyle = pPr ? Array.from(pPr.childNodes).find((c) => c.nodeType === 1 && c.tagName === "w:pStyle") : void 0;
|
|
31206
|
+
return pStyle?.getAttribute("w:val") ?? node.metadata?.namedStyle ?? void 0;
|
|
31207
|
+
};
|
|
31208
|
+
const textOf4 = (node) => {
|
|
31209
|
+
const runs = node.xml.getElementsByTagName("w:t");
|
|
31210
|
+
let text2 = "";
|
|
31211
|
+
for (let i = 0; i < runs.length; i++) text2 += runs[i].textContent ?? "";
|
|
31212
|
+
return text2 !== "" ? text2 : node.metadata?.text ?? "";
|
|
31213
|
+
};
|
|
31214
|
+
const pick = (match, from) => {
|
|
31215
|
+
for (const node of nodes) {
|
|
31216
|
+
if (node.type !== "paragraph" || !match(node)) continue;
|
|
31217
|
+
const title = cleanTitle(textOf4(node));
|
|
31218
|
+
if (title !== "" && !isPlaceholderTitle(title)) return { title, from };
|
|
31219
|
+
}
|
|
31220
|
+
return null;
|
|
31221
|
+
};
|
|
31222
|
+
return pick((n) => n.id.startsWith("cover_title_"), "cover page") ?? pick((n) => styleOf(n) === "Title", "Title") ?? pick((n) => styleOf(n) === "Heading1", "Heading 1");
|
|
31223
|
+
}
|
|
31224
|
+
function applyDocumentIdentity(ast, parser2) {
|
|
31225
|
+
const result = {};
|
|
31226
|
+
const patch = {};
|
|
31227
|
+
if (isPlaceholderTitle(ast.metadata?.title)) {
|
|
31228
|
+
const derived = deriveTitleFromContent(ast);
|
|
31229
|
+
if (derived) {
|
|
31230
|
+
patch.title = derived.title;
|
|
31231
|
+
result.title = derived.title;
|
|
31232
|
+
result.titleSetFrom = derived.from;
|
|
31233
|
+
}
|
|
31234
|
+
}
|
|
31235
|
+
const author = typeof ast.settings?.defaultAuthor === "string" ? ast.settings.defaultAuthor.trim() : "";
|
|
31236
|
+
if (author !== "" && author !== DEFAULT_CREATOR && ast.metadata?.creator === DEFAULT_CREATOR) {
|
|
31237
|
+
patch.creator = author;
|
|
31238
|
+
patch.lastModifiedBy = author;
|
|
31239
|
+
result.creatorSet = author;
|
|
31240
|
+
}
|
|
31241
|
+
if (Object.keys(patch).length === 0) return result;
|
|
31242
|
+
new CorePropertiesManipulator(ast, parser2?.getZipAdapter()).update(patch);
|
|
31243
|
+
return result;
|
|
31244
|
+
}
|
|
31245
|
+
|
|
30912
31246
|
// ../engine/src/operations/coverPage.ts
|
|
30913
31247
|
function isCoverReproducible(explicit, env) {
|
|
30914
31248
|
try {
|
|
@@ -30973,6 +31307,7 @@ function executeCoverPage(op, ast, parser2) {
|
|
|
30973
31307
|
if (!firstSection || firstSection.nodes.length === 0) {
|
|
30974
31308
|
throw new Error("Cannot insert cover page into an empty document");
|
|
30975
31309
|
}
|
|
31310
|
+
const seedIds = parser2 ? pristineSeedIdsOfAnyTemplate(parser2) : null;
|
|
30976
31311
|
const doc = firstSection.nodes[0].xml.ownerDocument;
|
|
30977
31312
|
if (!doc) throw new Error("Document not found on first section");
|
|
30978
31313
|
const sectPr = doc.getElementsByTagName("w:sectPr")[0];
|
|
@@ -31039,8 +31374,8 @@ function executeCoverPage(op, ast, parser2) {
|
|
|
31039
31374
|
titleColor.setAttribute("w:val", accentColor);
|
|
31040
31375
|
titleRPr.appendChild(titleFont);
|
|
31041
31376
|
titleRPr.appendChild(titleBold);
|
|
31042
|
-
titleRPr.appendChild(titleSz);
|
|
31043
31377
|
titleRPr.appendChild(titleColor);
|
|
31378
|
+
titleRPr.appendChild(titleSz);
|
|
31044
31379
|
titleRun.appendChild(titleRPr);
|
|
31045
31380
|
titleRun.appendChild(createTextElement(doc, titleText));
|
|
31046
31381
|
titleP.appendChild(titleRun);
|
|
@@ -31066,8 +31401,8 @@ function executeCoverPage(op, ast, parser2) {
|
|
|
31066
31401
|
subSz.setAttribute("w:val", "28");
|
|
31067
31402
|
const subColor = createWordElement(doc, "w:color");
|
|
31068
31403
|
subColor.setAttribute("w:val", "64748B");
|
|
31069
|
-
subRPr.appendChild(subSz);
|
|
31070
31404
|
subRPr.appendChild(subColor);
|
|
31405
|
+
subRPr.appendChild(subSz);
|
|
31071
31406
|
subRun.appendChild(subRPr);
|
|
31072
31407
|
subRun.appendChild(createTextElement(doc, subtitleText));
|
|
31073
31408
|
subP.appendChild(subRun);
|
|
@@ -31117,8 +31452,8 @@ function executeCoverPage(op, ast, parser2) {
|
|
|
31117
31452
|
metaSz.setAttribute("w:val", "22");
|
|
31118
31453
|
const metaColor = createWordElement(doc, "w:color");
|
|
31119
31454
|
metaColor.setAttribute("w:val", "475569");
|
|
31120
|
-
metaRPr.appendChild(metaSz);
|
|
31121
31455
|
metaRPr.appendChild(metaColor);
|
|
31456
|
+
metaRPr.appendChild(metaSz);
|
|
31122
31457
|
metaRun.appendChild(metaRPr);
|
|
31123
31458
|
const metaLines = [authorText, orgText, dateText].filter(Boolean).join(" \u2022 ");
|
|
31124
31459
|
metaRun.appendChild(createTextElement(doc, metaLines || dateText));
|
|
@@ -31163,6 +31498,13 @@ function executeCoverPage(op, ast, parser2) {
|
|
|
31163
31498
|
}
|
|
31164
31499
|
}
|
|
31165
31500
|
}
|
|
31501
|
+
if (seedIds !== null && seedIds.length > 0) {
|
|
31502
|
+
executeDelete({ action: "delete", target: seedIds }, ast, parser2);
|
|
31503
|
+
}
|
|
31504
|
+
try {
|
|
31505
|
+
applyDocumentIdentity(ast, parser2);
|
|
31506
|
+
} catch {
|
|
31507
|
+
}
|
|
31166
31508
|
}
|
|
31167
31509
|
|
|
31168
31510
|
// ../engine/src/operations/metadata.ts
|
|
@@ -31334,8 +31676,9 @@ function collectBatchTouchedIds(ops, ast) {
|
|
|
31334
31676
|
const touch = (raw) => {
|
|
31335
31677
|
if (typeof raw !== "string" || raw.length === 0) return;
|
|
31336
31678
|
let resolved = raw;
|
|
31337
|
-
|
|
31338
|
-
|
|
31679
|
+
const alias = anchorAliasOf(raw);
|
|
31680
|
+
if (alias === "start") resolved = order[0] ?? raw;
|
|
31681
|
+
else if (alias === "end") resolved = order[order.length - 1] ?? raw;
|
|
31339
31682
|
else {
|
|
31340
31683
|
const cell = parseCellAddress(raw);
|
|
31341
31684
|
if (cell && order.includes(cell.tableId)) resolved = cell.tableId;
|
|
@@ -31928,13 +32271,13 @@ var OperationEngine = class _OperationEngine {
|
|
|
31928
32271
|
/**
|
|
31929
32272
|
* R-4 helper: true when the op mints a live node id from the client id.
|
|
31930
32273
|
* Only these routes use `op.id` verbatim as the node id; other actions
|
|
31931
|
-
* either ignore `id` (footnote/
|
|
32274
|
+
* either ignore `id` (footnote/field generate their own) or are
|
|
31932
32275
|
* rejected at the schema gate, so live collision applies here only.
|
|
31933
32276
|
*/
|
|
31934
32277
|
static mintsNodeId(op) {
|
|
31935
32278
|
const fields = op;
|
|
31936
32279
|
const action = typeof fields["action"] === "string" ? String(fields["action"]) : "";
|
|
31937
|
-
if (action === "insert") return true;
|
|
32280
|
+
if (action === "insert" || action === "callout") return true;
|
|
31938
32281
|
if (action === "equation") {
|
|
31939
32282
|
const sub = fields["operation"] ?? fields["params"]?.["operation"];
|
|
31940
32283
|
return sub === "insert";
|
|
@@ -32034,7 +32377,7 @@ var OperationEngine = class _OperationEngine {
|
|
|
32034
32377
|
* E_VALIDATION + remedy.
|
|
32035
32378
|
*/
|
|
32036
32379
|
static isAnchorAlias(value) {
|
|
32037
|
-
return value
|
|
32380
|
+
return anchorAliasOf(value) !== null;
|
|
32038
32381
|
}
|
|
32039
32382
|
resolveAnchorAlias(op) {
|
|
32040
32383
|
const fields = op;
|
|
@@ -32050,6 +32393,10 @@ var OperationEngine = class _OperationEngine {
|
|
|
32050
32393
|
}
|
|
32051
32394
|
}
|
|
32052
32395
|
if (aliasSeen === null) return;
|
|
32396
|
+
for (const h of holders) {
|
|
32397
|
+
const alias = anchorAliasOf(h["anchor"]);
|
|
32398
|
+
if (alias !== null && h["position"] === void 0) h["position"] = alias === "start" ? "before" : "after";
|
|
32399
|
+
}
|
|
32053
32400
|
let first;
|
|
32054
32401
|
let last;
|
|
32055
32402
|
for (const section of this.ast.sections ?? []) {
|
|
@@ -32066,8 +32413,9 @@ var OperationEngine = class _OperationEngine {
|
|
|
32066
32413
|
);
|
|
32067
32414
|
}
|
|
32068
32415
|
for (const h of holders) {
|
|
32069
|
-
|
|
32070
|
-
|
|
32416
|
+
const alias = anchorAliasOf(h["anchor"]);
|
|
32417
|
+
if (alias === "start") h["anchor"] = first;
|
|
32418
|
+
else if (alias === "end") h["anchor"] = last;
|
|
32071
32419
|
}
|
|
32072
32420
|
}
|
|
32073
32421
|
async routeOperation(op, allowedDir) {
|
|
@@ -33324,7 +33672,11 @@ ${textLines.map((l) => `> ${l}`).join("\n")}
|
|
|
33324
33672
|
`);
|
|
33325
33673
|
} else {
|
|
33326
33674
|
const textLines = text2 ? text2.split("\n") : [];
|
|
33327
|
-
|
|
33675
|
+
const ind = node.xml?.getElementsByTagName?.("w:pPr")?.[0]?.getElementsByTagName?.("w:ind")?.[0] ?? null;
|
|
33676
|
+
const left = ind ? parseInt(ind.getAttribute("w:left") || "0", 10) : 0;
|
|
33677
|
+
const depth = style === "quote" && Number.isFinite(left) && left > 0 ? Math.min(3, Math.max(1, Math.round(left / 720))) : 1;
|
|
33678
|
+
const marker = ">".repeat(1).concat(" >".repeat(depth - 1));
|
|
33679
|
+
lines.push(`${textLines.map((l) => `${marker} ${l}`).join("\n")}
|
|
33328
33680
|
`);
|
|
33329
33681
|
}
|
|
33330
33682
|
} else {
|
|
@@ -33828,17 +34180,19 @@ function directKids(parent, tag) {
|
|
|
33828
34180
|
return out;
|
|
33829
34181
|
}
|
|
33830
34182
|
function cellTextOf(tc) {
|
|
33831
|
-
|
|
34183
|
+
const parts = [];
|
|
33832
34184
|
try {
|
|
33833
34185
|
const paras = directKids(tc, "w:p");
|
|
33834
34186
|
for (const p of paras) {
|
|
34187
|
+
let text2 = "";
|
|
33835
34188
|
const ts = p.getElementsByTagName("w:t");
|
|
33836
34189
|
for (let i = 0; i < ts.length; i++) text2 += ts[i].textContent || "";
|
|
34190
|
+
if (text2.trim().length > 0) parts.push(text2);
|
|
33837
34191
|
}
|
|
33838
34192
|
} catch {
|
|
33839
34193
|
return "";
|
|
33840
34194
|
}
|
|
33841
|
-
return
|
|
34195
|
+
return parts.join(" ");
|
|
33842
34196
|
}
|
|
33843
34197
|
function displayLen(value) {
|
|
33844
34198
|
return [...value].length;
|
|
@@ -33937,6 +34291,23 @@ function renderTableGrid(grid) {
|
|
|
33937
34291
|
const rows = [...byRow.keys()].sort((a, b) => a - b);
|
|
33938
34292
|
return rows.map((r) => (byRow.get(r) ?? []).map(renderGridCell).join(" | ")).join(" ; ");
|
|
33939
34293
|
}
|
|
34294
|
+
function tableGridPlainText(tbl) {
|
|
34295
|
+
try {
|
|
34296
|
+
const el = tbl;
|
|
34297
|
+
if (!el || typeof el.getElementsByTagName !== "function") return void 0;
|
|
34298
|
+
const grid = extractTableGrid(el);
|
|
34299
|
+
if (grid.cells.length === 0) return void 0;
|
|
34300
|
+
const byRow = /* @__PURE__ */ new Map();
|
|
34301
|
+
for (const c of grid.cells) {
|
|
34302
|
+
const list = byRow.get(c.row);
|
|
34303
|
+
if (list) list.push(c.text);
|
|
34304
|
+
else byRow.set(c.row, [c.text]);
|
|
34305
|
+
}
|
|
34306
|
+
return [...byRow.keys()].sort((a, b) => a - b).map((r) => (byRow.get(r) ?? []).join(" | ")).join("\n");
|
|
34307
|
+
} catch {
|
|
34308
|
+
return void 0;
|
|
34309
|
+
}
|
|
34310
|
+
}
|
|
33940
34311
|
function renderChartText(node) {
|
|
33941
34312
|
const kind = node.type === "chart3d" ? "3D Chart" : "Chart";
|
|
33942
34313
|
const data = node.metadata.chartData;
|
|
@@ -38010,6 +38381,134 @@ var HeadlessVisualRenderer = class {
|
|
|
38010
38381
|
};
|
|
38011
38382
|
var headlessScreenshotProvider = new HeadlessVisualRenderer();
|
|
38012
38383
|
|
|
38384
|
+
// ../engine/src/stats.ts
|
|
38385
|
+
function nearestOwner(el, tagName) {
|
|
38386
|
+
let cur = el.parentNode;
|
|
38387
|
+
while (cur && typeof cur === "object") {
|
|
38388
|
+
const e = cur;
|
|
38389
|
+
if (e.tagName === tagName) return e;
|
|
38390
|
+
cur = e.parentNode;
|
|
38391
|
+
}
|
|
38392
|
+
return null;
|
|
38393
|
+
}
|
|
38394
|
+
function tableCellTextSpaced(node) {
|
|
38395
|
+
try {
|
|
38396
|
+
const xml = node.xml;
|
|
38397
|
+
if (!xml || typeof xml.getElementsByTagName !== "function") return void 0;
|
|
38398
|
+
const tcs = xml.getElementsByTagName("w:tc");
|
|
38399
|
+
if (!tcs || tcs.length === 0) return void 0;
|
|
38400
|
+
const cells = [];
|
|
38401
|
+
for (let i = 0; i < tcs.length; i++) {
|
|
38402
|
+
const tc = tcs[i];
|
|
38403
|
+
const paras = tc.getElementsByTagName("w:p");
|
|
38404
|
+
if (paras.length > 0) {
|
|
38405
|
+
const parts = [];
|
|
38406
|
+
for (let p = 0; p < paras.length; p++) {
|
|
38407
|
+
const para = paras[p];
|
|
38408
|
+
if (nearestOwner(para, "w:tc") !== tc) continue;
|
|
38409
|
+
const runs = para.getElementsByTagName("w:t");
|
|
38410
|
+
let t = "";
|
|
38411
|
+
for (let r = 0; r < runs.length; r++) {
|
|
38412
|
+
const run = runs[r];
|
|
38413
|
+
if (nearestOwner(run, "w:tc") !== tc) continue;
|
|
38414
|
+
if (nearestOwner(run, "w:p") !== para) continue;
|
|
38415
|
+
t += run.textContent || "";
|
|
38416
|
+
}
|
|
38417
|
+
if (t.trim().length > 0) parts.push(t.trim());
|
|
38418
|
+
}
|
|
38419
|
+
if (parts.length > 0) cells.push(parts.join(" "));
|
|
38420
|
+
} else {
|
|
38421
|
+
const runs = tc.getElementsByTagName("w:t");
|
|
38422
|
+
let t = "";
|
|
38423
|
+
for (let r = 0; r < runs.length; r++) {
|
|
38424
|
+
const run = runs[r];
|
|
38425
|
+
if (nearestOwner(run, "w:tc") !== tc) continue;
|
|
38426
|
+
t += run.textContent || "";
|
|
38427
|
+
}
|
|
38428
|
+
if (t.trim().length > 0) cells.push(t.trim());
|
|
38429
|
+
}
|
|
38430
|
+
}
|
|
38431
|
+
if (cells.length === 0) return "";
|
|
38432
|
+
return cells.join(" ");
|
|
38433
|
+
} catch {
|
|
38434
|
+
return void 0;
|
|
38435
|
+
}
|
|
38436
|
+
}
|
|
38437
|
+
function nestedTableCount(node) {
|
|
38438
|
+
try {
|
|
38439
|
+
const xml = node.xml;
|
|
38440
|
+
if (!xml || typeof xml.getElementsByTagName !== "function") return 0;
|
|
38441
|
+
return xml.getElementsByTagName("w:tbl").length;
|
|
38442
|
+
} catch {
|
|
38443
|
+
return 0;
|
|
38444
|
+
}
|
|
38445
|
+
}
|
|
38446
|
+
function isCalloutTable(node, dims) {
|
|
38447
|
+
try {
|
|
38448
|
+
const xml = node.xml;
|
|
38449
|
+
if (!xml || typeof xml.getElementsByTagName !== "function") return false;
|
|
38450
|
+
if (dims) {
|
|
38451
|
+
if (dims.rows !== 1 || dims.cols !== 1) return false;
|
|
38452
|
+
} else {
|
|
38453
|
+
const rows = Array.from(xml.childNodes).filter((n) => n.nodeType === 1 && n.tagName === "w:tr");
|
|
38454
|
+
if (rows.length !== 1) return false;
|
|
38455
|
+
const cells = Array.from(rows[0].childNodes).filter((n) => n.nodeType === 1 && n.tagName === "w:tc");
|
|
38456
|
+
if (cells.length !== 1) return false;
|
|
38457
|
+
}
|
|
38458
|
+
const fill = xml.getElementsByTagName("w:shd")[0]?.getAttribute("w:fill") ?? "";
|
|
38459
|
+
return /^[0-9A-Fa-f]{6}$/.test(fill);
|
|
38460
|
+
} catch {
|
|
38461
|
+
return false;
|
|
38462
|
+
}
|
|
38463
|
+
}
|
|
38464
|
+
function countExplicitPageBreaks(node) {
|
|
38465
|
+
if (node.type === "pageBreak") return 1;
|
|
38466
|
+
const text2 = node.metadata?.text || "";
|
|
38467
|
+
let count = text2 === "[Page Break]" ? 1 : 0;
|
|
38468
|
+
try {
|
|
38469
|
+
const xml = node.xml;
|
|
38470
|
+
if (xml && typeof xml.getElementsByTagName === "function") {
|
|
38471
|
+
const brs = xml.getElementsByTagName("w:br");
|
|
38472
|
+
let brCount = 0;
|
|
38473
|
+
for (let i = 0; i < brs.length; i++) {
|
|
38474
|
+
if (brs[i].getAttribute("w:type") === "page") brCount++;
|
|
38475
|
+
}
|
|
38476
|
+
if (brCount > count) count = brCount;
|
|
38477
|
+
}
|
|
38478
|
+
} catch {
|
|
38479
|
+
}
|
|
38480
|
+
return count;
|
|
38481
|
+
}
|
|
38482
|
+
function computeDocStats(ast) {
|
|
38483
|
+
let words = 0;
|
|
38484
|
+
let paragraphs = 0;
|
|
38485
|
+
let tables = 0;
|
|
38486
|
+
let callouts = 0;
|
|
38487
|
+
let images = 0;
|
|
38488
|
+
let pageBreaks = 0;
|
|
38489
|
+
for (const sec of ast.sections) {
|
|
38490
|
+
for (const node of sec.nodes) {
|
|
38491
|
+
if (node.type === "paragraph") paragraphs += 1;
|
|
38492
|
+
if (node.type === "table") {
|
|
38493
|
+
if (isCalloutTable(node)) callouts += 1;
|
|
38494
|
+
else tables += 1;
|
|
38495
|
+
tables += nestedTableCount(node);
|
|
38496
|
+
}
|
|
38497
|
+
if (node.type === "image") images += 1;
|
|
38498
|
+
pageBreaks += countExplicitPageBreaks(node);
|
|
38499
|
+
let text2 = node.metadata?.text ?? "";
|
|
38500
|
+
if (node.type === "table") {
|
|
38501
|
+
const spaced = tableCellTextSpaced(node);
|
|
38502
|
+
if (spaced !== void 0) text2 = spaced;
|
|
38503
|
+
}
|
|
38504
|
+
const hits = text2.match(/\S+/g);
|
|
38505
|
+
if (hits) words += hits.length;
|
|
38506
|
+
}
|
|
38507
|
+
}
|
|
38508
|
+
const pageBudget = words / 450 + (tables + callouts) * 0.45 + images * 0.5 + pageBreaks;
|
|
38509
|
+
return { words, paragraphs, pages: Math.max(1, Math.ceil(pageBudget)), tables, callouts, images };
|
|
38510
|
+
}
|
|
38511
|
+
|
|
38013
38512
|
// ../engine/src/visual/telemetry.ts
|
|
38014
38513
|
var VISUAL_TELEMETRY_TOKEN_BUDGET = 500;
|
|
38015
38514
|
var VISUAL_WIREFRAME_WIDTH = 68;
|
|
@@ -38106,17 +38605,6 @@ var WIREFRAME_TABLE_ROWS = 3;
|
|
|
38106
38605
|
function flatText(value) {
|
|
38107
38606
|
return value.replace(/\s+/g, " ").trim();
|
|
38108
38607
|
}
|
|
38109
|
-
function isCalloutTable(node, grid) {
|
|
38110
|
-
if (!grid || grid.rows !== 1 || grid.cols !== 1) return false;
|
|
38111
|
-
try {
|
|
38112
|
-
const xml = node.xml;
|
|
38113
|
-
const shd = xml?.getElementsByTagName("w:shd")[0];
|
|
38114
|
-
const fill = shd?.getAttribute("w:fill") ?? "";
|
|
38115
|
-
return /^[0-9A-Fa-f]{6}$/.test(fill);
|
|
38116
|
-
} catch {
|
|
38117
|
-
return false;
|
|
38118
|
-
}
|
|
38119
|
-
}
|
|
38120
38608
|
function tableRowLines(grid, width) {
|
|
38121
38609
|
const byRow = /* @__PURE__ */ new Map();
|
|
38122
38610
|
for (const cell of grid.cells) {
|
|
@@ -38339,7 +38827,7 @@ function generateVisualWireframe(ast, targetPage = 1, pageMap, maxNodes = 24, op
|
|
|
38339
38827
|
} else if (node.type === "table") {
|
|
38340
38828
|
lines.push(emptyLine);
|
|
38341
38829
|
const grid = tableGridOf(node);
|
|
38342
|
-
if (isCalloutTable(node, grid)) {
|
|
38830
|
+
if (isCalloutTable(node, grid ? { rows: grid.rows, cols: grid.cols } : null)) {
|
|
38343
38831
|
const note = flatText(grid?.cells[0]?.text ?? "");
|
|
38344
38832
|
const body = note.length > 48 ? note.slice(0, 45) + "..." : note;
|
|
38345
38833
|
lines.push(`| [CALLOUT] ${body}`.slice(0, W2 - 2).padEnd(W2 - 1) + "|");
|
|
@@ -38527,6 +39015,10 @@ function buildLineIndex(src, tokens) {
|
|
|
38527
39015
|
}
|
|
38528
39016
|
return out;
|
|
38529
39017
|
}
|
|
39018
|
+
var QUOTE_INDENT = QUOTE_INDENT_TWIPS;
|
|
39019
|
+
var QUOTE_LIST_INDENT = 360;
|
|
39020
|
+
var QUOTE_MAX_LEVEL = 3;
|
|
39021
|
+
var CODE_BLOCK_BASE_INDENT = 80;
|
|
38530
39022
|
function subLine(blockRaw, blockLine, excerpt) {
|
|
38531
39023
|
const found = blockRaw.indexOf(excerpt);
|
|
38532
39024
|
if (found === -1) return blockLine;
|
|
@@ -38701,12 +39193,17 @@ function compileMarkdownDetailed(markdown, options) {
|
|
|
38701
39193
|
const pendingNotes = [];
|
|
38702
39194
|
let seq = 0;
|
|
38703
39195
|
let lastInsertId = null;
|
|
39196
|
+
const idPrefix = options?.idPrefix ?? "md";
|
|
38704
39197
|
const nextId = () => {
|
|
38705
39198
|
seq += 1;
|
|
38706
|
-
return
|
|
39199
|
+
return `${idPrefix}_${String(seq).padStart(4, "0")}`;
|
|
39200
|
+
};
|
|
39201
|
+
const startAnchor = {
|
|
39202
|
+
anchor: options?.anchor ?? "end",
|
|
39203
|
+
position: options?.position ?? "after"
|
|
38707
39204
|
};
|
|
38708
39205
|
const insertAnchor = () => {
|
|
38709
|
-
if (lastInsertId === null) return {
|
|
39206
|
+
if (lastInsertId === null) return { ...startAnchor };
|
|
38710
39207
|
return { anchor: lastInsertId, position: "after" };
|
|
38711
39208
|
};
|
|
38712
39209
|
const pushInsertParagraph = (text2, spans) => {
|
|
@@ -38834,12 +39331,12 @@ function compileMarkdownDetailed(markdown, options) {
|
|
|
38834
39331
|
const inlineMime = inlineKind === "jpeg" ? "image/jpeg" : inlineKind === "svg" ? "image/svg+xml" : inlineKind === "tiff" ? "image/tiff" : `image/${inlineKind}`;
|
|
38835
39332
|
const inlineFitted = fitImageEmus(decodeImagePixels(inlineBuf, inlineKind));
|
|
38836
39333
|
const inlineId = nextId();
|
|
38837
|
-
const inlineAnchor =
|
|
39334
|
+
const inlineAnchor = insertAnchor();
|
|
38838
39335
|
ops.push({
|
|
38839
39336
|
action: "insert",
|
|
38840
39337
|
type: "image",
|
|
38841
|
-
anchor: inlineAnchor,
|
|
38842
|
-
position:
|
|
39338
|
+
anchor: inlineAnchor.anchor,
|
|
39339
|
+
position: inlineAnchor.position,
|
|
38843
39340
|
id: inlineId,
|
|
38844
39341
|
fileData: `data:${inlineMime};base64,${inlineBuf.toString("base64")}`,
|
|
38845
39342
|
...inlineFitted ? { width: inlineFitted.width, height: inlineFitted.height } : {},
|
|
@@ -38848,7 +39345,7 @@ function compileMarkdownDetailed(markdown, options) {
|
|
|
38848
39345
|
lastInsertId = inlineId;
|
|
38849
39346
|
return { id: inlineId, kind: "image" };
|
|
38850
39347
|
}
|
|
38851
|
-
if (
|
|
39348
|
+
if (hasUriScheme(target)) {
|
|
38852
39349
|
fail(line2, "image reference points outside the allowed directory", "keep referenced files inside the allowed directory and use a relative path");
|
|
38853
39350
|
}
|
|
38854
39351
|
if (allowedDir === void 0) {
|
|
@@ -38857,14 +39354,17 @@ function compileMarkdownDetailed(markdown, options) {
|
|
|
38857
39354
|
const isChart = target.toLowerCase().endsWith(CHART_JSON_SUFFIX);
|
|
38858
39355
|
const absPath = resolveAssetPath(allowedDir, target, line2, isChart ? "chart" : "image");
|
|
38859
39356
|
const id = nextId();
|
|
38860
|
-
const
|
|
39357
|
+
const chartAnchor = insertAnchor();
|
|
38861
39358
|
if (isChart) {
|
|
39359
|
+
if (chartAnchor.position === "before") {
|
|
39360
|
+
fail(line2, "a chart cannot be the first block when the compose goes before a node", "start the Markdown with a paragraph, or place the compose after the node");
|
|
39361
|
+
}
|
|
38862
39362
|
const asset2 = readChartAsset(absPath, line2, allowedDir);
|
|
38863
39363
|
const title = asset2.title ?? (alt.trim().length > 0 ? alt.trim() : "Chart");
|
|
38864
39364
|
ops.push({
|
|
38865
39365
|
action: "chart",
|
|
38866
39366
|
operation: "insert",
|
|
38867
|
-
anchor:
|
|
39367
|
+
anchor: chartAnchor.anchor,
|
|
38868
39368
|
id,
|
|
38869
39369
|
chartType: asset2.chartType,
|
|
38870
39370
|
title,
|
|
@@ -38875,7 +39375,7 @@ function compileMarkdownDetailed(markdown, options) {
|
|
|
38875
39375
|
return { id, kind: "chart" };
|
|
38876
39376
|
}
|
|
38877
39377
|
const asset = readImageAsset(absPath, line2, allowedDir);
|
|
38878
|
-
const anchor =
|
|
39378
|
+
const anchor = insertAnchor();
|
|
38879
39379
|
ops.push({
|
|
38880
39380
|
action: "insert",
|
|
38881
39381
|
type: "image",
|
|
@@ -38893,15 +39393,11 @@ function compileMarkdownDetailed(markdown, options) {
|
|
|
38893
39393
|
const walkAlert = (list) => {
|
|
38894
39394
|
for (const tok of list) {
|
|
38895
39395
|
const t = tok;
|
|
38896
|
-
if (t.type === "strong" || t.type === "em" || t.type === "codespan") {
|
|
38897
|
-
fail(line2, "rich text inside alerts is out of scope", "move bold, italic, or code into a body paragraph");
|
|
38898
|
-
}
|
|
38899
39396
|
if (t.type === "link") {
|
|
38900
39397
|
const raw = t.raw;
|
|
38901
39398
|
if (typeof raw === "string" && raw.startsWith("<")) {
|
|
38902
39399
|
fail(line2, "raw HTML is out of scope in an alert", "use plain Markdown blocks instead of HTML tags");
|
|
38903
39400
|
}
|
|
38904
|
-
fail(line2, "links inside alerts are out of scope", "move the link into a body paragraph");
|
|
38905
39401
|
}
|
|
38906
39402
|
if (t.type === "image") {
|
|
38907
39403
|
fail(line2, "images are out of scope in an alert", "put images on their own block line instead");
|
|
@@ -38919,7 +39415,7 @@ function compileMarkdownDetailed(markdown, options) {
|
|
|
38919
39415
|
if (alertTokens) walkAlert(alertTokens);
|
|
38920
39416
|
};
|
|
38921
39417
|
const handleAlertBlockquote = (token, line2) => {
|
|
38922
|
-
const inner = token.tokens ?? [];
|
|
39418
|
+
const inner = (token.tokens ?? []).filter((child) => child.type !== "space");
|
|
38923
39419
|
if (inner.length === 0) {
|
|
38924
39420
|
fail(line2, "blockquotes are out of scope", "use a plain paragraph");
|
|
38925
39421
|
}
|
|
@@ -38945,6 +39441,7 @@ ${rest}` : sameLineBody;
|
|
|
38945
39441
|
}
|
|
38946
39442
|
const restLines = rest.split("\n");
|
|
38947
39443
|
let title;
|
|
39444
|
+
let titleSegments;
|
|
38948
39445
|
let bodySrc = rest;
|
|
38949
39446
|
if (restLines.length > 0 && ALERT_TITLE_PATTERN.test(restLines[0].trim())) {
|
|
38950
39447
|
const innerTitle = (ALERT_TITLE_PATTERN.exec(restLines[0].trim())?.[1] ?? "").trim();
|
|
@@ -38967,6 +39464,22 @@ ${rest}` : sameLineBody;
|
|
|
38967
39464
|
CITATION_REF_PATTERN.lastIndex = 0;
|
|
38968
39465
|
title = innerTitle;
|
|
38969
39466
|
bodySrc = restLines.slice(1).join("\n");
|
|
39467
|
+
let titleTokens;
|
|
39468
|
+
try {
|
|
39469
|
+
titleTokens = lexMarkdown(restLines[0].trim(), { dollarMath: options?.dollarMath });
|
|
39470
|
+
} catch (err) {
|
|
39471
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
39472
|
+
throw toError("E_VALIDATION", `line ${line2}: alert title rejected (${detail})`, "keep the alert title to one line of inline text");
|
|
39473
|
+
}
|
|
39474
|
+
const titleBlocks = titleTokens.filter((t) => t.type !== "space");
|
|
39475
|
+
if (titleBlocks.length !== 1 || titleBlocks[0].type !== "paragraph") {
|
|
39476
|
+
fail(line2, `alert [!${alertType}] title must be one line of inline text`, "keep the title to one bold line");
|
|
39477
|
+
}
|
|
39478
|
+
const titleInlineTokens = titleBlocks[0].tokens;
|
|
39479
|
+
failOnSpanTokensInAlert(titleInlineTokens, line2);
|
|
39480
|
+
const parsedTitle = parseInlineTokens(titleInlineTokens, line2, "an alert title", citationDefs, citationOrder, citationNum, usedCitations);
|
|
39481
|
+
title = parsedTitle.plainText.length > 0 ? parsedTitle.plainText : innerTitle;
|
|
39482
|
+
titleSegments = parsedTitle.segments;
|
|
38970
39483
|
}
|
|
38971
39484
|
for (const bodyLine of bodySrc.split("\n")) {
|
|
38972
39485
|
if (CONTAINER_FENCE_PATTERN.test(bodyLine)) {
|
|
@@ -38987,6 +39500,7 @@ ${rest}` : sameLineBody;
|
|
|
38987
39500
|
throw toError("E_VALIDATION", `line ${line2}: alert body rejected (${detail})`, "keep alert bodies plain text");
|
|
38988
39501
|
}
|
|
38989
39502
|
const cleanParas = [];
|
|
39503
|
+
const bodySegments = [];
|
|
38990
39504
|
for (const bodyToken of bodyTokens) {
|
|
38991
39505
|
if (bodyToken.type === "space") continue;
|
|
38992
39506
|
if (bodyToken.type !== "paragraph") {
|
|
@@ -39002,31 +39516,214 @@ ${rest}` : sameLineBody;
|
|
|
39002
39516
|
FOOTNOTE_REF_PATTERN.lastIndex = 0;
|
|
39003
39517
|
failOnImageOrHtml(paraText2, paraLine, "an alert");
|
|
39004
39518
|
failOnSpanTokensInAlert(para.tokens, paraLine);
|
|
39005
|
-
|
|
39519
|
+
const parsedPara = parseInlineTokens(para.tokens, paraLine, "an alert", citationDefs, citationOrder, citationNum, usedCitations);
|
|
39520
|
+
if (parsedPara.footnoteRefs.length > 0) {
|
|
39521
|
+
fail(paraLine, "footnote markers inside alerts are out of scope", "move the note reference into a body paragraph");
|
|
39522
|
+
}
|
|
39523
|
+
for (const key of parsedPara.citationKeys) usedCitations.add(key);
|
|
39524
|
+
if (cleanParas.length > 0) bodySegments.push({ text: " ", bold: false, italic: false, code: false });
|
|
39525
|
+
bodySegments.push(...parsedPara.segments);
|
|
39526
|
+
cleanParas.push(parsedPara.plainText);
|
|
39006
39527
|
}
|
|
39007
39528
|
const text2 = plainJoin(cleanParas.join(" "));
|
|
39008
39529
|
if (text2.length === 0) {
|
|
39009
39530
|
fail(line2, `alert [!${alertType}] needs body text`, "write the alert text on > lines after the marker");
|
|
39010
39531
|
}
|
|
39011
|
-
const
|
|
39532
|
+
const calloutAnchor = insertAnchor();
|
|
39012
39533
|
const alertTag = alertType.toLowerCase();
|
|
39013
39534
|
const tagThemeRaw = styleGuide?.callouts?.tagThemes?.[alertTag];
|
|
39014
39535
|
const tagTheme = typeof tagThemeRaw === "string" && tagThemeRaw.length > 0 ? tagThemeRaw : void 0;
|
|
39015
39536
|
const guideThemeRaw = styleGuide?.callouts?.defaultTheme;
|
|
39016
39537
|
const guideTheme = typeof guideThemeRaw === "string" && guideThemeRaw.length > 0 ? guideThemeRaw : void 0;
|
|
39538
|
+
const calloutId = nextId();
|
|
39017
39539
|
const op = {
|
|
39018
39540
|
action: "callout",
|
|
39019
|
-
|
|
39020
|
-
|
|
39541
|
+
id: calloutId,
|
|
39542
|
+
anchor: calloutAnchor.anchor,
|
|
39543
|
+
position: calloutAnchor.position,
|
|
39021
39544
|
text: text2,
|
|
39022
39545
|
theme: tagTheme ?? guideTheme ?? ALERT_TYPE_TO_THEME[alertType] ?? "indigo"
|
|
39023
39546
|
};
|
|
39024
39547
|
if (title !== void 0) op["title"] = title;
|
|
39548
|
+
const spanParams = {};
|
|
39549
|
+
if (titleSegments !== void 0 && segmentsNeedSplit(titleSegments)) spanParams["mdTitleSpans"] = titleSegments;
|
|
39550
|
+
if (segmentsNeedSplit(bodySegments)) spanParams["mdBodySpans"] = bodySegments;
|
|
39551
|
+
if (Object.keys(spanParams).length > 0) op["params"] = spanParams;
|
|
39025
39552
|
ops.push(op);
|
|
39026
|
-
lastInsertId =
|
|
39553
|
+
lastInsertId = calloutId;
|
|
39027
39554
|
};
|
|
39028
39555
|
let underTitle = false;
|
|
39029
39556
|
let lastBlockKind = "none";
|
|
39557
|
+
const compileList = (token, line2, quoteLevel = 0) => {
|
|
39558
|
+
const listType = token.ordered === true ? "numbered" : "bullet";
|
|
39559
|
+
const items = token.items ?? [];
|
|
39560
|
+
if (items.length === 0) {
|
|
39561
|
+
fail(line2, "empty list block", "add at least one list item");
|
|
39562
|
+
}
|
|
39563
|
+
const itemIds = [];
|
|
39564
|
+
const itemBaseLine = line2;
|
|
39565
|
+
for (const item of items) {
|
|
39566
|
+
const itemLine = subLine(token.raw, itemBaseLine, item.raw);
|
|
39567
|
+
if (item.task === true) {
|
|
39568
|
+
fail(itemLine, "task-list items are out of scope", "use a plain bullet or numbered item");
|
|
39569
|
+
}
|
|
39570
|
+
for (const child of item.tokens ?? []) {
|
|
39571
|
+
if (child.type === "list") {
|
|
39572
|
+
const nestedRaw = child.raw;
|
|
39573
|
+
const nestedLine = typeof nestedRaw === "string" && nestedRaw ? subLine(token.raw, itemBaseLine, nestedRaw) : itemLine;
|
|
39574
|
+
fail(nestedLine, "nested lists are out of scope", "flatten nested items into the top-level list");
|
|
39575
|
+
}
|
|
39576
|
+
if (child.type !== "text" && child.type !== "paragraph" && child.type !== "space") {
|
|
39577
|
+
fail(itemLine, `list items carry ${child.type} blocks, out of scope`, "keep list items plain text");
|
|
39578
|
+
}
|
|
39579
|
+
}
|
|
39580
|
+
if (item.text.split("\n").some((entry) => FOOTNOTE_DEF_PATTERN.test(entry))) {
|
|
39581
|
+
fail(itemLine, "footnote definition must stand alone as its own block", "move the definition out of the list");
|
|
39582
|
+
}
|
|
39583
|
+
if (item.text.split("\n").some((entry) => CITATION_DEF_PATTERN.test(entry))) {
|
|
39584
|
+
fail(itemLine, "citation definition must stand alone as its own block", "move the definition out of the list");
|
|
39585
|
+
}
|
|
39586
|
+
failOnImageOrHtml(item.text, itemLine, "a list item");
|
|
39587
|
+
const firstChild = (item.tokens ?? [])[0];
|
|
39588
|
+
const inlineTokens = firstChild?.tokens ?? [];
|
|
39589
|
+
if (inlineTokens.length === 1 && inlineTokens[0].type === "image") {
|
|
39590
|
+
fail(itemLine, "images are out of scope in a list item", "put images on their own block line instead of inside a list");
|
|
39591
|
+
}
|
|
39592
|
+
for (const it of inlineTokens) {
|
|
39593
|
+
if (it.type === "image") {
|
|
39594
|
+
fail(itemLine, "images are out of scope in a list item", "put images on their own block line instead of inside a list");
|
|
39595
|
+
}
|
|
39596
|
+
}
|
|
39597
|
+
const inline = parseInlineTokens(inlineTokens, itemLine, "a list item", citationDefs, citationOrder, citationNum, usedCitations);
|
|
39598
|
+
for (const key of inline.citationKeys) usedCitations.add(key);
|
|
39599
|
+
for (const label of inline.footnoteRefs) {
|
|
39600
|
+
if (!defs.has(label)) {
|
|
39601
|
+
fail(itemLine, `undefined footnote reference [^${label}]`, "add a matching [^label]: body definition");
|
|
39602
|
+
}
|
|
39603
|
+
}
|
|
39604
|
+
const id = pushInsertParagraph(inline.plainText, inline.segments);
|
|
39605
|
+
trackRefs(id, inline.footnoteEntries ?? inline.footnoteRefs, itemLine);
|
|
39606
|
+
itemIds.push(id);
|
|
39607
|
+
}
|
|
39608
|
+
ops.push({ action: "list", operation: "create", target: itemIds, listType });
|
|
39609
|
+
if (lastBlockKind === "table" && itemIds.length > 0) {
|
|
39610
|
+
ops.push({ action: "style", target: itemIds[0], spacingBefore: 240 });
|
|
39611
|
+
}
|
|
39612
|
+
if (quoteLevel > 0) {
|
|
39613
|
+
for (const itemId of itemIds) {
|
|
39614
|
+
ops.push({ action: "namedStyle", operation: "apply", styleName: "Quote", target: itemId });
|
|
39615
|
+
ops.push({ action: "style", target: itemId, indentLeft: quoteLevel * QUOTE_INDENT + QUOTE_LIST_INDENT, indentHanging: QUOTE_LIST_INDENT });
|
|
39616
|
+
}
|
|
39617
|
+
}
|
|
39618
|
+
};
|
|
39619
|
+
const compileCode = (token, line2, quoteLevel = 0) => {
|
|
39620
|
+
const lang = (token.lang ?? "").trim();
|
|
39621
|
+
if (CHART_FENCE_LANGUAGES.has(lang.toLowerCase())) {
|
|
39622
|
+
fail(line2, `fenced code tagged '${lang}' requests a chart`, "describe the chart with a chart path reference instead");
|
|
39623
|
+
}
|
|
39624
|
+
const id = nextId();
|
|
39625
|
+
const anchor = insertAnchor();
|
|
39626
|
+
ops.push({
|
|
39627
|
+
action: "insert",
|
|
39628
|
+
type: "paragraph",
|
|
39629
|
+
anchor: anchor.anchor,
|
|
39630
|
+
position: anchor.position,
|
|
39631
|
+
text: token.text ?? "",
|
|
39632
|
+
id,
|
|
39633
|
+
params: { codeBlock: true, ...lang ? { codeLanguage: lang } : {} }
|
|
39634
|
+
});
|
|
39635
|
+
lastInsertId = id;
|
|
39636
|
+
if (lastBlockKind === "table") {
|
|
39637
|
+
ops.push({ action: "style", target: id, spacingBefore: 240 });
|
|
39638
|
+
}
|
|
39639
|
+
if (quoteLevel > 0) {
|
|
39640
|
+
ops.push({ action: "style", target: id, indentLeft: quoteLevel * QUOTE_INDENT + CODE_BLOCK_BASE_INDENT });
|
|
39641
|
+
}
|
|
39642
|
+
};
|
|
39643
|
+
const compileQuote = (token, line2, level) => {
|
|
39644
|
+
const effective = Math.min(level, QUOTE_MAX_LEVEL);
|
|
39645
|
+
if (level > QUOTE_MAX_LEVEL) {
|
|
39646
|
+
warnings.push(`line ${line2}: quote nesting deeper than ${QUOTE_MAX_LEVEL} levels was written at level ${QUOTE_MAX_LEVEL}.`);
|
|
39647
|
+
}
|
|
39648
|
+
const children2 = (token.tokens ?? []).filter((child) => child.type !== "space");
|
|
39649
|
+
if (children2.length === 0) {
|
|
39650
|
+
fail(line2, "blockquote is empty", "write the quote text after the > marker");
|
|
39651
|
+
}
|
|
39652
|
+
const strippedRaw = token.raw.split("\n").map((entry) => entry.replace(/^ {0,3}>[ ]?/, "")).join("\n");
|
|
39653
|
+
for (const child of children2) {
|
|
39654
|
+
const childRaw = child.raw ?? "";
|
|
39655
|
+
const childLine = subLine(strippedRaw, line2, childRaw.split("\n")[0] ?? "");
|
|
39656
|
+
switch (child.type) {
|
|
39657
|
+
case "paragraph": {
|
|
39658
|
+
const para = child;
|
|
39659
|
+
const paraText2 = para.text ?? "";
|
|
39660
|
+
const paraLines = childRaw.replace(/\n+$/, "").split("\n");
|
|
39661
|
+
if (paraLines.some((entry) => FOOTNOTE_DEF_PATTERN.test(entry) || CITATION_DEF_PATTERN.test(entry))) {
|
|
39662
|
+
fail(childLine, "note and citation definitions must stand alone as their own block", "move the definition out of the quote");
|
|
39663
|
+
}
|
|
39664
|
+
if (CONTAINER_FENCE_PATTERN.test(paraLines[0] ?? "")) {
|
|
39665
|
+
fail(childLine, "container fences (:::) are out of scope as custom styles", "use plain Markdown blocks");
|
|
39666
|
+
}
|
|
39667
|
+
const paraTokens = para.tokens ?? [];
|
|
39668
|
+
for (const it of paraTokens) {
|
|
39669
|
+
if (it.type === "image") {
|
|
39670
|
+
fail(childLine, "images are out of scope in a quote", "put images on their own block line after the quote");
|
|
39671
|
+
}
|
|
39672
|
+
}
|
|
39673
|
+
failOnImageOrHtml(paraText2, childLine, "a quote");
|
|
39674
|
+
const inline = parseInlineTokens(para.tokens, childLine, "a quote", citationDefs, citationOrder, citationNum, usedCitations);
|
|
39675
|
+
for (const key of inline.citationKeys) usedCitations.add(key);
|
|
39676
|
+
for (const label of inline.footnoteRefs) {
|
|
39677
|
+
if (!defs.has(label)) {
|
|
39678
|
+
fail(childLine, `undefined footnote reference [^${label}]`, "add a matching [^label]: body definition");
|
|
39679
|
+
}
|
|
39680
|
+
}
|
|
39681
|
+
const id = pushInsertParagraph(inline.plainText, inline.segments);
|
|
39682
|
+
trackRefs(id, inline.footnoteEntries ?? inline.footnoteRefs, childLine);
|
|
39683
|
+
ops.push({ action: "namedStyle", operation: "apply", styleName: "Quote", target: id });
|
|
39684
|
+
if (effective > 1) {
|
|
39685
|
+
ops.push({ action: "style", target: id, indentLeft: effective * QUOTE_INDENT });
|
|
39686
|
+
}
|
|
39687
|
+
if (lastBlockKind === "table") {
|
|
39688
|
+
ops.push({ action: "style", target: id, spacingBefore: 240 });
|
|
39689
|
+
}
|
|
39690
|
+
lastBlockKind = "quote";
|
|
39691
|
+
underTitle = false;
|
|
39692
|
+
break;
|
|
39693
|
+
}
|
|
39694
|
+
case "blockquote":
|
|
39695
|
+
compileQuote(child, childLine, level + 1);
|
|
39696
|
+
break;
|
|
39697
|
+
case "list":
|
|
39698
|
+
compileList(child, childLine, effective);
|
|
39699
|
+
lastBlockKind = "list";
|
|
39700
|
+
break;
|
|
39701
|
+
case "code":
|
|
39702
|
+
compileCode(child, childLine, effective);
|
|
39703
|
+
lastBlockKind = "code";
|
|
39704
|
+
break;
|
|
39705
|
+
case "table":
|
|
39706
|
+
fail(childLine, "tables inside a quote are out of scope", "Word cannot indent a table as a quote: put the table after the quote");
|
|
39707
|
+
break;
|
|
39708
|
+
case "heading":
|
|
39709
|
+
fail(childLine, "headings inside a quote are out of scope", "put the heading before the quote");
|
|
39710
|
+
break;
|
|
39711
|
+
default:
|
|
39712
|
+
fail(childLine, `${child.type} blocks inside a quote are out of scope`, "keep quotes to paragraphs, flat lists and code blocks");
|
|
39713
|
+
}
|
|
39714
|
+
}
|
|
39715
|
+
};
|
|
39716
|
+
const quoteOpensWithAlertMarker = (token, line2) => {
|
|
39717
|
+
const first = (token.tokens ?? []).find((child) => child.type !== "space");
|
|
39718
|
+
if (!first || first.type !== "paragraph") return false;
|
|
39719
|
+
const head = (first.text ?? "").split("\n")[0]?.trim() ?? "";
|
|
39720
|
+
if (ALERT_MARKER_PATTERN.test(head)) return true;
|
|
39721
|
+
const unknown = /^\[!([A-Za-z][A-Za-z0-9_-]*)\]/.exec(head);
|
|
39722
|
+
if (unknown) {
|
|
39723
|
+
fail(line2, `unknown alert type [!${unknown[1]}]`, "use NOTE, TIP, IMPORTANT, WARNING or CAUTION, or drop the [!...] marker for a plain quote");
|
|
39724
|
+
}
|
|
39725
|
+
return false;
|
|
39726
|
+
};
|
|
39030
39727
|
for (const rawToken of tokens) {
|
|
39031
39728
|
const token = rawToken;
|
|
39032
39729
|
const line2 = lineOf.get(rawToken) ?? 1;
|
|
@@ -39136,60 +39833,7 @@ ${rest}` : sameLineBody;
|
|
|
39136
39833
|
break;
|
|
39137
39834
|
}
|
|
39138
39835
|
case "list": {
|
|
39139
|
-
|
|
39140
|
-
const items = token.items ?? [];
|
|
39141
|
-
if (items.length === 0) {
|
|
39142
|
-
fail(line2, "empty list block", "add at least one list item");
|
|
39143
|
-
}
|
|
39144
|
-
const itemIds = [];
|
|
39145
|
-
const itemBaseLine = line2;
|
|
39146
|
-
for (const item of items) {
|
|
39147
|
-
const itemLine = subLine(token.raw, itemBaseLine, item.raw);
|
|
39148
|
-
if (item.task === true) {
|
|
39149
|
-
fail(itemLine, "task-list items are out of scope", "use a plain bullet or numbered item");
|
|
39150
|
-
}
|
|
39151
|
-
for (const child of item.tokens ?? []) {
|
|
39152
|
-
if (child.type === "list") {
|
|
39153
|
-
const nestedRaw = child.raw;
|
|
39154
|
-
const nestedLine = typeof nestedRaw === "string" && nestedRaw ? subLine(token.raw, itemBaseLine, nestedRaw) : itemLine;
|
|
39155
|
-
fail(nestedLine, "nested lists are out of scope", "flatten nested items into the top-level list");
|
|
39156
|
-
}
|
|
39157
|
-
if (child.type !== "text" && child.type !== "paragraph" && child.type !== "space") {
|
|
39158
|
-
fail(itemLine, `list items carry ${child.type} blocks, out of scope`, "keep list items plain text");
|
|
39159
|
-
}
|
|
39160
|
-
}
|
|
39161
|
-
if (item.text.split("\n").some((entry) => FOOTNOTE_DEF_PATTERN.test(entry))) {
|
|
39162
|
-
fail(itemLine, "footnote definition must stand alone as its own block", "move the definition out of the list");
|
|
39163
|
-
}
|
|
39164
|
-
if (item.text.split("\n").some((entry) => CITATION_DEF_PATTERN.test(entry))) {
|
|
39165
|
-
fail(itemLine, "citation definition must stand alone as its own block", "move the definition out of the list");
|
|
39166
|
-
}
|
|
39167
|
-
failOnImageOrHtml(item.text, itemLine, "a list item");
|
|
39168
|
-
const firstChild = (item.tokens ?? [])[0];
|
|
39169
|
-
const inlineTokens = firstChild?.tokens ?? [];
|
|
39170
|
-
if (inlineTokens.length === 1 && inlineTokens[0].type === "image") {
|
|
39171
|
-
fail(itemLine, "images are out of scope in a list item", "put images on their own block line instead of inside a list");
|
|
39172
|
-
}
|
|
39173
|
-
for (const it of inlineTokens) {
|
|
39174
|
-
if (it.type === "image") {
|
|
39175
|
-
fail(itemLine, "images are out of scope in a list item", "put images on their own block line instead of inside a list");
|
|
39176
|
-
}
|
|
39177
|
-
}
|
|
39178
|
-
const inline = parseInlineTokens(inlineTokens, itemLine, "a list item", citationDefs, citationOrder, citationNum, usedCitations);
|
|
39179
|
-
for (const key of inline.citationKeys) usedCitations.add(key);
|
|
39180
|
-
for (const label of inline.footnoteRefs) {
|
|
39181
|
-
if (!defs.has(label)) {
|
|
39182
|
-
fail(itemLine, `undefined footnote reference [^${label}]`, "add a matching [^label]: body definition");
|
|
39183
|
-
}
|
|
39184
|
-
}
|
|
39185
|
-
const id = pushInsertParagraph(inline.plainText, inline.segments);
|
|
39186
|
-
trackRefs(id, inline.footnoteEntries ?? inline.footnoteRefs, itemLine);
|
|
39187
|
-
itemIds.push(id);
|
|
39188
|
-
}
|
|
39189
|
-
ops.push({ action: "list", operation: "create", target: itemIds, listType });
|
|
39190
|
-
if (lastBlockKind === "table" && itemIds.length > 0) {
|
|
39191
|
-
ops.push({ action: "style", target: itemIds[0], spacingBefore: 240 });
|
|
39192
|
-
}
|
|
39836
|
+
compileList(token, line2);
|
|
39193
39837
|
lastBlockKind = "list";
|
|
39194
39838
|
break;
|
|
39195
39839
|
}
|
|
@@ -39302,25 +39946,7 @@ ${rest}` : sameLineBody;
|
|
|
39302
39946
|
break;
|
|
39303
39947
|
}
|
|
39304
39948
|
case "code": {
|
|
39305
|
-
|
|
39306
|
-
if (CHART_FENCE_LANGUAGES.has(lang.toLowerCase())) {
|
|
39307
|
-
fail(line2, `fenced code tagged '${lang}' requests a chart`, "describe the chart with a chart path reference instead");
|
|
39308
|
-
}
|
|
39309
|
-
const id = nextId();
|
|
39310
|
-
const anchor = insertAnchor();
|
|
39311
|
-
ops.push({
|
|
39312
|
-
action: "insert",
|
|
39313
|
-
type: "paragraph",
|
|
39314
|
-
anchor: anchor.anchor,
|
|
39315
|
-
position: anchor.position,
|
|
39316
|
-
text: token.text ?? "",
|
|
39317
|
-
id,
|
|
39318
|
-
params: { codeBlock: true, ...lang ? { codeLanguage: lang } : {} }
|
|
39319
|
-
});
|
|
39320
|
-
lastInsertId = id;
|
|
39321
|
-
if (lastBlockKind === "table") {
|
|
39322
|
-
ops.push({ action: "style", target: id, spacingBefore: 240 });
|
|
39323
|
-
}
|
|
39949
|
+
compileCode(token, line2);
|
|
39324
39950
|
lastBlockKind = "code";
|
|
39325
39951
|
break;
|
|
39326
39952
|
}
|
|
@@ -39339,8 +39965,12 @@ ${rest}` : sameLineBody;
|
|
|
39339
39965
|
fail(line2, "raw HTML blocks are out of scope", "use plain Markdown blocks instead of HTML tags");
|
|
39340
39966
|
break;
|
|
39341
39967
|
case "blockquote":
|
|
39342
|
-
|
|
39343
|
-
|
|
39968
|
+
if (quoteOpensWithAlertMarker(token, line2)) {
|
|
39969
|
+
handleAlertBlockquote(token, line2);
|
|
39970
|
+
lastBlockKind = "callout";
|
|
39971
|
+
} else {
|
|
39972
|
+
compileQuote(token, line2, 1);
|
|
39973
|
+
}
|
|
39344
39974
|
break;
|
|
39345
39975
|
case "image": {
|
|
39346
39976
|
const img = token;
|
|
@@ -39424,107 +40054,15 @@ ${rest}` : sameLineBody;
|
|
|
39424
40054
|
}
|
|
39425
40055
|
|
|
39426
40056
|
// ../engine/src/compose/markdown.ts
|
|
39427
|
-
function
|
|
39428
|
-
|
|
39429
|
-
|
|
39430
|
-
|
|
39431
|
-
|
|
39432
|
-
|
|
39433
|
-
|
|
39434
|
-
|
|
39435
|
-
|
|
39436
|
-
if (![1, 2, 4, 5, 6].includes(nodes.length)) return null;
|
|
39437
|
-
if (summary.hasTrackedChanges === true) return null;
|
|
39438
|
-
if (Object.keys(summary.mediaFiles ?? {}).length > 0) return null;
|
|
39439
|
-
if ((summary.headers ?? []).length > 0) return null;
|
|
39440
|
-
if ((summary.footers ?? []).length > 0) return null;
|
|
39441
|
-
if ((summary.charts ?? []).length > 0) return null;
|
|
39442
|
-
try {
|
|
39443
|
-
const zip = parser2.getZipAdapter?.();
|
|
39444
|
-
if (typeof zip?.hasFile === "function") {
|
|
39445
|
-
for (const part of [
|
|
39446
|
-
"word/footnotes.xml",
|
|
39447
|
-
"word/endnotes.xml",
|
|
39448
|
-
"word/comments.xml",
|
|
39449
|
-
"word/commentsExtended.xml"
|
|
39450
|
-
]) {
|
|
39451
|
-
try {
|
|
39452
|
-
if (zip.hasFile(part)) return null;
|
|
39453
|
-
} catch {
|
|
39454
|
-
return null;
|
|
39455
|
-
}
|
|
39456
|
-
}
|
|
39457
|
-
} else {
|
|
39458
|
-
return null;
|
|
39459
|
-
}
|
|
39460
|
-
} catch {
|
|
39461
|
-
return null;
|
|
39462
|
-
}
|
|
39463
|
-
for (const n of nodes) {
|
|
39464
|
-
if (n.hasTrackedChanges === true) return null;
|
|
39465
|
-
const raw = n.rawXml ?? "";
|
|
39466
|
-
if (raw.includes("w:footnoteReference") || raw.includes("w:endnoteReference") || raw.includes("w:commentReference") || raw.includes("w:commentRangeStart") || raw.includes("w:commentRangeEnd")) {
|
|
39467
|
-
return null;
|
|
39468
|
-
}
|
|
39469
|
-
}
|
|
39470
|
-
const ids = nodes.map((n) => n.id ?? "").filter((id) => id.length > 0);
|
|
39471
|
-
if (ids.length !== nodes.length) return null;
|
|
39472
|
-
if (nodes.length === 1) {
|
|
39473
|
-
const node = nodes[0];
|
|
39474
|
-
if (node.type !== "paragraph") return null;
|
|
39475
|
-
if ((node.text ?? "").trim() !== "") return null;
|
|
39476
|
-
return ids;
|
|
39477
|
-
}
|
|
39478
|
-
if (nodes.length === 2) {
|
|
39479
|
-
const [first, second] = nodes;
|
|
39480
|
-
const t0 = (first.text ?? "").trim();
|
|
39481
|
-
const t1 = (second.text ?? "").trim();
|
|
39482
|
-
if (t0 === BLANK_SEED_TITLE && t1 === BLANK_SEED_HINT) {
|
|
39483
|
-
const firstRaw = first.rawXml ?? "";
|
|
39484
|
-
const secondRaw = second.rawXml ?? "";
|
|
39485
|
-
if (!firstRaw.includes('w:val="Title"') || !secondRaw.includes('w:val="Subtitle"')) return null;
|
|
39486
|
-
return ids;
|
|
39487
|
-
}
|
|
39488
|
-
const tmpl = requestedTemplate?.toLowerCase();
|
|
39489
|
-
if (t0 === "MEMORANDUM" && tmpl === "memo") return ids;
|
|
39490
|
-
if (t0 === "Letter" && tmpl === "letter") return ids;
|
|
39491
|
-
if (t0 === "Proposal" && tmpl === "proposal") return ids;
|
|
39492
|
-
if (t0 === "INVOICE" && tmpl === "invoice") return ids;
|
|
39493
|
-
return null;
|
|
39494
|
-
}
|
|
39495
|
-
if (nodes.length === 4) {
|
|
39496
|
-
const t0 = (nodes[0].text ?? "").trim();
|
|
39497
|
-
const tmpl = requestedTemplate?.toLowerCase();
|
|
39498
|
-
if (t0 === "Research Benchmark Report" && tmpl === "research") return ids;
|
|
39499
|
-
if (t0 === "Corporate Strategy Roadmap" && tmpl === "corporate") return ids;
|
|
39500
|
-
if (t0 === "Velocity Audit & Performance" && tmpl === "bar-chart-3d") return ids;
|
|
39501
|
-
return null;
|
|
39502
|
-
}
|
|
39503
|
-
if (nodes.length === 5) {
|
|
39504
|
-
const t0 = (nodes[0].text ?? "").trim();
|
|
39505
|
-
const tmpl = requestedTemplate?.toLowerCase();
|
|
39506
|
-
if (t0 === "Financial Report & Audit" && tmpl === "financial") return ids;
|
|
39507
|
-
return null;
|
|
39508
|
-
}
|
|
39509
|
-
if (nodes.length === 6) {
|
|
39510
|
-
const t0 = (nodes[0].text ?? "").trim();
|
|
39511
|
-
const t2 = (nodes[2].text ?? "").trim();
|
|
39512
|
-
const tmpl = requestedTemplate?.toLowerCase();
|
|
39513
|
-
if (t0 === "Q3 Executive Financial Performance Report" && t2 === "1. Executive Summary") {
|
|
39514
|
-
if (tmpl === "report") return ids;
|
|
39515
|
-
return null;
|
|
39516
|
-
}
|
|
39517
|
-
if (t0 === "Manuscript Title" && t2 === "Chapter 1: The Beginning" && tmpl === "novel") {
|
|
39518
|
-
return ids;
|
|
39519
|
-
}
|
|
39520
|
-
if (t0 === "Neural Document Architectures: A Comparative Benchmark" && tmpl === "academic") {
|
|
39521
|
-
return ids;
|
|
39522
|
-
}
|
|
39523
|
-
if (t0 === "TECH HORIZONS" && tmpl === "newsletter") return ids;
|
|
39524
|
-
if (t0 === "Platform Core API Reference" && (tmpl === "api" || tmpl === "api-spec")) return ids;
|
|
39525
|
-
return null;
|
|
39526
|
-
}
|
|
39527
|
-
return null;
|
|
40057
|
+
function nextComposeIdPrefix(ast) {
|
|
40058
|
+
const live = /* @__PURE__ */ new Set();
|
|
40059
|
+
for (const section of ast.sections ?? []) for (const node of section.nodes ?? []) live.add(node.id);
|
|
40060
|
+
const used = (prefix) => {
|
|
40061
|
+
for (const id of live) if (id.startsWith(`${prefix}_`)) return true;
|
|
40062
|
+
return false;
|
|
40063
|
+
};
|
|
40064
|
+
if (!used("md")) return "md";
|
|
40065
|
+
for (let n = 2; ; n++) if (!used(`md${n}`)) return `md${n}`;
|
|
39528
40066
|
}
|
|
39529
40067
|
var pristineBlankSeedIds = pristineTemplateSeedIds;
|
|
39530
40068
|
function assertTemplateSeed(parser2, template) {
|
|
@@ -39568,15 +40106,26 @@ function assertTemplateSeed(parser2, template) {
|
|
|
39568
40106
|
function applyInlineSpans(parser2, ast, operations) {
|
|
39569
40107
|
const docParser = parser2;
|
|
39570
40108
|
const liveAst = ast;
|
|
40109
|
+
const targets = [];
|
|
39571
40110
|
for (const op of operations) {
|
|
39572
40111
|
const rec = op;
|
|
39573
|
-
if (rec["action"] !== "insert" || rec["type"] !== "paragraph") continue;
|
|
39574
40112
|
const params = rec["params"];
|
|
39575
|
-
const rawSpans = params?.["mdSpans"];
|
|
39576
|
-
if (!Array.isArray(rawSpans) || !segmentsNeedSplit(rawSpans)) continue;
|
|
39577
|
-
const spans = rawSpans;
|
|
39578
40113
|
const id = String(rec["id"] ?? "");
|
|
39579
40114
|
if (!id) continue;
|
|
40115
|
+
if (rec["action"] === "callout") {
|
|
40116
|
+
const titleSpans = params?.["mdTitleSpans"];
|
|
40117
|
+
const bodySpans = params?.["mdBodySpans"];
|
|
40118
|
+
if (Array.isArray(titleSpans) && segmentsNeedSplit(titleSpans)) targets.push({ id, spans: titleSpans, part: "calloutTitle" });
|
|
40119
|
+
if (Array.isArray(bodySpans) && segmentsNeedSplit(bodySpans)) targets.push({ id, spans: bodySpans, part: "calloutBody" });
|
|
40120
|
+
continue;
|
|
40121
|
+
}
|
|
40122
|
+
if (rec["action"] !== "insert" || rec["type"] !== "paragraph") continue;
|
|
40123
|
+
const rawSpans = params?.["mdSpans"];
|
|
40124
|
+
if (!Array.isArray(rawSpans) || !segmentsNeedSplit(rawSpans)) continue;
|
|
40125
|
+
targets.push({ id, spans: rawSpans, part: "paragraph" });
|
|
40126
|
+
}
|
|
40127
|
+
for (const target of targets) {
|
|
40128
|
+
const { id, spans, part } = target;
|
|
39580
40129
|
let node;
|
|
39581
40130
|
for (const section of liveAst.sections) {
|
|
39582
40131
|
const found = section.nodes.find((n) => n.id === id);
|
|
@@ -39590,7 +40139,18 @@ function applyInlineSpans(parser2, ast, operations) {
|
|
|
39590
40139
|
`E_VALIDATION: line 1: span target paragraph '${id}' is missing after compose. Remedy: retry the compose without manual edits in between.`
|
|
39591
40140
|
);
|
|
39592
40141
|
}
|
|
39593
|
-
|
|
40142
|
+
let para = node.xml;
|
|
40143
|
+
let baseRPr;
|
|
40144
|
+
if (part !== "paragraph") {
|
|
40145
|
+
const cellParas = Array.from(para.getElementsByTagName("w:p"));
|
|
40146
|
+
const hasTitle = cellParas.length > 1;
|
|
40147
|
+
const picked = part === "calloutTitle" ? hasTitle ? cellParas[0] : void 0 : cellParas[cellParas.length - 1];
|
|
40148
|
+
if (!picked) continue;
|
|
40149
|
+
para = picked;
|
|
40150
|
+
const firstRun = Array.from(para.childNodes).find((k) => k.nodeType === 1 && k.tagName === "w:r");
|
|
40151
|
+
const rPrEl = firstRun ? Array.from(firstRun.childNodes).find((k) => k.nodeType === 1 && k.tagName === "w:rPr") : void 0;
|
|
40152
|
+
baseRPr = rPrEl ? rPrEl : void 0;
|
|
40153
|
+
}
|
|
39594
40154
|
const doc = para.ownerDocument;
|
|
39595
40155
|
if (!doc) continue;
|
|
39596
40156
|
const keptFootRefs = [];
|
|
@@ -39622,10 +40182,12 @@ function applyInlineSpans(parser2, ast, operations) {
|
|
|
39622
40182
|
}
|
|
39623
40183
|
const buildRun = (seg) => {
|
|
39624
40184
|
const run = createWordElement(doc, "w:r");
|
|
39625
|
-
const needsRPr = seg.bold || seg.italic || seg.strike || seg.code || seg.linkUrl !== void 0;
|
|
40185
|
+
const needsRPr = seg.bold || seg.italic || seg.strike || seg.code || seg.linkUrl !== void 0 || baseRPr !== void 0;
|
|
39626
40186
|
if (needsRPr) {
|
|
39627
40187
|
const rPr = createWordElement(doc, "w:rPr");
|
|
39628
|
-
|
|
40188
|
+
const baseBold = baseRPr !== void 0 && baseRPr.getElementsByTagName("w:b").length > 0;
|
|
40189
|
+
const baseColor = baseRPr?.getElementsByTagName("w:color")[0]?.getAttribute("w:val") ?? void 0;
|
|
40190
|
+
if (seg.bold || baseBold) rPr.appendChild(createWordElement(doc, "w:b"));
|
|
39629
40191
|
if (seg.italic) rPr.appendChild(createWordElement(doc, "w:i"));
|
|
39630
40192
|
if (seg.strike) rPr.appendChild(createWordElement(doc, "w:strike"));
|
|
39631
40193
|
if (seg.code) {
|
|
@@ -39634,6 +40196,11 @@ function applyInlineSpans(parser2, ast, operations) {
|
|
|
39634
40196
|
rFonts.setAttribute("w:hAnsi", COMPOSE_CODE_FONT);
|
|
39635
40197
|
rPr.appendChild(rFonts);
|
|
39636
40198
|
}
|
|
40199
|
+
if (seg.linkUrl === void 0 && baseColor) {
|
|
40200
|
+
const color = createWordElement(doc, "w:color");
|
|
40201
|
+
color.setAttribute("w:val", baseColor);
|
|
40202
|
+
rPr.appendChild(color);
|
|
40203
|
+
}
|
|
39637
40204
|
if (seg.linkUrl !== void 0) {
|
|
39638
40205
|
const color = createWordElement(doc, "w:color");
|
|
39639
40206
|
color.setAttribute("w:val", "0066CC");
|
|
@@ -39719,35 +40286,76 @@ function applyInlineSpans(parser2, ast, operations) {
|
|
|
39719
40286
|
);
|
|
39720
40287
|
for (let i = 0; i < refSibs.length; i++) {
|
|
39721
40288
|
const refEl = refSibs[i];
|
|
39722
|
-
const
|
|
39723
|
-
insertRunAtOffset(para, refEl,
|
|
40289
|
+
const op = noteOps[i];
|
|
40290
|
+
insertRunAtOffset(para, refEl, op?.offset);
|
|
39724
40291
|
}
|
|
39725
40292
|
node.version += 1;
|
|
39726
40293
|
}
|
|
39727
40294
|
}
|
|
40295
|
+
function validateComposedOperations(operations) {
|
|
40296
|
+
validateOperationsStrict(
|
|
40297
|
+
operations.map((op) => {
|
|
40298
|
+
const rec = op;
|
|
40299
|
+
const params = rec["params"];
|
|
40300
|
+
if (rec["action"] !== "callout" || !params) return op;
|
|
40301
|
+
const { mdTitleSpans: _t, mdBodySpans: _b, ...publicParams } = params;
|
|
40302
|
+
void _t;
|
|
40303
|
+
void _b;
|
|
40304
|
+
const { params: _p, ...rest } = rec;
|
|
40305
|
+
void _p;
|
|
40306
|
+
return Object.keys(publicParams).length > 0 ? { ...rest, params: publicParams } : rest;
|
|
40307
|
+
})
|
|
40308
|
+
);
|
|
40309
|
+
}
|
|
39728
40310
|
async function composeDocument(parser2, markdown, options) {
|
|
39729
40311
|
if (!parser2 || typeof parser2.getAST !== "function") {
|
|
39730
40312
|
throw new Error(
|
|
39731
40313
|
"E_VALIDATION: line 1: composeDocument needs an open document parser. Remedy: open a document first, then compose into it."
|
|
39732
40314
|
);
|
|
39733
40315
|
}
|
|
40316
|
+
const liveAst = await parser2.getAST();
|
|
40317
|
+
const idPrefix = options?.idPrefix ?? nextComposeIdPrefix(liveAst);
|
|
39734
40318
|
if (options?.template !== void 0) {
|
|
40319
|
+
if (idPrefix !== "md") {
|
|
40320
|
+
throw new Error(
|
|
40321
|
+
"E_VALIDATION: line 1: template is accepted only on the first compose of a document. Remedy: omit template on this compose; the document already holds composed content."
|
|
40322
|
+
);
|
|
40323
|
+
}
|
|
39735
40324
|
assertTemplateSeed(parser2, String(options.template));
|
|
39736
40325
|
}
|
|
40326
|
+
if (options?.position !== void 0 && options.anchor === void 0) {
|
|
40327
|
+
throw new Error(
|
|
40328
|
+
"E_VALIDATION: line 1: position needs an anchor. Remedy: pass anchor (a node id, start or end) together with position, or omit position to append at the end."
|
|
40329
|
+
);
|
|
40330
|
+
}
|
|
40331
|
+
if (options?.anchor !== void 0 && anchorAliasOf(options.anchor) === null) {
|
|
40332
|
+
const known = liveAst.sections.some((section) => section.nodes.some((node) => node.id === options.anchor));
|
|
40333
|
+
if (!known) {
|
|
40334
|
+
throw new Error(
|
|
40335
|
+
`E_VALIDATION: line 1: anchor '${options.anchor}' is not a node of this document. Remedy: pass a node id from inspect_document, or start or end.`
|
|
40336
|
+
);
|
|
40337
|
+
}
|
|
40338
|
+
}
|
|
39737
40339
|
const styleGuide = resolveComposeStyleGuide(options?.styleGuide);
|
|
39738
40340
|
const { operations, warnings: compileWarnings } = compileMarkdownDetailed(markdown, {
|
|
40341
|
+
idPrefix,
|
|
40342
|
+
...options?.anchor !== void 0 ? { anchor: options.anchor } : {},
|
|
40343
|
+
...options?.position !== void 0 ? { position: options.position } : {},
|
|
39739
40344
|
allowedDir: options?.allowedDir,
|
|
39740
40345
|
...options?.styleGuide !== void 0 ? { styleGuide: options.styleGuide } : {},
|
|
39741
40346
|
...options?.dollarMath ? { dollarMath: true } : {}
|
|
39742
40347
|
});
|
|
39743
|
-
|
|
40348
|
+
validateComposedOperations(operations);
|
|
40349
|
+
if (operations.some((op) => op.action === "namedStyle" && op.styleName === QUOTE_STYLE_ID)) {
|
|
40350
|
+
ensureQuoteStyle(parser2.getStylesDom());
|
|
40351
|
+
}
|
|
39744
40352
|
const chunkSize = options?.chunkSize !== void 0 ? Math.floor(options.chunkSize) : ENGINE_MAX_OPS;
|
|
39745
40353
|
if (!Number.isInteger(chunkSize) || chunkSize < 1 || chunkSize > ENGINE_MAX_OPS) {
|
|
39746
40354
|
throw new Error(
|
|
39747
40355
|
`E_VALIDATION: line 1: chunkSize must be an integer in [1, ${ENGINE_MAX_OPS}]. Remedy: omit chunkSize or pass a value inside that range.`
|
|
39748
40356
|
);
|
|
39749
40357
|
}
|
|
39750
|
-
const ast =
|
|
40358
|
+
const ast = liveAst;
|
|
39751
40359
|
const blankSeedIds = pristineBlankSeedIds(parser2, options?.template);
|
|
39752
40360
|
let preBatchSnapshot = null;
|
|
39753
40361
|
try {
|
|
@@ -39800,7 +40408,17 @@ async function composeDocument(parser2, markdown, options) {
|
|
|
39800
40408
|
}
|
|
39801
40409
|
base += chunk.length;
|
|
39802
40410
|
}
|
|
39803
|
-
if (!aggregate.success)
|
|
40411
|
+
if (!aggregate.success) {
|
|
40412
|
+
if (aggregate.appliedCount > 0 && preBatchSnapshot !== null) {
|
|
40413
|
+
try {
|
|
40414
|
+
parser2.restorePackageSnapshot(preBatchSnapshot);
|
|
40415
|
+
aggregate.appliedCount = 0;
|
|
40416
|
+
aggregate.rollbackTriggered = true;
|
|
40417
|
+
} catch {
|
|
40418
|
+
}
|
|
40419
|
+
}
|
|
40420
|
+
return aggregate;
|
|
40421
|
+
}
|
|
39804
40422
|
if (blankSeedIds !== null) {
|
|
39805
40423
|
const seedCleanup = await engine.executeBatch({
|
|
39806
40424
|
operations: [{ action: "delete", target: blankSeedIds }],
|
|
@@ -39871,119 +40489,19 @@ async function composeDocument(parser2, markdown, options) {
|
|
|
39871
40489
|
if (/^E_VALIDATION/.test(detail)) throw err;
|
|
39872
40490
|
throw new Error(`E_VALIDATION: line 1: inline formatting failed (${detail}). Remedy: simplify bold, italic, code, or link spans and retry.`);
|
|
39873
40491
|
}
|
|
39874
|
-
return aggregate;
|
|
39875
|
-
}
|
|
39876
|
-
|
|
39877
|
-
// ../engine/src/stats.ts
|
|
39878
|
-
function nearestOwner(el, tagName) {
|
|
39879
|
-
let cur = el.parentNode;
|
|
39880
|
-
while (cur && typeof cur === "object") {
|
|
39881
|
-
const e = cur;
|
|
39882
|
-
if (e.tagName === tagName) return e;
|
|
39883
|
-
cur = e.parentNode;
|
|
39884
|
-
}
|
|
39885
|
-
return null;
|
|
39886
|
-
}
|
|
39887
|
-
function tableCellTextSpaced(node) {
|
|
39888
40492
|
try {
|
|
39889
|
-
const
|
|
39890
|
-
if (
|
|
39891
|
-
const tcs = xml.getElementsByTagName("w:tc");
|
|
39892
|
-
if (!tcs || tcs.length === 0) return void 0;
|
|
39893
|
-
const cells = [];
|
|
39894
|
-
for (let i = 0; i < tcs.length; i++) {
|
|
39895
|
-
const tc = tcs[i];
|
|
39896
|
-
const paras = tc.getElementsByTagName("w:p");
|
|
39897
|
-
if (paras.length > 0) {
|
|
39898
|
-
const parts = [];
|
|
39899
|
-
for (let p = 0; p < paras.length; p++) {
|
|
39900
|
-
const para = paras[p];
|
|
39901
|
-
if (nearestOwner(para, "w:tc") !== tc) continue;
|
|
39902
|
-
const runs = para.getElementsByTagName("w:t");
|
|
39903
|
-
let t = "";
|
|
39904
|
-
for (let r = 0; r < runs.length; r++) {
|
|
39905
|
-
const run = runs[r];
|
|
39906
|
-
if (nearestOwner(run, "w:tc") !== tc) continue;
|
|
39907
|
-
if (nearestOwner(run, "w:p") !== para) continue;
|
|
39908
|
-
t += run.textContent || "";
|
|
39909
|
-
}
|
|
39910
|
-
if (t.trim().length > 0) parts.push(t.trim());
|
|
39911
|
-
}
|
|
39912
|
-
if (parts.length > 0) cells.push(parts.join(" "));
|
|
39913
|
-
} else {
|
|
39914
|
-
const runs = tc.getElementsByTagName("w:t");
|
|
39915
|
-
let t = "";
|
|
39916
|
-
for (let r = 0; r < runs.length; r++) {
|
|
39917
|
-
const run = runs[r];
|
|
39918
|
-
if (nearestOwner(run, "w:tc") !== tc) continue;
|
|
39919
|
-
t += run.textContent || "";
|
|
39920
|
-
}
|
|
39921
|
-
if (t.trim().length > 0) cells.push(t.trim());
|
|
39922
|
-
}
|
|
39923
|
-
}
|
|
39924
|
-
if (cells.length === 0) return "";
|
|
39925
|
-
return cells.join(" ");
|
|
39926
|
-
} catch {
|
|
39927
|
-
return void 0;
|
|
39928
|
-
}
|
|
39929
|
-
}
|
|
39930
|
-
function nestedTableCount(node) {
|
|
39931
|
-
try {
|
|
39932
|
-
const xml = node.xml;
|
|
39933
|
-
if (!xml || typeof xml.getElementsByTagName !== "function") return 0;
|
|
39934
|
-
return xml.getElementsByTagName("w:tbl").length;
|
|
39935
|
-
} catch {
|
|
39936
|
-
return 0;
|
|
39937
|
-
}
|
|
39938
|
-
}
|
|
39939
|
-
function countExplicitPageBreaks(node) {
|
|
39940
|
-
if (node.type === "pageBreak") return 1;
|
|
39941
|
-
const text2 = node.metadata?.text || "";
|
|
39942
|
-
let count = text2 === "[Page Break]" ? 1 : 0;
|
|
39943
|
-
try {
|
|
39944
|
-
const xml = node.xml;
|
|
39945
|
-
if (xml && typeof xml.getElementsByTagName === "function") {
|
|
39946
|
-
const brs = xml.getElementsByTagName("w:br");
|
|
39947
|
-
let brCount = 0;
|
|
39948
|
-
for (let i = 0; i < brs.length; i++) {
|
|
39949
|
-
if (brs[i].getAttribute("w:type") === "page") brCount++;
|
|
39950
|
-
}
|
|
39951
|
-
if (brCount > count) count = brCount;
|
|
39952
|
-
}
|
|
40493
|
+
const identity = applyDocumentIdentity(ast, parser2);
|
|
40494
|
+
if (identity.titleSetFrom) aggregate.titleSetFrom = identity.titleSetFrom;
|
|
39953
40495
|
} catch {
|
|
39954
40496
|
}
|
|
39955
|
-
return
|
|
39956
|
-
}
|
|
39957
|
-
function computeDocStats(ast) {
|
|
39958
|
-
let words = 0;
|
|
39959
|
-
let paragraphs = 0;
|
|
39960
|
-
let tables = 0;
|
|
39961
|
-
let images = 0;
|
|
39962
|
-
let pageBreaks = 0;
|
|
39963
|
-
for (const sec of ast.sections) {
|
|
39964
|
-
for (const node of sec.nodes) {
|
|
39965
|
-
if (node.type === "paragraph") paragraphs += 1;
|
|
39966
|
-
if (node.type === "table") tables += 1 + nestedTableCount(node);
|
|
39967
|
-
if (node.type === "image") images += 1;
|
|
39968
|
-
pageBreaks += countExplicitPageBreaks(node);
|
|
39969
|
-
let text2 = node.metadata?.text ?? "";
|
|
39970
|
-
if (node.type === "table") {
|
|
39971
|
-
const spaced = tableCellTextSpaced(node);
|
|
39972
|
-
if (spaced !== void 0) text2 = spaced;
|
|
39973
|
-
}
|
|
39974
|
-
const hits = text2.match(/\S+/g);
|
|
39975
|
-
if (hits) words += hits.length;
|
|
39976
|
-
}
|
|
39977
|
-
}
|
|
39978
|
-
const pageBudget = words / 450 + tables * 0.45 + images * 0.5 + pageBreaks;
|
|
39979
|
-
return { words, paragraphs, pages: Math.max(1, Math.ceil(pageBudget)), tables, images };
|
|
40497
|
+
return aggregate;
|
|
39980
40498
|
}
|
|
39981
40499
|
|
|
39982
40500
|
// ../engine/src/style-guide/resolve.ts
|
|
39983
40501
|
var import_fs8 = __toESM(require("fs"));
|
|
39984
40502
|
var import_path7 = __toESM(require("path"));
|
|
39985
40503
|
function hasScheme(ref) {
|
|
39986
|
-
return
|
|
40504
|
+
return hasUriScheme(ref);
|
|
39987
40505
|
}
|
|
39988
40506
|
function failValidation(message, remedy) {
|
|
39989
40507
|
throw toError("E_VALIDATION", message, remedy);
|
|
@@ -40166,7 +40684,13 @@ function mcpErrorFrom(err, fallbackCode, fallbackRemedy) {
|
|
|
40166
40684
|
const rawMessage = err instanceof Error ? err.message : String(err);
|
|
40167
40685
|
const message = rewritePhantomMcpToolNames(sanitizeMcpClientMessage(rawMessage));
|
|
40168
40686
|
const remedy = err?.remedy && typeof err.remedy === "string" ? rewritePhantomMcpToolNames(String(err.remedy)) : rewritePhantomMcpToolNames(fallbackRemedy);
|
|
40169
|
-
|
|
40687
|
+
const shape = { ok: false, code: resolved, message, remedy };
|
|
40688
|
+
const carried = err;
|
|
40689
|
+
if (carried && Array.isArray(carried.errors) && carried.errors.length > 0 && typeof carried.errorCount === "number") {
|
|
40690
|
+
shape.errorCount = carried.errorCount;
|
|
40691
|
+
shape.errors = carried.errors;
|
|
40692
|
+
}
|
|
40693
|
+
return shape;
|
|
40170
40694
|
}
|
|
40171
40695
|
function sanitizeMcpClientMessage(raw) {
|
|
40172
40696
|
let text2 = typeof raw === "string" ? raw : String(raw ?? "");
|
|
@@ -40352,7 +40876,8 @@ function collectAstIdTexts(ast) {
|
|
|
40352
40876
|
for (const n of sec.nodes ?? []) {
|
|
40353
40877
|
if (typeof n.id !== "string" || !n.id) continue;
|
|
40354
40878
|
order.push(n.id);
|
|
40355
|
-
|
|
40879
|
+
const grid = n.type === "table" ? tableGridPlainText(n.xml) : void 0;
|
|
40880
|
+
texts.set(n.id, grid ?? String(n.metadata?.text ?? ""));
|
|
40356
40881
|
}
|
|
40357
40882
|
}
|
|
40358
40883
|
} catch {
|
|
@@ -40662,6 +41187,25 @@ function descriptionOf(e) {
|
|
|
40662
41187
|
function firstMessages(result, limit = 5) {
|
|
40663
41188
|
return (result.errors ?? []).slice(0, limit).map((e) => descriptionOf(e).slice(0, 300));
|
|
40664
41189
|
}
|
|
41190
|
+
var AUDIT_VALIDATION_ERRORS_MAX = 10;
|
|
41191
|
+
function summarizeValidationErrors(errors, max = AUDIT_VALIDATION_ERRORS_MAX) {
|
|
41192
|
+
const raw = Array.isArray(errors) ? errors.slice(0, max) : [];
|
|
41193
|
+
return raw.map((e) => {
|
|
41194
|
+
const err = e;
|
|
41195
|
+
const description = String(
|
|
41196
|
+
typeof err.description === "string" && err.description || typeof err.message === "string" && err.message || "OOXML validation error"
|
|
41197
|
+
).slice(0, 500);
|
|
41198
|
+
const xp = typeof err.xPath === "string" ? err.xPath : typeof err.path === "string" ? err.path : void 0;
|
|
41199
|
+
return xp ? { description, xPath: xp } : { description };
|
|
41200
|
+
});
|
|
41201
|
+
}
|
|
41202
|
+
function toAuditValidation(validation, max = AUDIT_VALIDATION_ERRORS_MAX) {
|
|
41203
|
+
return {
|
|
41204
|
+
valid: validation.ok,
|
|
41205
|
+
errorCount: Array.isArray(validation.errors) ? validation.errors.length : 0,
|
|
41206
|
+
errors: summarizeValidationErrors(validation.errors, max)
|
|
41207
|
+
};
|
|
41208
|
+
}
|
|
40665
41209
|
function introducedError(toolName, verdict) {
|
|
40666
41210
|
const first = verdict.introduced[0];
|
|
40667
41211
|
const desc = descriptionOf(first);
|
|
@@ -40670,7 +41214,12 @@ function introducedError(toolName, verdict) {
|
|
|
40670
41214
|
new Error(
|
|
40671
41215
|
`E_VALIDATION: ${toolName} blocked: this edit adds ${verdict.introduced.length} validator error(s) (the file already had ${verdict.preExisting}): ${desc.slice(0, 2e3)}`
|
|
40672
41216
|
),
|
|
40673
|
-
{
|
|
41217
|
+
{
|
|
41218
|
+
code: "E_VALIDATION",
|
|
41219
|
+
remedy: remedyForValidationError(xp, desc),
|
|
41220
|
+
errorCount: verdict.introduced.length,
|
|
41221
|
+
errors: summarizeValidationErrors(verdict.introduced)
|
|
41222
|
+
}
|
|
40674
41223
|
);
|
|
40675
41224
|
}
|
|
40676
41225
|
function validationField(result, verdict) {
|
|
@@ -40680,10 +41229,12 @@ function validationField(result, verdict) {
|
|
|
40680
41229
|
tolerated: true,
|
|
40681
41230
|
preExistingErrors: verdict.preExisting,
|
|
40682
41231
|
introducedErrors: 0,
|
|
40683
|
-
messages: firstMessages(result)
|
|
41232
|
+
messages: firstMessages(result),
|
|
41233
|
+
valid: false,
|
|
41234
|
+
errorCount: Array.isArray(result.errors) ? result.errors.length : verdict.preExisting
|
|
40684
41235
|
};
|
|
40685
41236
|
}
|
|
40686
|
-
return { ok: true };
|
|
41237
|
+
return { ok: true, valid: true, errorCount: 0 };
|
|
40687
41238
|
}
|
|
40688
41239
|
function withToleratedNote(message, preExisting) {
|
|
40689
41240
|
const note = ` (saved with ${preExisting} existing validator errors, 0 introduced)`;
|
|
@@ -40955,6 +41506,13 @@ function getMcpAllowedRoots() {
|
|
|
40955
41506
|
}
|
|
40956
41507
|
return [...new Set(roots)];
|
|
40957
41508
|
}
|
|
41509
|
+
function describeAllowedFolders() {
|
|
41510
|
+
const named = getExistingExtraRoots();
|
|
41511
|
+
if (named.length > 0) {
|
|
41512
|
+
return `inside the folder${named.length > 1 ? "s" : ""} this server allows: ${[...new Set(named)].join(", ")}`;
|
|
41513
|
+
}
|
|
41514
|
+
return "inside ./documents or the system temp folder, or start the server with --allowed-dir <folder> to allow another folder";
|
|
41515
|
+
}
|
|
40958
41516
|
setTrustedWriteRootsProvider(getMcpAllowedRoots);
|
|
40959
41517
|
function getExistingExtraRoots() {
|
|
40960
41518
|
const raw = (process.env.ASTRA_EXTRA_DOC_ROOTS || "").trim();
|
|
@@ -40993,7 +41551,7 @@ function assertNoDotSegments(canonical, root) {
|
|
|
40993
41551
|
if (seg.startsWith(".")) {
|
|
40994
41552
|
throw Object.assign(new Error(`Document path traverses a hidden directory "${seg}"`), {
|
|
40995
41553
|
code: "E_SANDBOX",
|
|
40996
|
-
remedy:
|
|
41554
|
+
remedy: `Use a path with no hidden (dot-prefixed) folder, ${describeAllowedFolders()}.`
|
|
40997
41555
|
});
|
|
40998
41556
|
}
|
|
40999
41557
|
}
|
|
@@ -41035,7 +41593,7 @@ function validateDocPath(rawPath) {
|
|
|
41035
41593
|
if (segments.includes("..")) {
|
|
41036
41594
|
throw Object.assign(new Error('Document path contains forbidden ".." traversal'), {
|
|
41037
41595
|
code: "E_SANDBOX",
|
|
41038
|
-
remedy:
|
|
41596
|
+
remedy: `Use a path ${describeAllowedFolders()}.`
|
|
41039
41597
|
});
|
|
41040
41598
|
}
|
|
41041
41599
|
const resolved = path12.resolve(trimmed);
|
|
@@ -41043,7 +41601,7 @@ function validateDocPath(rawPath) {
|
|
|
41043
41601
|
if (resolvedSegments.includes("..")) {
|
|
41044
41602
|
throw Object.assign(new Error("Document path resolves outside its directory"), {
|
|
41045
41603
|
code: "E_SANDBOX",
|
|
41046
|
-
remedy:
|
|
41604
|
+
remedy: `Use a path ${describeAllowedFolders()}.`
|
|
41047
41605
|
});
|
|
41048
41606
|
}
|
|
41049
41607
|
const canonical = canonicalizeForAsset(resolved);
|
|
@@ -41672,12 +42230,17 @@ var PARITY_TOOL_NAMES = [
|
|
|
41672
42230
|
var MCP_ECMA_ESSENTIALS = "ECMA-376 essentials: colors are hex without # (e.g. 4F46E5, never #4F46E5); font sizes are half-points in w:sz (e.g. 28 = 14pt, 22 = 11pt); spacing/margins/cellMar use twips dxa (twentieths of a point, e.g. 240 = 12pt, 180 = 9pt); borders use eighths-of-a-point sz (e.g. 32 = 4pt left accent).";
|
|
41673
42231
|
var MCP_WORKFLOW = "Workflow: open_document -> inspect_document -> search_content -> execute_batch -> save_document -> close_document. Always inspect first, batch second, save third. Single edit = 1-item batch. Never call twice when once suffices.";
|
|
41674
42232
|
var MCP_ENVELOPE_NOTE = "";
|
|
41675
|
-
function withSharedDocs(body) {
|
|
41676
|
-
|
|
42233
|
+
function withSharedDocs(body, shared = {}) {
|
|
42234
|
+
const parts = [body.trimEnd()];
|
|
42235
|
+
if (shared.ecma) parts.push(MCP_ECMA_ESSENTIALS);
|
|
42236
|
+
if (shared.workflow) parts.push(MCP_WORKFLOW);
|
|
42237
|
+
if (MCP_ENVELOPE_NOTE) parts.push(MCP_ENVELOPE_NOTE);
|
|
42238
|
+
return parts.join(" ");
|
|
41677
42239
|
}
|
|
41678
42240
|
var LEAN6_DESCRIPTIONS = {
|
|
41679
42241
|
open_document: withSharedDocs(
|
|
41680
|
-
'Open a .docx file into a session (Map sessionId->DocHandle, LRU 10 docs / 200MB / 30min idle evict). Returns {ok:true,sessionId,revision,summary,evicted} where evicted:string[] lists sessions dropped by the capacity cap to make room (re-open them if needed; later calls with an evicted id fail with E_SESSION plus a close_document remedy). The default summary is compact: palette, counts, header/footer text, chart ids and pending-revision flag. Pass {"summaryOnly":false} for the full structural summary including styles XML. Example input {"path":"documents/report.docx"} -> {"ok":true,"sessionId":"sess_abc","revision":1,"evicted":[]}. Open first; keep the sessionId for every later call. The response also carries validation: {status:"valid"}, {status:"invalid",preExistingErrors,messages,baselineComparable,note} when the file already has validator errors (edits that add no new error can still be saved), {status:"timed-out"} or {status:"skipped"}. The check waits validationTimeoutMs (default 5000 ms; 0 skips it; server flag --open-validation-timeout, env ASTRA_OPEN_VALIDATION_TIMEOUT_MS) and never refuses the open. Macro/active content refuses by default (E_VALIDATION naming the parts): pass {"allowMacros":true} to preserve or {"stripMacros":true} to strip (never both). Session ids are server-minted (sess_<uuid>, DEPRECATED to supply sessionId: client-supplied values are rejected with E_VALIDATION; deterministic ids are test-only via --allow-client-session-ids). IDs are session-scoped: valid only in the issuing session, must not be stored across sessions, and duplicate client-supplied ids are rejected.'
|
|
42242
|
+
'Open a .docx file into a session (Map sessionId->DocHandle, LRU 10 docs / 200MB / 30min idle evict). Returns {ok:true,sessionId,revision,summary,evicted} where evicted:string[] lists sessions dropped by the capacity cap to make room (re-open them if needed; later calls with an evicted id fail with E_SESSION plus a close_document remedy). The default summary is compact: palette, counts, header/footer text, chart ids and pending-revision flag. Pass {"summaryOnly":false} for the full structural summary including styles XML. Example input {"path":"documents/report.docx"} -> {"ok":true,"sessionId":"sess_abc","revision":1,"evicted":[]}. Open first; keep the sessionId for every later call. The response also carries validation: {status:"valid"}, {status:"invalid",preExistingErrors,messages,baselineComparable,note} when the file already has validator errors (edits that add no new error can still be saved), {status:"timed-out"} or {status:"skipped"}. The check waits validationTimeoutMs (default 5000 ms; 0 skips it; server flag --open-validation-timeout, env ASTRA_OPEN_VALIDATION_TIMEOUT_MS) and never refuses the open. Macro/active content refuses by default (E_VALIDATION naming the parts): pass {"allowMacros":true} to preserve or {"stripMacros":true} to strip (never both). Session ids are server-minted (sess_<uuid>, DEPRECATED to supply sessionId: client-supplied values are rejected with E_VALIDATION; deterministic ids are test-only via --allow-client-session-ids). IDs are session-scoped: valid only in the issuing session, must not be stored across sessions, and duplicate client-supplied ids are rejected.',
|
|
42243
|
+
{ workflow: true }
|
|
41681
42244
|
),
|
|
41682
42245
|
inspect_document: withSharedDocs(
|
|
41683
42246
|
'Inspect a session: standard TOON outline (fidelity compact|standard|full, default standard) plus style palette passthrough {availableStyles,brandColors,primaryFont} parsed from word/styles.xml, plus document core properties `metadata` {title,creator,subject,description,keywords,category,lastModifiedBy,revision,created,modified} read from docProps/core.xml (absent when the docx has no core part). Use include:["comments","revisions"] for review records paged by reviewOffset/reviewLimit (max 50; offset/limit page nodes only); commentId or revisionId reads full detail and needs its include. Renders the in-memory session state (includes unsaved execute_batch edits); `dirty:true` means the session differs from the last saved file, `revision` is the in-memory revision. Node ids are verbatim: paragraphs carry 8-hex w14:paraId (e.g. 3F2A9C1B), tables t_001.., sections sec_root, sec_2, sec_3.. by position; use them as-is for targets/anchors. A repeat paragraph sharing one native paraId reports a `<paraId>_dup` session id instead (e.g. 3F2A9C1B_dup): saving refuses until the repeat is deleted and its text reinserted for a fresh id. Tables render one row per table with grid cells as rNcM coordinates (r1c1 value | r1c2 value, rows joined by ;); merged spans are marked (spanN); merge covers are tagged hidden-cover (not directly editable, address the restart cell). Cell edits: the replace, style, namedStyle, clearFormat, hyperlink, bookmark and comment actions accept target "<tableId>:r<row>c<col>" (1-based, as the grid prints it; add ":p<n>" to pick one paragraph of a multi-paragraph cell). Other actions, including table, take the table id. Every row and column number counts from 1 (cell addresses, rowIndex, colIndex, columnIndex, row, col, index, afterIndex, fromRow/toRow/fromCol/toCol), so r3c2 and {rowIndex:3,colIndex:2} are the same cell. Chart nodes render style chart|chart3d with title + categories + series in the text ([Chart column] Title | cats: Q1,Q2 | ARR: 10,25), read from the chart part. Flag legend: [TBL] table [IMG] image/drawing [EQ] equation [CITE] citation [CMT] comment [TRK] tracked [LNK] link [BKMK] bookmark [L#] list level (1 is the top level; list ops count the same way) [+ADDED]/[~MODIFIED] diff; standard tier adds [B] bold [I] italic [C:hex] color [SZ:half-points] size [RS:name] run style [J:just] [N:num] numbering [HDR]/[ZEBRA] table look [CH:type]/[RID:id] chart. Palette lists every style id verbatim with no trim (large templates return the full list). 3-tier workflow for large docs: (1) macro-overview inspect (compact, first window) -> (2) search_content to find a target node id -> (3) local zoom via {aroundNode:"<id>",window:5} or page with {offset,limit}. Windowing opts: offset (min 0, default 0), limit (1-200, default 50), aroundNode + window (1-20, default 5) returning [node-w,node+w], maxTokens (200-8000, cuts on a clean row boundary, always reported via truncated/hasMore). aroundNode and offset/limit are mutually exclusive (use one or the other). Windowed renders preserve the requested tier; the TOON fidelity marker stays lossy by design (TOON is a lossy projection). Docs under ~2000 tokens return the full TOON (unchanged); larger docs default to the first ~50-node window {totalNodes,returnedNodes,offset,limit,hasMore,nextOffset,toonText} with a navigation remedy. Full fidelity requires sectionId; sectionId is a section id or a node id (a node id prefix needs 4 characters or more), and an id that matches nothing is an error that names the available section ids. Example {"sessionId":"sess_abc","fidelity":"standard"} -> TOON text + palette. Use the palette style names verbatim (e.g. Corporate Callout); never invent styles. When the session carries a style guide, the envelope also includes its compact styleGuide summary {name,headerFill,headerText,chartPalette,calloutTheme}. IDs are session-scoped: valid only in the issuing session, must not be stored across sessions, and duplicate client-supplied ids are rejected.'
|
|
@@ -41686,10 +42249,12 @@ var LEAN6_DESCRIPTIONS = {
|
|
|
41686
42249
|
'Search live paragraph/table w:t text; returns [{id,preview}] node hits for anchors/targets. Use scope:"comments" or scope:"all" to search comment text; the default scope is body. At most 50 matches come back; truncated:true says more exist. A table hit also lists `cells` (up to 5 addresses such as t_001:r3c1) for the cells that hold the match; pass one as the target of replace/style/namedStyle/clearFormat/hyperlink/bookmark/comment. Chart titles/series are NOT matched by search (they live in the chart part, not w:t); discover them in inspect TOON chart rows, then anchor by the chart node id. Image alt text and footnote/endnote bodies are NOT matched either (search covers body w:t text only); find images via inspect [IMG] rows and footnotes via their anchor ids. Search extension to those parts is a later-program candidate, not this surface. Default is an exact literal substring (case-insensitive); pass {"regex":true} to interpret the query as a regular expression (bounded: pattern <=200 chars, ~500ms evaluation budget, case-insensitive). Caps: first 50 hits, 120-char previews. Literal queries containing regex metacharacters return literal results plus a hint that regex:true exists. Example {"sessionId":"sess_abc","query":"Executive Summary"} -> {"ok":true,"matches":[{"id":"3F2A9C1B","preview":"Executive ..."}]}. Search before editing when the anchor id is unknown.'
|
|
41687
42250
|
),
|
|
41688
42251
|
execute_batch: withSharedDocs(
|
|
41689
|
-
'Apply 1..500 strictly-typed operations atomically (strict discriminatedUnion per action; sequential:true default preserves document order; Tracked edits return revisionIds; trackedChange accept/reject selects by ids, author or target and reports revisionResolution {resolved,unresolved,removedBookmarks,removedComments,staleLinks}. Comment operations include resolve, unresolve and delete and take an id as 3 or comment:3. single edit = 1-item batch). Caps 500 ops / 200MB throw E_QUOTA with remedy (maxSteps is an optional positive integer, unbounded when omitted). Client-supplied op ids are honored where the schema allows (insert/equation/footnote/chart accept `id`; strict schemas reject it elsewhere). Style ops support word-level spans: `match` styles only the matched substring (run splitting; `matchAll` styles every occurrence, `matchCase:false` matches case-insensitively); an absent match fails with E_NOT_FOUND. Returns {ok:true,appliedCount,revision,skippedIds,createdNodeIds,results:[{index,action,createdNodeIds}],revisionsPending,revisionsUnresolved?,revisionsRemedy?} where createdNodeIds After a trackedChange that left revisions it could not resolve, revisionsUnresolved counts them and revisionsRemedy lists kind, node and reason instead of repeating acceptAll. lists new node ids in document order (use them as anchors in the next batch without re-inspecting). A batch that drops a non-empty redo stack signals it (redoCleared:true + redoClearedWarning naming the depth): a fresh edit invalidates redo, so redo_document afterwards reports an empty stack. Example {"sessionId":"sess_abc","operations":[{"action":"replace","target":"p_001","text":"Hello"}],"sequential":true} -> {"ok":true,"appliedCount":1,"revision":2}. On failure returns {ok:false,code,remedy} with a fix-and-retry hint. Image inserts take exactly one source: {action:"image",operation:"insert",anchor,fileData} for inline base64 bytes, or {action:"image",operation:"insert",anchor,filePath} for a file read from a directory jail (both together fail with E_VALIDATION). The jail resolves explicit allowedDir first, else the session anchor (dirname of the open document); outside-jail refs fail with E_SANDBOX, missing files with E_IO, oversize files with E_LIMIT; batch reads nothing otherwise. Callout themes (operations/callout.ts THEMES): indigo|emerald|amber|slate|rose (default indigo; unknown names render indigo). Optional styleGuide (bundled guide name or guide .md path, jailed like filePath: explicit allowedDir else the session anchor; remote URLs fail closed) overrides the session guide for this batch only; it never replaces or persists (to re-skin the session use compose_document with styleGuide). Omitted inherits the session guide. Guides cannot be detached: an empty styleGuide fails with E_VALIDATION; open a new session to work without a guide. Unknown names fail with E_VALIDATION naming the available set. Guide parse notes ride both server stderr and the client-visible warnings array. Layout operations: {action:"section",operation:"setColumns",columns,spacing,separator} (columns 1-12, spacing in twips, separator draws the dividing rule), {action:"section",operation:"split"|"merge",...}, and {action:"pageSetup",orientation:"portrait"|"landscape",paperSize:"A4"|"Letter"|"Legal"|"A3",marginTop,marginBottom,marginLeft,marginRight,columns,sectionIndex} (margins in twips and written as given, never rotated; orientation turns the page by swapping its width and height; sectionIndex counts sections from 1 and limits the change to one section, omit it to change every section). Section breaks: {action:"insert",type:"sectionBreak",anchor,position,breakType:"nextPage"|"continuous"|"evenPage"|"oddPage"|"nextColumn"}; the section before the break keeps the page setup and headers of the section it splits. Headers and footers: {action:"headerFooter",operation:"set",targetType:"header"|"footer",text,alignment,sectionIndex (from 1, default the first section)}; {PAGE} and {NUMPAGES} inside text become live page-number fields. Images: width and height are integers in EMU (914400 per inch, 12700 per point, 9525 per pixel at 96 dpi); give one and the other follows the image aspect ratio, give neither and the image keeps its own size fitted to the page. Tables created by insert type:"table" or by Markdown repeat their header row across pages by default. Equations: {action:"equation",operation:"insert"|"replace",latex,mode:"auto"|"latex"|"plain"}; LaTeX that contains a backslash, ^ or _ becomes a real Word equation, and mode:"plain" keeps the text exactly as written. IDs are session-scoped: valid only in the issuing session, must not be stored across sessions, and duplicate client-supplied ids are rejected.'
|
|
42252
|
+
'Apply 1..500 strictly-typed operations atomically (strict discriminatedUnion per action; sequential:true default preserves document order; Tracked edits return revisionIds; trackedChange accept/reject selects by ids, author or target and reports revisionResolution {resolved,unresolved,removedBookmarks,removedComments,staleLinks}. Comment operations include resolve, unresolve and delete and take an id as 3 or comment:3. single edit = 1-item batch). Caps 500 ops / 200MB throw E_QUOTA with remedy (maxSteps is an optional positive integer, unbounded when omitted). Client-supplied op ids are honored where the schema allows (insert/equation/footnote/chart accept `id`; strict schemas reject it elsewhere). Style ops support word-level spans: `match` styles only the matched substring (run splitting; `matchAll` styles every occurrence, `matchCase:false` matches case-insensitively); an absent match fails with E_NOT_FOUND. Returns {ok:true,appliedCount,revision,skippedIds,createdNodeIds,results:[{index,action,createdNodeIds}],revisionsPending,revisionsUnresolved?,revisionsRemedy?} where createdNodeIds After a trackedChange that left revisions it could not resolve, revisionsUnresolved counts them and revisionsRemedy lists kind, node and reason instead of repeating acceptAll. lists new node ids in document order (use them as anchors in the next batch without re-inspecting). A batch that drops a non-empty redo stack signals it (redoCleared:true + redoClearedWarning naming the depth): a fresh edit invalidates redo, so redo_document afterwards reports an empty stack. Example {"sessionId":"sess_abc","operations":[{"action":"replace","target":"p_001","text":"Hello"}],"sequential":true} -> {"ok":true,"appliedCount":1,"revision":2}. On failure returns {ok:false,code,remedy} with a fix-and-retry hint. Image inserts take exactly one source: {action:"image",operation:"insert",anchor,fileData} for inline base64 bytes, or {action:"image",operation:"insert",anchor,filePath} for a file read from a directory jail (both together fail with E_VALIDATION). The jail resolves explicit allowedDir first, else the session anchor (dirname of the open document); outside-jail refs fail with E_SANDBOX, missing files with E_IO, oversize files with E_LIMIT; batch reads nothing otherwise. Callout themes (operations/callout.ts THEMES): indigo|emerald|amber|slate|rose (default indigo; unknown names render indigo). Optional styleGuide (bundled guide name or guide .md path, jailed like filePath: explicit allowedDir else the session anchor; remote URLs fail closed) overrides the session guide for this batch only; it never replaces or persists (to re-skin the session use compose_document with styleGuide). Omitted inherits the session guide. Guides cannot be detached: an empty styleGuide fails with E_VALIDATION; open a new session to work without a guide. Unknown names fail with E_VALIDATION naming the available set. Guide parse notes ride both server stderr and the client-visible warnings array. Layout operations: {action:"section",operation:"setColumns",columns,spacing,separator} (columns 1-12, spacing in twips, separator draws the dividing rule), {action:"section",operation:"split"|"merge",...}, and {action:"pageSetup",orientation:"portrait"|"landscape",paperSize:"A4"|"Letter"|"Legal"|"A3",marginTop,marginBottom,marginLeft,marginRight,columns,sectionIndex} (margins in twips and written as given, never rotated; orientation turns the page by swapping its width and height; sectionIndex counts sections from 1 and limits the change to one section, omit it to change every section). Section breaks: {action:"insert",type:"sectionBreak",anchor,position,breakType:"nextPage"|"continuous"|"evenPage"|"oddPage"|"nextColumn"}; the section before the break keeps the page setup and headers of the section it splits. Headers and footers: {action:"headerFooter",operation:"set",targetType:"header"|"footer",text,alignment,sectionIndex (from 1, default the first section)}; {PAGE} and {NUMPAGES} inside text become live page-number fields. Images: width and height are integers in EMU (914400 per inch, 12700 per point, 9525 per pixel at 96 dpi); give one and the other follows the image aspect ratio, give neither and the image keeps its own size fitted to the page. Tables created by insert type:"table" or by Markdown repeat their header row across pages by default. Equations: {action:"equation",operation:"insert"|"replace",latex,mode:"auto"|"latex"|"plain"}; LaTeX that contains a backslash, ^ or _ becomes a real Word equation, and mode:"plain" keeps the text exactly as written. IDs are session-scoped: valid only in the issuing session, must not be stored across sessions, and duplicate client-supplied ids are rejected.',
|
|
42253
|
+
{ workflow: true, ecma: true }
|
|
41690
42254
|
),
|
|
41691
42255
|
save_document: withSharedDocs(
|
|
41692
|
-
`Validate then persist the session docx; the default policy permits pre-existing validation errors when no new normalized error signatures are detected. Save-blocked remedies are xpath-specific (drawing docPr/id, table tbl/tr/tc, relationships, header/footer parts, session tracking-attribute leaks each name their own fix). Optional commit params: message (commit subject, single-line, max 500 chars; newlines become spaces; blank/omitted falls back to \`save_document: rev <n> (<k> ops since last save)\`); author (max 100 chars; blank/omitted falls back to ASTra Agent). Over-long message/author fail at the handler with E_VALIDATION plus a shorten-and-retry remedy (never a bare MCP -32602: caps are enforced in the handler, not the wire schema). A successful save clears the session dirty flag (revision is re-synced to the file on disk). A file that already had validator errors when it was opened is saved when the edit adds none: the envelope then carries validation:{ok:false,tolerated:true,preExistingErrors,introducedErrors:0,messages}. An edit that adds an error is refused with E_VALIDATION naming both numbers. Pass {"strict":true} (or start the server with --strict-validation, or set ASTRA_STRICT_VALIDATION=1) to refuse every validator error as before. Paragraphs that share a w14:paraId are renumbered by the save: the first keeps its id, each later copy gets a new one and is listed in normalized, and the state before is recorded in the history (revert_document returns it); strict refuses such a document instead. The success envelope also carries revisionsPending (every revision element left in the saved package; a move counts as each of its markers) plus a revisionsRemedy when >0. The success envelope also carries advisory changedValues [{nodeId,before,after}] (old saved text -> new live text, bounded: at most ${CHANGED_VALUES_MAX} entries, each side at most ${CHANGED_VALUE_TEXT_MAX} chars, truncated flag honest; empty + reason when no baseline is computable). When the session carries a style guide (new_document/compose_document styleGuide), the envelope also carries advisory styleWarnings [{nodeId,rule,message}] (table contrast, chart duplicate colors, split-table orphans), never blocking. Example {"sessionId":"sess_abc"} -> {"ok":true,"path":"documents/report.docx"}. Always save after execute_batch; close afterwards. IDs are session-scoped: valid only in the issuing session, must not be stored across sessions, and duplicate client-supplied ids are rejected
|
|
42256
|
+
`Validate then persist the session docx; the default policy permits pre-existing validation errors when no new normalized error signatures are detected. Save-blocked remedies are xpath-specific (drawing docPr/id, table tbl/tr/tc, relationships, header/footer parts, session tracking-attribute leaks each name their own fix). Optional commit params: message (commit subject, single-line, max 500 chars; newlines become spaces; blank/omitted falls back to \`save_document: rev <n> (<k> ops since last save)\`); author (max 100 chars; blank/omitted falls back to ASTra Agent). Over-long message/author fail at the handler with E_VALIDATION plus a shorten-and-retry remedy (never a bare MCP -32602: caps are enforced in the handler, not the wire schema). A successful save clears the session dirty flag (revision is re-synced to the file on disk). When dc:title is still a template placeholder ("Blank Document", "Report") the save writes it from the cover title, the first Title or the first Heading 1 and reports title and titleSetFrom; a title already set is never changed. A file that already had validator errors when it was opened is saved when the edit adds none: the envelope then carries validation:{ok:false,valid:false,errorCount,tolerated:true,preExistingErrors,introducedErrors:0,messages}; a clean save carries validation:{ok:true,valid:true,errorCount:0}. An edit that adds an error is refused with E_VALIDATION naming both numbers, with errorCount and the first 10 errors as errors[{description,xPath}] (validate_document lists them all). Pass {"strict":true} (or start the server with --strict-validation, or set ASTRA_STRICT_VALIDATION=1) to refuse every validator error as before. Paragraphs that share a w14:paraId are renumbered by the save: the first keeps its id, each later copy gets a new one and is listed in normalized, and the state before is recorded in the history (revert_document returns it); strict refuses such a document instead. The success envelope also carries revisionsPending (every revision element left in the saved package; a move counts as each of its markers) plus a revisionsRemedy when >0. The success envelope also carries advisory changedValues [{nodeId,before,after}] (old saved text -> new live text, bounded: at most ${CHANGED_VALUES_MAX} entries, each side at most ${CHANGED_VALUE_TEXT_MAX} chars, truncated flag honest; empty + reason when no baseline is computable). When the session carries a style guide (new_document/compose_document styleGuide), the envelope also carries advisory styleWarnings [{nodeId,rule,message}] (table contrast, chart duplicate colors, split-table orphans), never blocking. Example {"sessionId":"sess_abc"} -> {"ok":true,"path":"documents/report.docx"}. Always save after execute_batch; close afterwards. IDs are session-scoped: valid only in the issuing session, must not be stored across sessions, and duplicate client-supplied ids are rejected.`,
|
|
42257
|
+
{ workflow: true }
|
|
41693
42258
|
),
|
|
41694
42259
|
close_document: withSharedDocs(
|
|
41695
42260
|
'Evict a session and release its lock/memory. Returns {ok:true,sessionId,dirty,discardedRevisions,warning?}: a dirty close warns (dirty:true + warning naming the discarded unsaved revision count) instead of silently discarding; save first when the warning matters. Example {"sessionId":"sess_abc"} -> {"ok":true,"dirty":false,"discardedRevisions":0}. Close when finished; expired sessions (30min idle) must be re-opened.'
|
|
@@ -41697,7 +42262,7 @@ var LEAN6_DESCRIPTIONS = {
|
|
|
41697
42262
|
};
|
|
41698
42263
|
var GIT_HISTORY_DESCRIPTIONS = {
|
|
41699
42264
|
get_document_history: withSharedDocs(
|
|
41700
|
-
'List the saved git history for a session (newest first, read-only: never mutates, never flips dirty). Paginate with limit (1-100, default 20) and offset (default 0). Returns {ok:true,sessionId,documentPath,currentCommit,totalCommits,history:[{oid,message,author,timestamp,timestampMs,opCount,affectedNodeIds,resultingHeadOid}]} (timestampMs = ms-precision epoch millis; commit-time clock for tracked commits, git seconds for older ones; opCount is always present (0 for pre-tracking commits and heals-only saves: it folds operations, not normalizer rewrites); affectedNodeIds is always present ([] when untracked); resultingHeadOid always equals oid (append-only; refs never move). Example {"sessionId":"sess_abc","limit":10} -> history slice. Use the oids with revert_document (or preview_revert first) to restore an earlier saved state.
|
|
42265
|
+
'List the saved git history for a session (newest first, read-only: never mutates, never flips dirty). Paginate with limit (1-100, default 20) and offset (default 0). Returns {ok:true,sessionId,documentPath,currentCommit,totalCommits,history:[{oid,message,author,timestamp,timestampMs,opCount,affectedNodeIds,resultingHeadOid}]} (timestampMs = ms-precision epoch millis; commit-time clock for tracked commits, git seconds for older ones; opCount is always present (0 for pre-tracking commits and heals-only saves: it folds operations, not normalizer rewrites); affectedNodeIds is always present ([] when untracked); resultingHeadOid always equals oid (append-only; refs never move). Example {"sessionId":"sess_abc","limit":10} -> history slice. Use the oids with revert_document (or preview_revert first) to restore an earlier saved state. '
|
|
41701
42266
|
),
|
|
41702
42267
|
preview_revert: withSharedDocs(
|
|
41703
42268
|
`Read-only WOULD-restore preview for revert_document (never mutates: no revision bump, no disk write, no redo-stack change). Target one commit: {steps:N} walks back N saved commits from the head (1 = the previous saved commit, must be < totalCommits) OR {commitOid:"<40-hex>"} jumps to that commit (mutually exclusive). Returns {ok:true,sessionId,targetOid,targetMessage,targetAuthor,stepsBack,affectedFiles,changedValues,changedValuesTruncated,changedValuesPositional,dirty,dirtyHandling,currentHeadOid,totalCommits} where affectedFiles lists package paths that differ from the target (word/document.xml plus tracked extras). The preview also carries advisory changedValues [{nodeId,before,after}] (live current text -> target restore text, bounded: at most ${CHANGED_VALUES_MAX} entries, each side at most ${CHANGED_VALUE_TEXT_MAX} chars, truncated flag honest; changedValuesPositional:true means either side held orphan ids (no shared id across the boundary, common under duplicate native ids) so before/after pairing is positional, not identity; empty + changedValuesReason when the target range is not computable). Example {"sessionId":"sess_abc","steps":1} -> preview summary. Preview first when a revert would discard unsaved edits.`
|
|
@@ -41714,19 +42279,19 @@ var EXPORT_AUDIT_TRAIL_DESCRIPTION = withSharedDocs(
|
|
|
41714
42279
|
);
|
|
41715
42280
|
var PARITY_DESCRIPTIONS = {
|
|
41716
42281
|
validate_document: withSharedDocs(
|
|
41717
|
-
'Preflight read-only OOXML audit for a session: runs the same validator gate save_document enforces, before anything is written. Detects schema violations while they are still cheap to fix. Strictly read-only: zero session mutation (revision and dirty flag untouched), zero disk writes, zero history commits. Returns {ok:true,sessionId,revision,dirty,validation:{valid,errorCount,errors}} where errors carry description + xPath (first 10, same shape as the save-blocked envelope). The verdict is on the document as a save would write it (children put in schema order first); when that save would repair something the result also carries pendingNormalizations (a list of repairs, absent when none), so a valid verdict is not mistaken for "valid exactly as it stands". Use before save_document when the package may be non-conforming; save_document re-gates before writing. Example {"sessionId":"sess_abc"} -> {"ok":true,"validation":{"valid":true,"errorCount":0,"errors":[]}}.
|
|
42282
|
+
'Preflight read-only OOXML audit for a session: runs the same validator gate save_document enforces, before anything is written. Detects schema violations while they are still cheap to fix. Strictly read-only: zero session mutation (revision and dirty flag untouched), zero disk writes, zero history commits. Returns {ok:true,sessionId,revision,dirty,validation:{valid,errorCount,errors}} where errors carry description + xPath (first 10, same shape as the save-blocked envelope). The verdict is on the document as a save would write it (children put in schema order first); when that save would repair something the result also carries pendingNormalizations (a list of repairs, absent when none), so a valid verdict is not mistaken for "valid exactly as it stands". Use before save_document when the package may be non-conforming; save_document re-gates before writing. Example {"sessionId":"sess_abc"} -> {"ok":true,"validation":{"valid":true,"errorCount":0,"errors":[]}}. '
|
|
41718
42283
|
),
|
|
41719
42284
|
get_stats: withSharedDocs(
|
|
41720
|
-
'Token-cheap document census for a session: words, paragraphs, tables, images, and an estimated editorial page budget (layout-weighted by words, tables, images, and explicit page breaks) in a single small envelope. Counters are the exact engine counters behind CLI stats, so both surfaces always agree. Physical pagination is finalized when exporting to PDF via LibreOffice. Read-only: never mutates, never bumps the revision. Returns {ok:true,sessionId,revision,dirty,words,paragraphs,tables,images,pages}. Use instead of inspect_document when only counts are needed (a full TOON dump costs thousands of tokens for the same question). Example {"sessionId":"sess_abc"} -> {"ok":true,"words":42,"paragraphs":5}.
|
|
42285
|
+
'Token-cheap document census for a session: words, paragraphs, tables, callouts, images, and an estimated editorial page budget (layout-weighted by words, tables, images, and explicit page breaks) in a single small envelope. Counters are the exact engine counters behind CLI stats, so both surfaces always agree. Physical pagination is finalized when exporting to PDF via LibreOffice. Read-only: never mutates, never bumps the revision. Returns {ok:true,sessionId,revision,dirty,words,paragraphs,tables,callouts,images,pages}. A callout (a "> [!NOTE]" alert or a callout op: a one-cell table with a fill) is counted in callouts and not in tables. Use instead of inspect_document when only counts are needed (a full TOON dump costs thousands of tokens for the same question). Example {"sessionId":"sess_abc"} -> {"ok":true,"words":42,"paragraphs":5}. '
|
|
41721
42286
|
),
|
|
41722
42287
|
sanitize_document: withSharedDocs(
|
|
41723
|
-
`Scrub an open session document. By default it removes revision ids, authorship, embedded EXIF and hidden text PII. Tracked changes and comments are kept, with the text of every tracked deletion, unless you ask: acceptAllRevisions:true accepts all tracked changes (deleted text is removed) and stripComments:true removes comments. The CLI presets (bare sanitize, --client-safe, --metadata-only, --redact-pii) turn both on; this tool does not. Macro/active parts are removed on request. It uses the same engine scrub path as CLI sanitize. Body text is kept. Options mirror the engine scrub keys 1:1 (zeroRetention, purgeRSIDs, purgeAuthors, purgeExif, purgePII, acceptAllRevisions, stripComments, replacementAuthor, allowMacros, stripMacros); omitted options take engine defaults. With acceptAllRevisions it returns E_VALIDATION and no file when revision markup remains that cannot be resolved. allowMacros and stripMacros together fail with E_VALIDATION. ${piiCoverageSentence()} Returns {ok:true,sessionId,revision,dirty,removedRSIDs,purgedAuthors,strippedExifBytes,purgedPII,bodyPII,piiCoverage,acceptedRevisions,strippedComments,strippedMacros,macroParts,filesTouched,droppedRogue,...} plus the standard redoCleared signal when a redo chain is dropped. Example {"sessionId":"sess_abc","stripMacros":true} -> scrubbed session at a new revision (save after).
|
|
42288
|
+
`Scrub an open session document. By default it removes revision ids, authorship, embedded EXIF and hidden text PII. Tracked changes and comments are kept, with the text of every tracked deletion, unless you ask: acceptAllRevisions:true accepts all tracked changes (deleted text is removed) and stripComments:true removes comments. The CLI presets (bare sanitize, --client-safe, --metadata-only, --redact-pii) turn both on; this tool does not. Macro/active parts are removed on request. It uses the same engine scrub path as CLI sanitize. Body text is kept. Options mirror the engine scrub keys 1:1 (zeroRetention, purgeRSIDs, purgeAuthors, purgeExif, purgePII, acceptAllRevisions, stripComments, replacementAuthor, allowMacros, stripMacros); omitted options take engine defaults. With acceptAllRevisions it returns E_VALIDATION and no file when revision markup remains that cannot be resolved. allowMacros and stripMacros together fail with E_VALIDATION. ${piiCoverageSentence()} Returns {ok:true,sessionId,revision,dirty,removedRSIDs,purgedAuthors,strippedExifBytes,purgedPII,bodyPII,piiCoverage,acceptedRevisions,strippedComments,strippedMacros,macroParts,filesTouched,droppedRogue,...} plus the standard redoCleared signal when a redo chain is dropped. Example {"sessionId":"sess_abc","stripMacros":true} -> scrubbed session at a new revision (save after). `
|
|
41724
42289
|
),
|
|
41725
42290
|
normalize_document: withSharedDocs(
|
|
41726
|
-
'Canonical repair for an open session document: runs the same allowlist normalizer CLI normalize calls (verified tag renames, whitelisted default infills, schema-sequence sorts) over the live package, then refreshes the session view and proves the result with a read-only validation verdict. Output bytes match CLI normalize on the same input. When the package is still invalid afterwards, the session is restored untouched and the call fails with E_VALIDATION plus a fix-and-retry remedy (same contract as CLI normalize writing nothing on failure). After the normalizer it repairs known validator error kinds one rule at a time; a rule that adds a validator error or changes the visible text is dropped and listed in rolledBack. Returns {ok:true,sessionId,revision,dirty,normalized,remaining,rolledBack,validation:{valid:true,...}} where normalized lists every rewrite once and remaining counts the validator errors left (-1 when the validator did not run), plus the standard redoCleared signal when a redo chain is dropped. Example {"sessionId":"sess_abc"} -> {"ok":true,"normalized":[...]}. Save after to persist.
|
|
42291
|
+
'Canonical repair for an open session document: runs the same allowlist normalizer CLI normalize calls (verified tag renames, whitelisted default infills, schema-sequence sorts) over the live package, then refreshes the session view and proves the result with a read-only validation verdict. Output bytes match CLI normalize on the same input. When the package is still invalid afterwards, the session is restored untouched and the call fails with E_VALIDATION plus a fix-and-retry remedy (same contract as CLI normalize writing nothing on failure). After the normalizer it repairs known validator error kinds one rule at a time; a rule that adds a validator error or changes the visible text is dropped and listed in rolledBack. Returns {ok:true,sessionId,revision,dirty,normalized,remaining,rolledBack,validation:{valid:true,...}} where normalized lists every rewrite once and remaining counts the validator errors left (-1 when the validator did not run), plus the standard redoCleared signal when a redo chain is dropped. Example {"sessionId":"sess_abc"} -> {"ok":true,"normalized":[...]}. Save after to persist. '
|
|
41727
42292
|
),
|
|
41728
42293
|
import_document: withSharedDocs(
|
|
41729
|
-
'Ingest an on-disk Markdown file into the OPEN session (the file-ingress half compose_document lacks: compose takes an inline string only). Reads the file under the workspace jail, then runs the identical compose pipeline (strict compile gate, quotas, template-seed check, inline-span splitting), so the result is byte-equal to composing the same Markdown inline. sessionId is strictly required: the tool never auto-creates sessions (fresh templated document: new_document first, then import; existing file: open_document first, then import). Accepts .md/.markdown/.txt; .html/.htm refuse with E_VALIDATION (convert to Markdown first, same verdict as CLI import); .docx refuses with E_VALIDATION naming open_document (open .docx files directly). Outside-jail reads fail E_SANDBOX, missing files E_IO, oversize files E_LIMIT. Optional allowedDir (image refs), template (report|memo|blank seed check), and styleGuide (same attach semantics as compose_document). Optional dollarMath (default false) treats $...$ as inline math; off by default because prices and shell text can look like math. Example {"sessionId":"sess_abc","path":"documents/notes.md"} -> compose envelope.
|
|
42294
|
+
'Ingest an on-disk Markdown file into the OPEN session (the file-ingress half compose_document lacks: compose takes an inline string only). Reads the file under the workspace jail, then runs the identical compose pipeline (strict compile gate, quotas, template-seed check, inline-span splitting), so the result is byte-equal to composing the same Markdown inline. sessionId is strictly required: the tool never auto-creates sessions (fresh templated document: new_document first, then import; existing file: open_document first, then import). Accepts .md/.markdown/.txt; .html/.htm refuse with E_VALIDATION (convert to Markdown first, same verdict as CLI import); .docx refuses with E_VALIDATION naming open_document (open .docx files directly). Outside-jail reads fail E_SANDBOX, missing files E_IO, oversize files E_LIMIT. Optional allowedDir (image refs), template (report|memo|blank seed check), and styleGuide (same attach semantics as compose_document). Optional dollarMath (default false) treats $...$ as inline math; off by default because prices and shell text can look like math. Example {"sessionId":"sess_abc","path":"documents/notes.md"} -> compose envelope. '
|
|
41730
42295
|
)
|
|
41731
42296
|
};
|
|
41732
42297
|
|
|
@@ -41768,7 +42333,7 @@ var InspectDocumentSchema = import_zod4.z.object({
|
|
|
41768
42333
|
// defaults (offset 0, limit 50, window 5) are applied manually in
|
|
41769
42334
|
// handleInspectDocument AFTER the explicit-presence check below.
|
|
41770
42335
|
offset: import_zod4.z.number().int().min(0).optional(),
|
|
41771
|
-
limit: import_zod4.z.number().int().min(1).max(200).optional(),
|
|
42336
|
+
limit: import_zod4.z.number().int().min(1).max(200, "limit is at most 200 per call; use offset and nextOffset to read more").optional(),
|
|
41772
42337
|
aroundNode: import_zod4.z.string().min(1).max(256).optional(),
|
|
41773
42338
|
window: import_zod4.z.number().int().min(1).max(20).optional(),
|
|
41774
42339
|
maxTokens: import_zod4.z.number().int().min(200).max(8e3).optional(),
|
|
@@ -41776,7 +42341,7 @@ var InspectDocumentSchema = import_zod4.z.object({
|
|
|
41776
42341
|
// FX-13: review records (include) page on their own window, so they never move the node window.
|
|
41777
42342
|
// reviewLimit above 50 is clamped to 50 by the handler, as limit was before.
|
|
41778
42343
|
reviewOffset: import_zod4.z.number().int().min(0).optional(),
|
|
41779
|
-
reviewLimit: import_zod4.z.number().int().min(1).max(200).optional(),
|
|
42344
|
+
reviewLimit: import_zod4.z.number().int().min(1).max(200, "reviewLimit is at most 200 per call; use reviewOffset to read more").optional(),
|
|
41780
42345
|
commentId: import_zod4.z.number().int().nonnegative().optional(),
|
|
41781
42346
|
revisionId: import_zod4.z.number().int().nonnegative().optional()
|
|
41782
42347
|
}).strict();
|
|
@@ -42213,7 +42778,7 @@ var import_zod5 = require("zod");
|
|
|
42213
42778
|
var COMPOSE_MARKDOWN_MAX = 5e5;
|
|
42214
42779
|
var COMPOSE_VALIDATION_ERRORS_MAX = 3;
|
|
42215
42780
|
function hasGuideScheme(ref) {
|
|
42216
|
-
return
|
|
42781
|
+
return hasUriScheme(ref.trim());
|
|
42217
42782
|
}
|
|
42218
42783
|
function guideSandboxError(message, remedy) {
|
|
42219
42784
|
return Object.assign(new Error(message), { code: "E_SANDBOX", remedy });
|
|
@@ -42251,9 +42816,13 @@ var ComposeDocumentSchema = import_zod5.z.object({
|
|
|
42251
42816
|
// on purpose so the hint (not a bare schema error) reaches the caller.
|
|
42252
42817
|
styleGuide: import_zod5.z.string().max(1024).optional(),
|
|
42253
42818
|
// Read $...$ as inline math (off by default: prices and shell text can look like math).
|
|
42254
|
-
dollarMath: import_zod5.z.boolean().optional()
|
|
42819
|
+
dollarMath: import_zod5.z.boolean().optional(),
|
|
42820
|
+
// Placement of a later compose: a node id, start or end. Absent = append after the last node.
|
|
42821
|
+
anchor: import_zod5.z.string().min(1).max(256).optional(),
|
|
42822
|
+
// Side of anchor the first block goes on (default after). Needs anchor.
|
|
42823
|
+
position: import_zod5.z.enum(["before", "after"]).optional()
|
|
42255
42824
|
}).strict();
|
|
42256
|
-
var COMPOSE_DOCUMENT_DESCRIPTION = 'Compose a Markdown string into the open session document (appends after existing content,
|
|
42825
|
+
var COMPOSE_DOCUMENT_DESCRIPTION = 'Compose a Markdown string into the open session document (appends after existing content, or at anchor with position before|after; an unchanged built-in template created in this session has its sample content replaced). A document can be built in several compose calls: the first call of a session mints ids md_0001..., later calls mint md2_0001..., md3_0001..., so ids never collide; pass anchor (a node id, start or end) and position to add a section in the middle without another tool. Runs the engine compose path, then applies the resulting operations through the same execute_batch path (strict schema gate, per-chunk quotas, structural checks, per-chunk atomic rollback, then inline-span run splitting), so every gate a batch meets applies here unchanged. Supported input: #/##/### headings (real Heading1..3 styles), blank-line paragraphs (single newlines join with a space), flat bullet (-, *, +) and ordered (1., 1)) lists with real numbering, GitHub-style tables with a header separator row (header row shaded and repeated), --- dividers, fenced or indented code blocks (monospace, line breaks kept), [^label] footnotes with definitions, inline spans (**bold**, *italic*, `code`, [text](url)) applied as runs with real hyperlink targets, plain `> quote` blocks (written in the Quote paragraph style: blank > lines start a new paragraph, > > nests up to 3 levels, flat lists and fenced code are kept inside, a table, heading or image inside a quote fails), and standalone block images/charts () resolved from allowedDir. Out-of-scope input fails closed with E_VALIDATION plus a 1-based line number and writes nothing: raw HTML (except [!NOTE]-style alert callouts, which compile to callout containers and keep bold, italic, code and links), custom styles, deeper headings, nested lists or tables, task items. Block image/chart refs need allowedDir pointing at the folder holding the files (refs stay jailed inside it; without allowedDir they fail closed); remote URL refs fail closed; inline data-URL block images bypass allowedDir by design (bytes decode in memory through the same image gates). Sources compiling past 500 operations apply as sequential 500-operation chunks; the compose is atomic as a whole: when any chunk fails, every chunk before it is rewound and the document stays as it was. Citation numbers and the References list restart in each compose call. Optional template (report|memo|blank) is accepted on the first compose only and requires the open document to still hold that template seed, otherwise composition fails before any write (for a fresh templated document call new_document first, then compose_document with template), and placement is append after existing content except for an unchanged built-in template created in this session. Reopened documents and external templates keep existing content. Returns {ok:true,opCount,appliedCount,revision,createdNodeIds,results,validation:{valid,errorCount,errors}} where createdNodeIds lists new node ids in document order (use them as anchors without re-inspecting) and validation proves the post-compose package state via a read-only OOXML check (save_document re-gates before writing). A compose that drops a non-empty redo stack signals it (redoCleared:true + redoClearedWarning naming the depth): a fresh edit invalidates redo. Optional styleGuide (bundled guide name or guide .md path, jailed like image refs: explicit allowedDir else the session anchor; remote URLs fail closed) replaces the session guide (not a one-batch override: later batches inherit it until another compose replaces it) and skins this compose (table/header-fill, chart-palette, callout, and code defaults apply where ops omit params; explicit params still win). Guides cannot be detached: open a new session to work without a guide. With no guide, compose uses the built-in Default Corporate look (navy table header, bold white header text, striped rows). styleGuide "legacy" gives the older grey header for this call only and is not stored on the session. An italic-only line directly under the opening # title becomes the Subtitle; a [!NOTE]-style line outside a blockquote stays plain text and comes back in warnings. Later batches inherit it, inspect_document shows its compact summary, save_document reports advisory styleWarnings. Unknown names fail with E_VALIDATION naming the available set. Math: $$ ... $$ or \\[ ... \\] blocks become display equations and \\(...\\) becomes inline math (one line, up to 500 characters; refused inside links and table cells). Display blocks are top level only (inside a list item or alert body they fail closed). Optional dollarMath (default false) treats $...$ as inline math; off by default because prices and shell text can look like math. Workflow: open_document -> compose_document -> save_document. Example {"sessionId":"sess_abc","markdown":"# Plan\\n\\n- a\\n- b\\n"} (add "allowedDir":"<dir>" for image/chart refs, "template":"report|memo|blank" to verify the seed). IDs are session-scoped: valid only in the issuing session, must not be stored across sessions. When dc:title is still a template placeholder it is set from the first Title or Heading 1 (titleSetFrom says so); a title already set is kept. persistentIds maps each created paragraph id (md_0003) to the paragraph id the saved file carries, so a reopened session can be addressed with it (tables are renumbered t_001.. by position on reopen).';
|
|
42257
42826
|
function persistentParagraphIds(ast, createdIds) {
|
|
42258
42827
|
const wanted = new Set(createdIds);
|
|
42259
42828
|
const out = {};
|
|
@@ -42316,7 +42885,7 @@ function assertPathAllowed(allowedDir, roots) {
|
|
|
42316
42885
|
if (segments.includes("..")) {
|
|
42317
42886
|
throw Object.assign(new Error('allowedDir contains forbidden ".." traversal'), {
|
|
42318
42887
|
code: "E_SANDBOX",
|
|
42319
|
-
remedy:
|
|
42888
|
+
remedy: `Use a folder ${describeAllowedFolders()}.`
|
|
42320
42889
|
});
|
|
42321
42890
|
}
|
|
42322
42891
|
const resolved = path13.resolve(trimmed);
|
|
@@ -42341,7 +42910,7 @@ function assertPathAllowed(allowedDir, roots) {
|
|
|
42341
42910
|
new Error(`allowedDir is outside the allowed directories (${roots.join(", ")})`),
|
|
42342
42911
|
{
|
|
42343
42912
|
code: "E_SANDBOX",
|
|
42344
|
-
remedy:
|
|
42913
|
+
remedy: `Point allowedDir at a folder ${describeAllowedFolders()}.`
|
|
42345
42914
|
}
|
|
42346
42915
|
);
|
|
42347
42916
|
}
|
|
@@ -42366,7 +42935,7 @@ async function handleComposeDocument(store, rawArgs) {
|
|
|
42366
42935
|
} catch (err) {
|
|
42367
42936
|
return composeErrorFrom(
|
|
42368
42937
|
err,
|
|
42369
|
-
|
|
42938
|
+
`Point allowedDir at a folder ${describeAllowedFolders()}.`
|
|
42370
42939
|
);
|
|
42371
42940
|
}
|
|
42372
42941
|
}
|
|
@@ -42382,7 +42951,7 @@ async function handleComposeDocument(store, rawArgs) {
|
|
|
42382
42951
|
);
|
|
42383
42952
|
}
|
|
42384
42953
|
try {
|
|
42385
|
-
|
|
42954
|
+
validateComposedOperations(operations);
|
|
42386
42955
|
} catch (err) {
|
|
42387
42956
|
return composeErrorFrom(
|
|
42388
42957
|
err,
|
|
@@ -42435,10 +43004,15 @@ async function handleComposeDocument(store, rawArgs) {
|
|
|
42435
43004
|
}
|
|
42436
43005
|
}
|
|
42437
43006
|
const sessionGuide = legacyLook ? "legacy" : handle.styleGuide;
|
|
42438
|
-
|
|
43007
|
+
const idPrefix = nextComposeIdPrefix(handle.ast);
|
|
43008
|
+
if (sessionGuide !== void 0 || idPrefix !== "md" || parsed.data.anchor !== void 0) {
|
|
42439
43009
|
operations = compileMarkdown(parsed.data.markdown, {
|
|
42440
43010
|
...canonicalAllowedDir !== void 0 ? { allowedDir: canonicalAllowedDir } : {},
|
|
42441
|
-
styleGuide: sessionGuide
|
|
43011
|
+
...sessionGuide !== void 0 ? { styleGuide: sessionGuide } : {},
|
|
43012
|
+
...parsed.data.dollarMath ? { dollarMath: true } : {},
|
|
43013
|
+
idPrefix,
|
|
43014
|
+
...parsed.data.anchor !== void 0 ? { anchor: parsed.data.anchor } : {},
|
|
43015
|
+
...parsed.data.position !== void 0 ? { position: parsed.data.position } : {}
|
|
42442
43016
|
});
|
|
42443
43017
|
({ shadows: opShadows, actions: opActions, targets: opTargets } = shadowsOf(operations));
|
|
42444
43018
|
}
|
|
@@ -42446,7 +43020,10 @@ async function handleComposeDocument(store, rawArgs) {
|
|
|
42446
43020
|
...canonicalAllowedDir !== void 0 ? { allowedDir: canonicalAllowedDir } : {},
|
|
42447
43021
|
...template !== void 0 ? { template } : {},
|
|
42448
43022
|
...sessionGuide !== void 0 ? { styleGuide: sessionGuide } : {},
|
|
42449
|
-
...parsed.data.dollarMath ? { dollarMath: true } : {}
|
|
43023
|
+
...parsed.data.dollarMath ? { dollarMath: true } : {},
|
|
43024
|
+
idPrefix,
|
|
43025
|
+
...parsed.data.anchor !== void 0 ? { anchor: parsed.data.anchor } : {},
|
|
43026
|
+
...parsed.data.position !== void 0 ? { position: parsed.data.position } : {}
|
|
42450
43027
|
});
|
|
42451
43028
|
if (!aggregate.success) {
|
|
42452
43029
|
const first = aggregate.errors[0];
|
|
@@ -42515,6 +43092,7 @@ async function handleComposeDocument(store, rawArgs) {
|
|
|
42515
43092
|
dirty: store.isDirty(handle),
|
|
42516
43093
|
createdNodeIds,
|
|
42517
43094
|
...Object.keys(persistentIds).length > 0 ? { persistentIds } : {},
|
|
43095
|
+
...aggregate.titleSetFrom ? { titleSetFrom: aggregate.titleSetFrom } : {},
|
|
42518
43096
|
results,
|
|
42519
43097
|
validation: {
|
|
42520
43098
|
valid: validation.ok,
|
|
@@ -42802,6 +43380,13 @@ async function handleExecuteBatch(store, input) {
|
|
|
42802
43380
|
const afterOrder = collectAstIdTexts(handle.ast).order;
|
|
42803
43381
|
const createdNodeIds = afterOrder.filter((id) => !beforeIds.has(id));
|
|
42804
43382
|
const results = attributeCreatedIds(opActions, opShadows, createdNodeIds);
|
|
43383
|
+
const coverIndex = opActions.indexOf("coverPage");
|
|
43384
|
+
if (coverIndex >= 0) {
|
|
43385
|
+
const afterSet = new Set(afterOrder);
|
|
43386
|
+
const named = new Set(opTargets);
|
|
43387
|
+
const removed = [...beforeIds].filter((id) => !afterSet.has(id) && !named.has(id));
|
|
43388
|
+
if (removed.length > 0) results[coverIndex].removedSeedNodes = removed;
|
|
43389
|
+
}
|
|
42805
43390
|
store.accumulatePendingAudit(handle, res.appliedCount, [...opTargets, ...createdNodeIds]);
|
|
42806
43391
|
const batchRevisionsPending = typeof res.revisionsPending === "number" ? res.revisionsPending : countPendingRevisionsInParser(handle.parser);
|
|
42807
43392
|
const batchRevisionsRemedy = typeof res.revisionsRemedy === "string" ? String(res.revisionsRemedy) : revisionsRemedy(batchRevisionsPending, res.revisionResolution?.unresolved);
|
|
@@ -42911,6 +43496,11 @@ async function executeSaveDocument(store, input) {
|
|
|
42911
43496
|
const strict = parsed.data.strict ?? strictValidationDefault();
|
|
42912
43497
|
const paraIdPolicy = strict ? "refuse" : "renumber";
|
|
42913
43498
|
const dupPreState = paraIdPolicy === "renumber" && handle.parser.getDuplicateParaIds().length > 0 ? { xml: handle.parser.readDocumentXml(), extras: collectTrackedExtrasForGit(handle.parser) } : null;
|
|
43499
|
+
let identity = {};
|
|
43500
|
+
try {
|
|
43501
|
+
identity = applyDocumentIdentity(handle.ast, handle.parser);
|
|
43502
|
+
} catch {
|
|
43503
|
+
}
|
|
42914
43504
|
const validation = await handle.parser.validateOOXML({ maxErrors: BASELINE_MAX_ERRORS, duplicateParaIds: paraIdPolicy });
|
|
42915
43505
|
const verdict = validation.ok ? { kind: "clean" } : strict ? { kind: "incomparable", reason: "no-baseline" } : judgeAgainstBaseline(validation, await resolveBaseline(handle));
|
|
42916
43506
|
if (verdict.kind === "introduced") throw introducedError("save_document", verdict);
|
|
@@ -42924,7 +43514,9 @@ async function executeSaveDocument(store, input) {
|
|
|
42924
43514
|
code: "E_VALIDATION",
|
|
42925
43515
|
// Phase C-1c: xpath-specific remedy (docPr/@id, tbl children,
|
|
42926
43516
|
// relationships each name their own fix).
|
|
42927
|
-
remedy: remedyForValidationError(xp, desc)
|
|
43517
|
+
remedy: remedyForValidationError(xp, desc) + (count ? " validate_document lists every error with its location." : ""),
|
|
43518
|
+
errorCount: validation.errors.length,
|
|
43519
|
+
errors: summarizeValidationErrors(validation.errors)
|
|
42928
43520
|
});
|
|
42929
43521
|
}
|
|
42930
43522
|
if (dupPreState) {
|
|
@@ -43060,6 +43652,8 @@ async function executeSaveDocument(store, input) {
|
|
|
43060
43652
|
...changedValuesReason ? { changedValuesReason } : {},
|
|
43061
43653
|
// Display-only: the core save-path hook healed during the rezip above.
|
|
43062
43654
|
normalized: saveNormalized,
|
|
43655
|
+
...identity.titleSetFrom ? { title: identity.title, titleSetFrom: identity.titleSetFrom } : {},
|
|
43656
|
+
...identity.creatorSet ? { creatorSet: identity.creatorSet } : {},
|
|
43063
43657
|
// Advisory style pass under the session guide (read-only, best-effort,
|
|
43064
43658
|
// never blocks the save). Absent without a guide. The stored chart
|
|
43065
43659
|
// reader mirrors validate/document-manager so persisted twin colors
|
|
@@ -44498,7 +45092,7 @@ var NewDocumentSchema = import_zod8.z.object({
|
|
|
44498
45092
|
"DEPRECATED: session ids are server-minted (sess_<uuid>); omit sessionId and use the returned id. Client-supplied values are rejected with E_VALIDATION (deterministic ids are test-only via --allow-client-session-ids)."
|
|
44499
45093
|
)
|
|
44500
45094
|
}).strict();
|
|
44501
|
-
var NEW_DOCUMENT_DESCRIPTION = 'Create a new .docx from a template (report|memo|blank, default blank) at outPath inside an allowed
|
|
45095
|
+
var NEW_DOCUMENT_DESCRIPTION = 'Create a new .docx from a template (report|memo|blank, default blank) at outPath inside an allowed folder (the folders named with --allowed-dir or ASTRA_EXTRA_DOC_ROOTS; only when none are named, ./documents and the system temp folder). Validates the fresh package before writing (E_VALIDATION when invalid, never writes corruption); refuses to overwrite without {force:true} (E_CONFLICT). Success returns {ok:true,path,sessionId,revision,template,bytes} with the file opened into a session (use sessionId for inspect/execute_batch/save). Example {"outPath":"documents/new-report.docx","template":"report"}. Session ids are server-minted (sess_<uuid>, DEPRECATED to supply sessionId: client-supplied values are rejected with E_VALIDATION; deterministic ids are test-only via --allow-client-session-ids). IDs are session-scoped: valid only in the issuing session, must not be stored across sessions. Optional styleGuide (bundled guide name or guide .md path, jailed to the document folder; remote URLs fail closed) attaches a house style to the session at creation: later batches inherit its table/chart/callout defaults, inspect_document shows its compact summary, save_document reports advisory styleWarnings. Guides cannot be detached afterwards: open a new session to work without a guide. Unknown names fail with E_VALIDATION naming the available set.';
|
|
44502
45096
|
function newDocError(code, message, remedy) {
|
|
44503
45097
|
return { ok: false, code, message, remedy };
|
|
44504
45098
|
}
|
|
@@ -44659,24 +45253,7 @@ async function handleNewDocument(store, rawArgs) {
|
|
|
44659
45253
|
// src/lib/mcp/handlers/parity.ts
|
|
44660
45254
|
var fs17 = __toESM(require("fs"));
|
|
44661
45255
|
var path19 = __toESM(require("path"));
|
|
44662
|
-
var AUDIT_VALIDATION_ERRORS_MAX = 10;
|
|
44663
45256
|
var IMPORT_FILE_MAX_BYTES = COMPOSE_MARKDOWN_MAX;
|
|
44664
|
-
function toAuditValidation(validation, max = AUDIT_VALIDATION_ERRORS_MAX) {
|
|
44665
|
-
const rawErrors = Array.isArray(validation.errors) ? validation.errors.slice(0, max) : [];
|
|
44666
|
-
const errors = rawErrors.map((e) => {
|
|
44667
|
-
const err = e;
|
|
44668
|
-
const description = String(
|
|
44669
|
-
typeof err.description === "string" && err.description || typeof err.message === "string" && err.message || "OOXML validation error"
|
|
44670
|
-
).slice(0, 500);
|
|
44671
|
-
const xp = typeof err.xPath === "string" ? err.xPath : typeof err.path === "string" ? err.path : void 0;
|
|
44672
|
-
return xp ? { description, xPath: xp } : { description };
|
|
44673
|
-
});
|
|
44674
|
-
return {
|
|
44675
|
-
valid: validation.ok,
|
|
44676
|
-
errorCount: Array.isArray(validation.errors) ? validation.errors.length : 0,
|
|
44677
|
-
errors
|
|
44678
|
-
};
|
|
44679
|
-
}
|
|
44680
45257
|
async function refreshSessionAst(handle) {
|
|
44681
45258
|
await refreshAstInPlace(handle.ast, handle.parser);
|
|
44682
45259
|
}
|
|
@@ -44758,6 +45335,7 @@ async function handleGetStats(store, input) {
|
|
|
44758
45335
|
words: stats.words,
|
|
44759
45336
|
paragraphs: stats.paragraphs,
|
|
44760
45337
|
tables: stats.tables,
|
|
45338
|
+
callouts: stats.callouts,
|
|
44761
45339
|
images: stats.images,
|
|
44762
45340
|
pages: stats.pages
|
|
44763
45341
|
};
|
|
@@ -44916,7 +45494,7 @@ async function handleNormalizeDocument(store, input) {
|
|
|
44916
45494
|
}
|
|
44917
45495
|
function readImportMarkdownFile(rawPath, roots = getMcpAllowedRoots()) {
|
|
44918
45496
|
if (typeof rawPath !== "string" || !rawPath.trim()) {
|
|
44919
|
-
throw toError("E_SANDBOX", "import path must be a non-empty string",
|
|
45497
|
+
throw toError("E_SANDBOX", "import path must be a non-empty string", `Pass a .md path ${describeAllowedFolders()}.`);
|
|
44920
45498
|
}
|
|
44921
45499
|
if (rawPath.includes("\0") || /%00/i.test(rawPath)) {
|
|
44922
45500
|
throw toError("E_SANDBOX", "import path contains a null byte", "Remove null bytes from the path and retry.");
|
|
@@ -45003,7 +45581,7 @@ function readImportMarkdownFile(rawPath, roots = getMcpAllowedRoots()) {
|
|
|
45003
45581
|
return normC === normR || normC.startsWith(normR + path19.sep);
|
|
45004
45582
|
});
|
|
45005
45583
|
if (!stillInside) {
|
|
45006
|
-
throw toError("E_SANDBOX", "import path points outside the allowed directory",
|
|
45584
|
+
throw toError("E_SANDBOX", "import path points outside the allowed directory", `Keep Markdown files ${describeAllowedFolders()}.`);
|
|
45007
45585
|
}
|
|
45008
45586
|
return buf.toString("utf8");
|
|
45009
45587
|
} finally {
|
|
@@ -45080,7 +45658,8 @@ var import_zod9 = require("zod");
|
|
|
45080
45658
|
// package.json
|
|
45081
45659
|
var package_default = {
|
|
45082
45660
|
name: "@attentionform/astra-mcp-server",
|
|
45083
|
-
|
|
45661
|
+
mcpName: "com.attentionform/astra-mcp-server",
|
|
45662
|
+
version: "0.4.1",
|
|
45084
45663
|
description: "Official Model Context Protocol (MCP) server for ASTra document IDE",
|
|
45085
45664
|
main: "dist/index.js",
|
|
45086
45665
|
types: "dist/index.d.ts",
|
|
@@ -45121,8 +45700,8 @@ var package_default = {
|
|
|
45121
45700
|
"adm-zip-gate": "node ../../scripts/check-adm-zip-imports.mjs"
|
|
45122
45701
|
},
|
|
45123
45702
|
dependencies: {
|
|
45124
|
-
"@attentionform/astra-engine": "^0.
|
|
45125
|
-
"@attentionform/ooxml-validator": "^0.
|
|
45703
|
+
"@attentionform/astra-engine": "^0.4.1",
|
|
45704
|
+
"@attentionform/ooxml-validator": "^0.4.1",
|
|
45126
45705
|
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
45127
45706
|
"@xmldom/xmldom": "0.8.15",
|
|
45128
45707
|
"adm-zip": "0.6.1",
|
|
@@ -45528,6 +46107,10 @@ async function main(argv = process.argv) {
|
|
|
45528
46107
|
return 0;
|
|
45529
46108
|
}
|
|
45530
46109
|
function isDirectEntry() {
|
|
46110
|
+
try {
|
|
46111
|
+
if (typeof require !== "undefined" && typeof module !== "undefined" && require.main === module) return true;
|
|
46112
|
+
} catch {
|
|
46113
|
+
}
|
|
45531
46114
|
const entry = process.argv?.[1] ?? "";
|
|
45532
46115
|
return entry.endsWith("/index.js") || entry.endsWith("\\index.js") || entry.endsWith("/index.ts") || entry.endsWith("astra-mcp-server");
|
|
45533
46116
|
}
|