@ansonlai/docx-redline-js 0.7.2 → 0.8.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.
Files changed (53) hide show
  1. package/AGENTS.md +15 -10
  2. package/ARCHITECTURE.md +30 -10
  3. package/CHANGELOG.md +539 -431
  4. package/README.md +331 -309
  5. package/adapters/xml-adapter.js +84 -56
  6. package/core/sha256.js +120 -0
  7. package/dist/docx-redline-js.esm.js +8982 -693
  8. package/dist/docx-redline-js.esm.js.map +4 -4
  9. package/dist/docx-redline-js.esm.min.js +102 -89
  10. package/dist/docx-redline-js.esm.min.js.map +4 -4
  11. package/dist/docx-redline.bundle.cjs +28305 -0
  12. package/dist/docx-redline.bundle.cjs.map +7 -0
  13. package/dist/docx-redline.bundle.js +28296 -0
  14. package/dist/docx-redline.bundle.js.map +7 -0
  15. package/docs/AGENT_FAST_START.md +2 -1
  16. package/docs/AGENT_KNOWLEDGE_BASE.md +480 -393
  17. package/docs/SKILL_AUTHORING.md +5 -2
  18. package/docs/TESTING.md +2 -1
  19. package/docs/schemas/document-operations.schema.json +29 -0
  20. package/document/docx-document.d.ts +39 -0
  21. package/document/docx-document.js +1010 -0
  22. package/document/zip-archive.js +258 -0
  23. package/engine/reconstruction-writer.js +18 -0
  24. package/index.d.ts +598 -531
  25. package/index.js +349 -340
  26. package/node/cli-help.js +5 -5
  27. package/node/cli.js +760 -754
  28. package/node/docx-document.js +11 -355
  29. package/node/index.d.ts +1 -1
  30. package/node/zip-archive.js +28 -38
  31. package/package.json +14 -11
  32. package/scripts/build.mjs +40 -5
  33. package/scripts/generate-word-comment-edge-case-fixture.ps1 +58 -0
  34. package/scripts/generate-word-comment-thread-fixture.ps1 +36 -0
  35. package/scripts/generate-word-header-footer-fixture.ps1 +32 -0
  36. package/scripts/generate-word-multi-section-fixture.ps1 +27 -0
  37. package/scripts/generate-word-package-integrity-cases.mjs +141 -0
  38. package/scripts/generate-word-thread-state-fixtures.ps1 +50 -0
  39. package/scripts/word-com-package-integrity.ps1 +98 -0
  40. package/services/batch-operation-orchestrator.js +262 -259
  41. package/services/comment-replies.js +96 -95
  42. package/services/comment-thread-parts.js +280 -0
  43. package/services/document-inspection.js +366 -365
  44. package/services/document-operation-applier.js +230 -202
  45. package/services/document-operation-contract.js +17 -2
  46. package/services/error-recovery.js +6 -0
  47. package/services/headers-footers.js +206 -0
  48. package/services/operation-batch-compiler.js +1 -0
  49. package/services/operation-preflight.js +231 -218
  50. package/services/package-parts.js +80 -0
  51. package/services/revision-token.js +11 -7
  52. package/services/standalone-docx-plumbing.js +631 -569
  53. package/services/standalone-operation-runner.d.ts +19 -3
package/index.js CHANGED
@@ -1,340 +1,349 @@
1
- /**
2
- * Standalone reconciliation entrypoint (no Word JS API dependencies).
3
- */
4
-
5
- // Adapters
6
- export { configureXmlProvider, parseOoxmlSafe } from './adapters/xml-adapter.js';
7
- export { configureLogger } from './adapters/logger.js';
8
- export { setDefaultAuthor, getDefaultAuthor, setPlatform, getPlatform } from './adapters/config.js';
9
-
10
- // Engine
11
- import {
12
- applyRedlineToOxml as applyRedlineToOxmlEngine,
13
- sanitizeAiResponse,
14
- parseOoxml,
15
- serializeOoxml
16
- } from './engine/oxml-engine.js';
17
- import { parseTable as parseMarkdownTable } from './pipeline/pipeline.js';
18
- import { wrapInDocumentFragment as wrapInDocumentFragmentShared } from './pipeline/serialization.js';
19
- import {
20
- buildSingleLineListStructuralFallbackPlan,
21
- executeSingleLineListStructuralFallback
22
- } from './orchestration/list-structural-fallback.js';
23
- import { withOoxmlSourceType } from './core/word-xml.js';
24
- export { containsTrackedChanges, getTrackedChangeAuthors } from './core/word-xml.js';
25
- export { validateRedlineOoxml } from './core/redline-validation.js';
26
- export { analyzeStructuredContent, planStructuredReplacement } from './pipeline/structured-content.js';
27
-
28
- /**
29
- * Standalone-safe redline wrapper.
30
- *
31
- * In non-Word runtimes, the engine can return `{ useNativeApi: true, hasChanges: true }`
32
- * without an OOXML payload for some format-only operations. Standalone callers cannot
33
- * complete that native fallback path, so normalize to a no-op with warnings.
34
- */
35
- export async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}) {
36
- const result = await applyRedlineToOxmlEngine(oxml, originalText, modifiedText, options);
37
- if (result?.useNativeApi && typeof result?.oxml !== 'string') {
38
- const existingWarnings = Array.isArray(result?.warnings) ? result.warnings : [];
39
- return withOoxmlSourceType({
40
- ...result,
41
- oxml,
42
- hasChanges: false,
43
- warnings: [
44
- ...existingWarnings,
45
- 'Standalone mode cannot execute native Word API fallback for this operation.'
46
- ]
47
- });
48
- }
49
- return result;
50
- }
51
-
52
- /**
53
- * Reconciles a Markdown table against an OOXML scope.
54
- *
55
- * This centralizes table-specific validation + reconciliation so Word add-in
56
- * and browser modules can share the same entrypoint.
57
- *
58
- * @param {string} oxml - OOXML scope to reconcile (paragraph/range/table package)
59
- * @param {string} originalText - Original visible text in that scope
60
- * @param {string} markdownTable - Markdown table text
61
- * @param {Object} [options={}] - Reconciliation options forwarded to applyRedlineToOxml
62
- * @returns {Promise<{ oxml: string, hasChanges: boolean, warnings?: string[], isMarkdownTable: boolean, tableData?: Object }>}
63
- */
64
- export async function reconcileMarkdownTableOoxml(oxml, originalText, markdownTable, options = {}) {
65
- const sourceOoxml = typeof oxml === 'string' ? oxml : '';
66
- const tableText = typeof markdownTable === 'string' ? markdownTable : String(markdownTable || '');
67
- let tableData;
68
-
69
- try {
70
- tableData = parseMarkdownTable(tableText);
71
- } catch {
72
- tableData = { headers: [], rows: [] };
73
- }
74
-
75
- const hasTableData = (tableData?.headers?.length || 0) > 0 || (tableData?.rows?.length || 0) > 0;
76
- if (!hasTableData) {
77
- return {
78
- oxml: sourceOoxml,
79
- hasChanges: false,
80
- isMarkdownTable: false,
81
- warnings: ['Could not parse Markdown table from input.']
82
- };
83
- }
84
-
85
- const result = await applyRedlineToOxml(
86
- sourceOoxml,
87
- originalText || '',
88
- tableText,
89
- options
90
- );
91
-
92
- return {
93
- ...result,
94
- isMarkdownTable: true,
95
- tableData
96
- };
97
- }
98
-
99
- export { resolveParagraphRangeByRefs } from './core/paragraph-targeting.js';
100
- export { inferTableReplacementParagraphBlock, isLikelyStructuredTableSourceParagraph } from './core/table-targeting.js';
101
-
102
- /**
103
- * Applies redline reconciliation, then forces single-line structural list
104
- * conversion when the redline is a no-op on marker-prefixed list text.
105
- *
106
- * This is useful for inputs like `1. HEADER` where text diff is unchanged but
107
- * OOXML should convert plain text markers into real Word list structure.
108
- *
109
- * @param {string} oxml - Original OOXML
110
- * @param {string} originalText - Original visible text
111
- * @param {string} modifiedText - Proposed modified text
112
- * @param {Object} [options={}] - Reconciliation options
113
- * @param {boolean} [options.listFallbackAllowExistingList=true] - Allow fallback even when paragraph is already list-bound
114
- * @returns {Promise<{ oxml: string, hasChanges: boolean } & Record<string, any>>}
115
- */
116
- export async function applyRedlineToOxmlWithListFallback(oxml, originalText, modifiedText, options = {}) {
117
- const allowExistingListForFallback = options.listFallbackAllowExistingList !== false;
118
- const fallbackModifiedText = options.sanitizeInput === true ? sanitizeAiResponse(modifiedText) : modifiedText;
119
- const sanitizationWarnings = fallbackModifiedText !== modifiedText
120
- ? ['Input was sanitized; pass sanitizeInput: false to disable.']
121
- : [];
122
- const plan = buildSingleLineListStructuralFallbackPlan({
123
- oxml,
124
- originalText,
125
- modifiedText: fallbackModifiedText,
126
- allowExistingList: allowExistingListForFallback
127
- });
128
- const preferListFallback = options.preferListStructuralFallback !== false;
129
- let preflightFallbackWarnings = [];
130
-
131
- if (plan && preferListFallback) {
132
- const fallbackResult = await executeSingleLineListStructuralFallback(plan, {
133
- author: options.author,
134
- generateRedlines: options.generateRedlines,
135
- pipeline: options.listFallbackPipeline
136
- });
137
- if (fallbackResult?.hasChanges && fallbackResult?.oxml) {
138
- const wrappedOxml = wrapInDocumentFragmentShared(fallbackResult.oxml, {
139
- includeNumbering: fallbackResult.includeNumbering ?? true,
140
- numberingXml: fallbackResult.numberingXml
141
- });
142
- const fallbackWarnings = Array.isArray(fallbackResult?.warnings) ? fallbackResult.warnings : [];
143
- return withOoxmlSourceType({
144
- oxml: wrappedOxml,
145
- hasChanges: true,
146
- warnings: [...sanitizationWarnings, ...fallbackWarnings],
147
- listStructuralFallbackApplied: true,
148
- listStructuralFallbackKey: fallbackResult.listStructuralFallbackKey || null,
149
- listStructuralFallbackNumberingXml: fallbackResult.numberingXml || null
150
- });
151
- }
152
- preflightFallbackWarnings = Array.isArray(fallbackResult?.warnings) ? fallbackResult.warnings : [];
153
- }
154
-
155
- const baseResult = await applyRedlineToOxml(oxml, originalText, modifiedText, options);
156
-
157
- if (!plan) {
158
- return {
159
- ...baseResult,
160
- warnings: [
161
- ...(Array.isArray(baseResult?.warnings) ? baseResult.warnings : []),
162
- ...preflightFallbackWarnings
163
- ],
164
- listStructuralFallbackApplied: false
165
- };
166
- }
167
-
168
- if (preferListFallback) {
169
- return {
170
- ...baseResult,
171
- warnings: [
172
- ...(Array.isArray(baseResult?.warnings) ? baseResult.warnings : []),
173
- ...preflightFallbackWarnings
174
- ],
175
- listStructuralFallbackApplied: false
176
- };
177
- }
178
-
179
- if (baseResult?.hasChanges) {
180
- return {
181
- ...baseResult,
182
- listStructuralFallbackApplied: false
183
- };
184
- }
185
-
186
- const fallbackResult = await executeSingleLineListStructuralFallback(plan, {
187
- author: options.author,
188
- generateRedlines: options.generateRedlines,
189
- pipeline: options.listFallbackPipeline
190
- });
191
- if (!fallbackResult?.hasChanges || !fallbackResult?.oxml) {
192
- const existingWarnings = Array.isArray(baseResult?.warnings) ? baseResult.warnings : [];
193
- const fallbackWarnings = Array.isArray(fallbackResult?.warnings) ? fallbackResult.warnings : [];
194
- return {
195
- ...baseResult,
196
- warnings: [...existingWarnings, ...fallbackWarnings],
197
- listStructuralFallbackApplied: false
198
- };
199
- }
200
-
201
- const wrappedOxml = wrapInDocumentFragmentShared(fallbackResult.oxml, {
202
- includeNumbering: fallbackResult.includeNumbering ?? true,
203
- numberingXml: fallbackResult.numberingXml
204
- });
205
- const existingWarnings = Array.isArray(baseResult?.warnings) ? baseResult.warnings : [];
206
- const fallbackWarnings = Array.isArray(fallbackResult?.warnings) ? fallbackResult.warnings : [];
207
-
208
- return withOoxmlSourceType({
209
- ...baseResult,
210
- oxml: wrappedOxml,
211
- hasChanges: true,
212
- warnings: [...existingWarnings, ...preflightFallbackWarnings, ...fallbackWarnings],
213
- listStructuralFallbackApplied: true,
214
- listStructuralFallbackKey: fallbackResult.listStructuralFallbackKey || null,
215
- listStructuralFallbackNumberingXml: fallbackResult.numberingXml || null
216
- });
217
- }
218
-
219
- export { sanitizeAiResponse, parseOoxml, serializeOoxml };
220
-
221
- export {
222
- createDynamicNumberingIdState,
223
- reserveNextNumberingId,
224
- reserveNextNumberingIdPair,
225
- overwriteParagraphNumIds,
226
- extractFirstParagraphNumId,
227
- buildExplicitDecimalMultilevelNumberingXml,
228
- remapNumberingPayloadForDocument,
229
- mergeNumberingXmlBySchemaOrder
230
- } from './services/numbering-helpers.js';
231
-
232
- // Pipeline components
233
- export { ReconciliationPipeline } from './pipeline/pipeline.js';
234
- export { ingestOoxml } from './pipeline/ingestion.js';
235
- export {
236
- ingestWordOoxmlToPlainText,
237
- ingestWordOoxmlToMarkdown,
238
- ingestWordOoxmlToPlainTextResult,
239
- ingestWordOoxmlToMarkdownResult
240
- } from './pipeline/ingestion-export.js';
241
- export { extractCanonicalParagraphText, readCanonicalRunText, isNodeVisibleInRevisionView, extractParagraphRevisionSegments } from './core/paragraph-text.js';
242
- export { inspectDocumentParts } from './services/document-inspection.js';
243
- export {
244
- buildRevisionTokenFraming,
245
- computeRevisionToken,
246
- computeRevisionTokenSync,
247
- computeDocumentPartsRevisionToken,
248
- validateRevisionToken,
249
- areRevisionTokensEqual,
250
- normalizeOpcEntryName,
251
- extractDocumentPartsEntries
252
- } from './services/revision-token.js';
253
- export { preprocessMarkdown } from './pipeline/markdown-processor.js';
254
- export { serializeToOoxml, wrapInDocumentFragment } from './pipeline/serialization.js';
255
-
256
- // Comment engine
257
- export {
258
- injectCommentsIntoOoxml,
259
- injectCommentsIntoPackage,
260
- buildCommentElement,
261
- buildCommentsPartXml
262
- } from './services/comment-engine.js';
263
- export { applyCommentReplyToParts } from './services/comment-replies.js';
264
- export {
265
- acceptTrackedChangesInOoxml,
266
- rejectTrackedChangesInOoxml,
267
- deleteCommentsByAuthorInOoxml
268
- } from './services/revision-comment-management.js';
269
-
270
- // Formatting removal utilities
271
- export {
272
- removeFormattingFromRPr,
273
- applyFormattingRemovalToOoxml,
274
- applyHighlightToOoxml
275
- } from './engine/formatting-removal.js';
276
-
277
- // Table/list tools
278
- export { generateTableOoxml } from './services/table-reconciliation.js';
279
- export { NumberingService } from './services/numbering-service.js';
280
- export {
281
- getBodyElementFromDocument,
282
- insertBodyElementBeforeSectPr,
283
- normalizeBodySectionOrderStandalone,
284
- sanitizeNestedParagraphsInTables,
285
- getPackagePartName,
286
- extractReplacementNodesFromOoxml,
287
- ensureNumberingArtifactsInZip,
288
- ensureCommentsArtifactsInZip,
289
- ensureCommentsExtendedArtifactsInZip,
290
- validateDocxPackage
291
- } from './services/standalone-docx-plumbing.js';
292
- export { buildReconciliationPlan, RoutePlanKind, normalizeContentEscapesForRouting } from './orchestration/route-plan.js';
293
- export { parseMarkdownListContent, hasListItems } from './orchestration/list-parsing.js';
294
- export { buildListMarkdown, inferNumberingStyleFromMarker, normalizeListItemsWithLevels } from './orchestration/list-markdown.js';
295
- export {
296
- buildSingleLineListStructuralFallbackPlan,
297
- executeSingleLineListStructuralFallback,
298
- resolveSingleLineListFallbackNumberingAction,
299
- recordSingleLineListFallbackExplicitSequence,
300
- clearSingleLineListFallbackExplicitSequence,
301
- enforceListBindingOnParagraphNodes,
302
- stripSingleLineListMarkerPrefix
303
- } from './orchestration/list-structural-fallback.js';
304
-
305
- // Core types/constants
306
- export { DiffOp, RunKind, ContainerKind, ContentType, NS_W, escapeXml } from './core/types.js';
307
- export { extractParagraphIdFromOoxml } from './core/ooxml-identifiers.js';
308
- export {
309
- WORD_MAIN_NS,
310
- getParagraphText,
311
- getParagraphId,
312
- createParagraphFingerprint,
313
- getDocumentParagraphNodes,
314
- normalizeWhitespaceForTargeting,
315
- isMarkdownTableText,
316
- parseParagraphReference,
317
- stripLeadingParagraphMarker,
318
- splitLeadingParagraphMarker,
319
- findContainingWordElement,
320
- findParagraphByReference,
321
- findParagraphByStrictText,
322
- findParagraphByBestTextMatch,
323
- findStrictTargetCandidates,
324
- resolveTargetParagraph,
325
- buildTargetReferenceSnapshot,
326
- resolveTargetParagraphWithSnapshot
327
- } from './core/paragraph-targeting.js';
328
- export { synthesizeTableMarkdownFromMultilineCellEdit } from './core/table-targeting.js';
329
- export {
330
- getParagraphListInfo,
331
- collectContiguousListParagraphBlock,
332
- synthesizeExpandedListScopeEdit,
333
- planListInsertionOnlyEdit,
334
- stripRedundantLeadingListMarkers
335
- } from './core/list-targeting.js';
336
- export {
337
- ReceiptCollector,
338
- createEmptyReceipt,
339
- reconcileReceiptsAgainstOutput
340
- } from './services/receipt-collector.js';
1
+ /**
2
+ * Standalone reconciliation entrypoint (no Word JS API dependencies).
3
+ */
4
+
5
+ // Adapters
6
+ export { configureXmlProvider, parseOoxmlSafe } from './adapters/xml-adapter.js';
7
+ export { configureLogger } from './adapters/logger.js';
8
+ export { setDefaultAuthor, getDefaultAuthor, setPlatform, getPlatform } from './adapters/config.js';
9
+
10
+ // Engine
11
+ import {
12
+ applyRedlineToOxml as applyRedlineToOxmlEngine,
13
+ sanitizeAiResponse,
14
+ parseOoxml,
15
+ serializeOoxml
16
+ } from './engine/oxml-engine.js';
17
+ import { parseTable as parseMarkdownTable } from './pipeline/pipeline.js';
18
+ import { wrapInDocumentFragment as wrapInDocumentFragmentShared } from './pipeline/serialization.js';
19
+ import {
20
+ buildSingleLineListStructuralFallbackPlan,
21
+ executeSingleLineListStructuralFallback
22
+ } from './orchestration/list-structural-fallback.js';
23
+ import { withOoxmlSourceType } from './core/word-xml.js';
24
+ export { containsTrackedChanges, getTrackedChangeAuthors } from './core/word-xml.js';
25
+ export { validateRedlineOoxml } from './core/redline-validation.js';
26
+ export { analyzeStructuredContent, planStructuredReplacement } from './pipeline/structured-content.js';
27
+
28
+ /**
29
+ * Standalone-safe redline wrapper.
30
+ *
31
+ * In non-Word runtimes, the engine can return `{ useNativeApi: true, hasChanges: true }`
32
+ * without an OOXML payload for some format-only operations. Standalone callers cannot
33
+ * complete that native fallback path, so normalize to a no-op with warnings.
34
+ */
35
+ export async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}) {
36
+ const result = await applyRedlineToOxmlEngine(oxml, originalText, modifiedText, options);
37
+ if (result?.useNativeApi && typeof result?.oxml !== 'string') {
38
+ const existingWarnings = Array.isArray(result?.warnings) ? result.warnings : [];
39
+ return withOoxmlSourceType({
40
+ ...result,
41
+ oxml,
42
+ hasChanges: false,
43
+ warnings: [
44
+ ...existingWarnings,
45
+ 'Standalone mode cannot execute native Word API fallback for this operation.'
46
+ ]
47
+ });
48
+ }
49
+ return result;
50
+ }
51
+
52
+ /**
53
+ * Reconciles a Markdown table against an OOXML scope.
54
+ *
55
+ * This centralizes table-specific validation + reconciliation so Word add-in
56
+ * and browser modules can share the same entrypoint.
57
+ *
58
+ * @param {string} oxml - OOXML scope to reconcile (paragraph/range/table package)
59
+ * @param {string} originalText - Original visible text in that scope
60
+ * @param {string} markdownTable - Markdown table text
61
+ * @param {Object} [options={}] - Reconciliation options forwarded to applyRedlineToOxml
62
+ * @returns {Promise<{ oxml: string, hasChanges: boolean, warnings?: string[], isMarkdownTable: boolean, tableData?: Object }>}
63
+ */
64
+ export async function reconcileMarkdownTableOoxml(oxml, originalText, markdownTable, options = {}) {
65
+ const sourceOoxml = typeof oxml === 'string' ? oxml : '';
66
+ const tableText = typeof markdownTable === 'string' ? markdownTable : String(markdownTable || '');
67
+ let tableData;
68
+
69
+ try {
70
+ tableData = parseMarkdownTable(tableText);
71
+ } catch {
72
+ tableData = { headers: [], rows: [] };
73
+ }
74
+
75
+ const hasTableData = (tableData?.headers?.length || 0) > 0 || (tableData?.rows?.length || 0) > 0;
76
+ if (!hasTableData) {
77
+ return {
78
+ oxml: sourceOoxml,
79
+ hasChanges: false,
80
+ isMarkdownTable: false,
81
+ warnings: ['Could not parse Markdown table from input.']
82
+ };
83
+ }
84
+
85
+ const result = await applyRedlineToOxml(
86
+ sourceOoxml,
87
+ originalText || '',
88
+ tableText,
89
+ options
90
+ );
91
+
92
+ return {
93
+ ...result,
94
+ isMarkdownTable: true,
95
+ tableData
96
+ };
97
+ }
98
+
99
+ export { resolveParagraphRangeByRefs } from './core/paragraph-targeting.js';
100
+ export { inferTableReplacementParagraphBlock, isLikelyStructuredTableSourceParagraph } from './core/table-targeting.js';
101
+
102
+ /**
103
+ * Applies redline reconciliation, then forces single-line structural list
104
+ * conversion when the redline is a no-op on marker-prefixed list text.
105
+ *
106
+ * This is useful for inputs like `1. HEADER` where text diff is unchanged but
107
+ * OOXML should convert plain text markers into real Word list structure.
108
+ *
109
+ * @param {string} oxml - Original OOXML
110
+ * @param {string} originalText - Original visible text
111
+ * @param {string} modifiedText - Proposed modified text
112
+ * @param {Object} [options={}] - Reconciliation options
113
+ * @param {boolean} [options.listFallbackAllowExistingList=true] - Allow fallback even when paragraph is already list-bound
114
+ * @returns {Promise<{ oxml: string, hasChanges: boolean } & Record<string, any>>}
115
+ */
116
+ export async function applyRedlineToOxmlWithListFallback(oxml, originalText, modifiedText, options = {}) {
117
+ const allowExistingListForFallback = options.listFallbackAllowExistingList !== false;
118
+ const fallbackModifiedText = options.sanitizeInput === true ? sanitizeAiResponse(modifiedText) : modifiedText;
119
+ const sanitizationWarnings = fallbackModifiedText !== modifiedText
120
+ ? ['Input was sanitized; pass sanitizeInput: false to disable.']
121
+ : [];
122
+ const plan = buildSingleLineListStructuralFallbackPlan({
123
+ oxml,
124
+ originalText,
125
+ modifiedText: fallbackModifiedText,
126
+ allowExistingList: allowExistingListForFallback
127
+ });
128
+ const preferListFallback = options.preferListStructuralFallback !== false;
129
+ let preflightFallbackWarnings = [];
130
+
131
+ if (plan && preferListFallback) {
132
+ const fallbackResult = await executeSingleLineListStructuralFallback(plan, {
133
+ author: options.author,
134
+ generateRedlines: options.generateRedlines,
135
+ pipeline: options.listFallbackPipeline
136
+ });
137
+ if (fallbackResult?.hasChanges && fallbackResult?.oxml) {
138
+ const wrappedOxml = wrapInDocumentFragmentShared(fallbackResult.oxml, {
139
+ includeNumbering: fallbackResult.includeNumbering ?? true,
140
+ numberingXml: fallbackResult.numberingXml
141
+ });
142
+ const fallbackWarnings = Array.isArray(fallbackResult?.warnings) ? fallbackResult.warnings : [];
143
+ return withOoxmlSourceType({
144
+ oxml: wrappedOxml,
145
+ hasChanges: true,
146
+ warnings: [...sanitizationWarnings, ...fallbackWarnings],
147
+ listStructuralFallbackApplied: true,
148
+ listStructuralFallbackKey: fallbackResult.listStructuralFallbackKey || null,
149
+ listStructuralFallbackNumberingXml: fallbackResult.numberingXml || null
150
+ });
151
+ }
152
+ preflightFallbackWarnings = Array.isArray(fallbackResult?.warnings) ? fallbackResult.warnings : [];
153
+ }
154
+
155
+ const baseResult = await applyRedlineToOxml(oxml, originalText, modifiedText, options);
156
+
157
+ if (!plan) {
158
+ return {
159
+ ...baseResult,
160
+ warnings: [
161
+ ...(Array.isArray(baseResult?.warnings) ? baseResult.warnings : []),
162
+ ...preflightFallbackWarnings
163
+ ],
164
+ listStructuralFallbackApplied: false
165
+ };
166
+ }
167
+
168
+ if (preferListFallback) {
169
+ return {
170
+ ...baseResult,
171
+ warnings: [
172
+ ...(Array.isArray(baseResult?.warnings) ? baseResult.warnings : []),
173
+ ...preflightFallbackWarnings
174
+ ],
175
+ listStructuralFallbackApplied: false
176
+ };
177
+ }
178
+
179
+ if (baseResult?.hasChanges) {
180
+ return {
181
+ ...baseResult,
182
+ listStructuralFallbackApplied: false
183
+ };
184
+ }
185
+
186
+ const fallbackResult = await executeSingleLineListStructuralFallback(plan, {
187
+ author: options.author,
188
+ generateRedlines: options.generateRedlines,
189
+ pipeline: options.listFallbackPipeline
190
+ });
191
+ if (!fallbackResult?.hasChanges || !fallbackResult?.oxml) {
192
+ const existingWarnings = Array.isArray(baseResult?.warnings) ? baseResult.warnings : [];
193
+ const fallbackWarnings = Array.isArray(fallbackResult?.warnings) ? fallbackResult.warnings : [];
194
+ return {
195
+ ...baseResult,
196
+ warnings: [...existingWarnings, ...fallbackWarnings],
197
+ listStructuralFallbackApplied: false
198
+ };
199
+ }
200
+
201
+ const wrappedOxml = wrapInDocumentFragmentShared(fallbackResult.oxml, {
202
+ includeNumbering: fallbackResult.includeNumbering ?? true,
203
+ numberingXml: fallbackResult.numberingXml
204
+ });
205
+ const existingWarnings = Array.isArray(baseResult?.warnings) ? baseResult.warnings : [];
206
+ const fallbackWarnings = Array.isArray(fallbackResult?.warnings) ? fallbackResult.warnings : [];
207
+
208
+ return withOoxmlSourceType({
209
+ ...baseResult,
210
+ oxml: wrappedOxml,
211
+ hasChanges: true,
212
+ warnings: [...existingWarnings, ...preflightFallbackWarnings, ...fallbackWarnings],
213
+ listStructuralFallbackApplied: true,
214
+ listStructuralFallbackKey: fallbackResult.listStructuralFallbackKey || null,
215
+ listStructuralFallbackNumberingXml: fallbackResult.numberingXml || null
216
+ });
217
+ }
218
+
219
+ export { sanitizeAiResponse, parseOoxml, serializeOoxml };
220
+
221
+ export {
222
+ createDynamicNumberingIdState,
223
+ reserveNextNumberingId,
224
+ reserveNextNumberingIdPair,
225
+ overwriteParagraphNumIds,
226
+ extractFirstParagraphNumId,
227
+ buildExplicitDecimalMultilevelNumberingXml,
228
+ remapNumberingPayloadForDocument,
229
+ mergeNumberingXmlBySchemaOrder
230
+ } from './services/numbering-helpers.js';
231
+
232
+ // Pipeline components
233
+ export { ReconciliationPipeline } from './pipeline/pipeline.js';
234
+ export { ingestOoxml } from './pipeline/ingestion.js';
235
+ export {
236
+ ingestWordOoxmlToPlainText,
237
+ ingestWordOoxmlToMarkdown,
238
+ ingestWordOoxmlToPlainTextResult,
239
+ ingestWordOoxmlToMarkdownResult
240
+ } from './pipeline/ingestion-export.js';
241
+ export { extractCanonicalParagraphText, readCanonicalRunText, isNodeVisibleInRevisionView, extractParagraphRevisionSegments } from './core/paragraph-text.js';
242
+ export { inspectDocumentParts } from './services/document-inspection.js';
243
+ export {
244
+ buildRevisionTokenFraming,
245
+ computeRevisionToken,
246
+ computeRevisionTokenSync,
247
+ computeDocumentPartsRevisionToken,
248
+ validateRevisionToken,
249
+ areRevisionTokensEqual,
250
+ normalizeOpcEntryName,
251
+ extractDocumentPartsEntries
252
+ } from './services/revision-token.js';
253
+ export { preprocessMarkdown } from './pipeline/markdown-processor.js';
254
+ export { serializeToOoxml, wrapInDocumentFragment } from './pipeline/serialization.js';
255
+
256
+ // Comment engine
257
+ export {
258
+ injectCommentsIntoOoxml,
259
+ injectCommentsIntoPackage,
260
+ buildCommentElement,
261
+ buildCommentsPartXml
262
+ } from './services/comment-engine.js';
263
+ export { applyCommentReplyToParts } from './services/comment-replies.js';
264
+ export {
265
+ acceptTrackedChangesInOoxml,
266
+ rejectTrackedChangesInOoxml,
267
+ deleteCommentsByAuthorInOoxml
268
+ } from './services/revision-comment-management.js';
269
+
270
+ // Formatting removal utilities
271
+ export {
272
+ removeFormattingFromRPr,
273
+ applyFormattingRemovalToOoxml,
274
+ applyHighlightToOoxml
275
+ } from './engine/formatting-removal.js';
276
+
277
+ // Table/list tools
278
+ export { generateTableOoxml } from './services/table-reconciliation.js';
279
+ export { NumberingService } from './services/numbering-service.js';
280
+ export {
281
+ getBodyElementFromDocument,
282
+ insertBodyElementBeforeSectPr,
283
+ normalizeBodySectionOrderStandalone,
284
+ sanitizeNestedParagraphsInTables,
285
+ getPackagePartName,
286
+ extractReplacementNodesFromOoxml,
287
+ ensureNumberingArtifactsInZip,
288
+ ensureCommentsArtifactsInZip,
289
+ ensureCommentsExtendedArtifactsInZip,
290
+ repairKnownContentTypes,
291
+ validateDocxPackage
292
+ } from './services/standalone-docx-plumbing.js';
293
+ export { buildReconciliationPlan, RoutePlanKind, normalizeContentEscapesForRouting } from './orchestration/route-plan.js';
294
+ export { parseMarkdownListContent, hasListItems } from './orchestration/list-parsing.js';
295
+ export { buildListMarkdown, inferNumberingStyleFromMarker, normalizeListItemsWithLevels } from './orchestration/list-markdown.js';
296
+ export {
297
+ buildSingleLineListStructuralFallbackPlan,
298
+ executeSingleLineListStructuralFallback,
299
+ resolveSingleLineListFallbackNumberingAction,
300
+ recordSingleLineListFallbackExplicitSequence,
301
+ clearSingleLineListFallbackExplicitSequence,
302
+ enforceListBindingOnParagraphNodes,
303
+ stripSingleLineListMarkerPrefix
304
+ } from './orchestration/list-structural-fallback.js';
305
+
306
+ // Core types/constants
307
+ export { DiffOp, RunKind, ContainerKind, ContentType, NS_W, escapeXml } from './core/types.js';
308
+ export { extractParagraphIdFromOoxml } from './core/ooxml-identifiers.js';
309
+ export {
310
+ WORD_MAIN_NS,
311
+ getParagraphText,
312
+ getParagraphId,
313
+ createParagraphFingerprint,
314
+ getDocumentParagraphNodes,
315
+ normalizeWhitespaceForTargeting,
316
+ isMarkdownTableText,
317
+ parseParagraphReference,
318
+ stripLeadingParagraphMarker,
319
+ splitLeadingParagraphMarker,
320
+ findContainingWordElement,
321
+ findParagraphByReference,
322
+ findParagraphByStrictText,
323
+ findParagraphByBestTextMatch,
324
+ findStrictTargetCandidates,
325
+ resolveTargetParagraph,
326
+ buildTargetReferenceSnapshot,
327
+ resolveTargetParagraphWithSnapshot
328
+ } from './core/paragraph-targeting.js';
329
+ export { synthesizeTableMarkdownFromMultilineCellEdit } from './core/table-targeting.js';
330
+ export {
331
+ getParagraphListInfo,
332
+ collectContiguousListParagraphBlock,
333
+ synthesizeExpandedListScopeEdit,
334
+ planListInsertionOnlyEdit,
335
+ stripRedundantLeadingListMarkers
336
+ } from './core/list-targeting.js';
337
+ export {
338
+ ReceiptCollector,
339
+ createEmptyReceipt,
340
+ reconcileReceiptsAgainstOutput
341
+ } from './services/receipt-collector.js';
342
+
343
+ // Universal Document Facade
344
+ export {
345
+ DocxDocument,
346
+ openDocx,
347
+ computePackageRevisionToken
348
+ } from './document/docx-document.js';
349
+