@ansonlai/docx-redline-js 0.8.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/ARCHITECTURE.md +221 -12
  2. package/CHANGELOG.md +803 -586
  3. package/README.md +1018 -901
  4. package/core/current-properties.js +136 -0
  5. package/core/paragraph-text.js +211 -209
  6. package/core/types.js +9 -3
  7. package/core/word-revision-semantics.js +256 -0
  8. package/dist/docx-redline-js.esm.js +4551 -967
  9. package/dist/docx-redline-js.esm.js.map +4 -4
  10. package/dist/docx-redline-js.esm.min.js +112 -106
  11. package/dist/docx-redline-js.esm.min.js.map +4 -4
  12. package/dist/docx-redline.bundle.cjs +4690 -1106
  13. package/dist/docx-redline.bundle.cjs.map +4 -4
  14. package/dist/docx-redline.bundle.js +4690 -1106
  15. package/dist/docx-redline.bundle.js.map +4 -4
  16. package/docs/AGENT_FAST_START.md +2 -0
  17. package/docs/AGENT_KNOWLEDGE_BASE.md +23 -4
  18. package/docs/SKILL_AUTHORING.md +165 -150
  19. package/docs/TESTING.md +1142 -1059
  20. package/docs/schemas/document-operations.schema.json +221 -168
  21. package/document/docx-document.js +1022 -1010
  22. package/engine/oxml-engine.js +567 -560
  23. package/engine/paragraph-mark-revisions.js +292 -0
  24. package/engine/reconstruction-mapper.js +312 -311
  25. package/engine/reconstruction-mode.js +183 -183
  26. package/engine/reconstruction-writer.js +67 -10
  27. package/engine/rpr-helpers.js +42 -0
  28. package/engine/run-builders.js +0 -73
  29. package/engine/surgical-diff-application.js +604 -605
  30. package/engine/table-mode.js +63 -63
  31. package/index.d.ts +51 -1
  32. package/index.js +47 -3
  33. package/node/cli-help.js +1 -0
  34. package/node/cli.js +992 -974
  35. package/orchestration/list-structural-fallback.js +67 -9
  36. package/package.json +1 -1
  37. package/pipeline/diff-engine.js +130 -1
  38. package/pipeline/ingestion-export.js +227 -243
  39. package/pipeline/ingestion-paragraph.js +93 -98
  40. package/pipeline/list-generation.js +415 -419
  41. package/pipeline/pipeline.js +206 -204
  42. package/pipeline/serialization.js +111 -94
  43. package/scripts/export-validation-fixtures.mjs +19 -6
  44. package/scripts/generate-word-revision-semantics-matrix.mjs +134 -0
  45. package/scripts/lib/word-coverage-catalogue.mjs +7 -2
  46. package/scripts/render-multilevel-bullet-visual.ps1 +2 -2
  47. package/scripts/word-com-corpus-visual-suite.ps1 +2 -2
  48. package/scripts/word-com-differential.ps1 +443 -335
  49. package/scripts/word-com-visual-suite.ps1 +2 -2
  50. package/scripts/word-revision-semantics-matrix.ps1 +108 -0
  51. package/services/batch-operation-orchestrator.js +26 -0
  52. package/services/capture-engine.js +228 -226
  53. package/services/document-operation-applier.js +215 -0
  54. package/services/document-operation-contract.js +628 -537
  55. package/services/document-operation-mutations.js +789 -46
  56. package/services/error-recovery.js +17 -0
  57. package/services/list-conversion-diagnostics.js +296 -0
  58. package/services/list-format-change.js +586 -0
  59. package/services/list-format-mutation.js +416 -0
  60. package/services/list-format.js +305 -0
  61. package/services/numbering-allocation.js +642 -0
  62. package/services/numbering-helpers.js +354 -524
  63. package/services/operation-batch-compiler.js +612 -436
  64. package/services/operation-heuristics.js +197 -173
  65. package/services/operation-preflight.js +1 -0
  66. package/services/receipt-collector.js +288 -288
  67. package/services/revision-comment-management.js +634 -594
  68. package/services/revision-fidelity-verifier.js +498 -0
  69. package/services/standalone-docx-plumbing.js +627 -631
  70. package/services/standalone-operation-runner.d.ts +606 -427
  71. package/services/table-reconciliation.js +103 -49
@@ -2,30 +2,30 @@
2
2
  * Table-specific reconciliation and transformation flows.
3
3
  */
4
4
 
5
- import {
6
- NS_W,
7
- createRevisionMetadata,
8
- getRevisionIdAllocatorForDocument,
9
- setRevisionIdAllocatorForDocument
10
- } from '../core/types.js';
11
- import {
12
- getElementsByTagNS,
13
- getElementsByTagNSOrTag,
14
- getFirstElementByTagNS,
15
- getFirstElementByTagNSOrTag,
16
- getXmlParseError
17
- } from '../core/xml-query.js';
18
- import { parseOoxmlSafe } from '../adapters/xml-adapter.js';
19
- import { log, error } from '../adapters/logger.js';
20
- import { diffTablesWithVirtualGrid, serializeVirtualGridToOoxml, generateTableOoxml } from '../services/table-reconciliation.js';
21
- import { parseTable } from '../pipeline/pipeline.js';
22
- import { ingestTableToVirtualGrid } from '../pipeline/ingestion.js';
23
- import { createWordElement, withOoxmlSourceType } from '../core/word-xml.js';
24
- import { markParagraphMarkDeleted } from './run-builders.js';
25
-
26
- function noChanges(serializer, xmlDoc) {
27
- return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: false });
28
- }
5
+ import {
6
+ NS_W,
7
+ createRevisionMetadata,
8
+ getRevisionIdAllocatorForDocument,
9
+ setRevisionIdAllocatorForDocument
10
+ } from '../core/types.js';
11
+ import {
12
+ getElementsByTagNS,
13
+ getElementsByTagNSOrTag,
14
+ getFirstElementByTagNS,
15
+ getFirstElementByTagNSOrTag,
16
+ getXmlParseError
17
+ } from '../core/xml-query.js';
18
+ import { parseOoxmlSafe } from '../adapters/xml-adapter.js';
19
+ import { log, error } from '../adapters/logger.js';
20
+ import { diffTablesWithVirtualGrid, serializeVirtualGridToOoxml, generateTableOoxml } from '../services/table-reconciliation.js';
21
+ import { parseTable } from '../pipeline/pipeline.js';
22
+ import { ingestTableToVirtualGrid } from '../pipeline/ingestion.js';
23
+ import { createWordElement, withOoxmlSourceType } from '../core/word-xml.js';
24
+ import { markParagraphDeleted } from './paragraph-mark-revisions.js';
25
+
26
+ function noChanges(serializer, xmlDoc) {
27
+ return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: false });
28
+ }
29
29
 
30
30
  /**
31
31
  * Applies structural reconciliation to tables using Virtual Grid.
@@ -38,8 +38,8 @@ function noChanges(serializer, xmlDoc) {
38
38
  * @param {boolean} [generateRedlines=true] - Track change toggle
39
39
  * @returns {{ oxml: string, hasChanges: boolean }}
40
40
  */
41
- export function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines = true) {
42
- const tableNodes = getElementsByTagNSOrTag(xmlDoc, NS_W, 'tbl');
41
+ export function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines = true) {
42
+ const tableNodes = getElementsByTagNSOrTag(xmlDoc, NS_W, 'tbl');
43
43
  const newTableData = parseTable(modifiedText);
44
44
  const hasNewContent = newTableData.rows.length > 0 || newTableData.headers.length > 0;
45
45
 
@@ -55,15 +55,15 @@ export function applyTableReconciliation(xmlDoc, modifiedText, serializer, parse
55
55
  return noChanges(serializer, xmlDoc);
56
56
  }
57
57
 
58
- const options = {
59
- generateRedlines,
60
- author,
61
- revisionIdAllocator: getRevisionIdAllocatorForDocument(xmlDoc)
62
- };
58
+ const options = {
59
+ generateRedlines,
60
+ author,
61
+ revisionIdAllocator: getRevisionIdAllocatorForDocument(xmlDoc)
62
+ };
63
63
  const reconciledOxml = serializeVirtualGridToOoxml(oldGrid, operations, options);
64
64
  const wrappedOxml = `<root xmlns:w="${NS_W}">${reconciledOxml}</root>`;
65
- const reconciledDoc = parseOoxmlSafe(wrappedOxml, 'application/xml').doc;
66
- if (!reconciledDoc) return noChanges(serializer, xmlDoc);
65
+ const reconciledDoc = parseOoxmlSafe(wrappedOxml, 'application/xml').doc;
66
+ if (!reconciledDoc) return noChanges(serializer, xmlDoc);
67
67
 
68
68
  const parseError = getXmlParseError(reconciledDoc);
69
69
  if (parseError) {
@@ -71,7 +71,7 @@ export function applyTableReconciliation(xmlDoc, modifiedText, serializer, parse
71
71
  return noChanges(serializer, xmlDoc);
72
72
  }
73
73
 
74
- const newTableNode = getFirstElementByTagNSOrTag(reconciledDoc, NS_W, 'tbl');
74
+ const newTableNode = getFirstElementByTagNSOrTag(reconciledDoc, NS_W, 'tbl');
75
75
  if (!newTableNode) {
76
76
  error('[OxmlEngine] No table found in reconciled OOXML');
77
77
  return noChanges(serializer, xmlDoc);
@@ -80,7 +80,7 @@ export function applyTableReconciliation(xmlDoc, modifiedText, serializer, parse
80
80
  const importedTable = xmlDoc.importNode(newTableNode, true);
81
81
  targetTable.parentNode.replaceChild(importedTable, targetTable);
82
82
 
83
- return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: true });
83
+ return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: true });
84
84
  }
85
85
 
86
86
  /**
@@ -94,17 +94,17 @@ export function applyTableReconciliation(xmlDoc, modifiedText, serializer, parse
94
94
  * @param {boolean} generateRedlines - Track change toggle
95
95
  * @returns {{ oxml: string, hasChanges: boolean }}
96
96
  */
97
- export function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines) {
98
- const revisionIdAllocator = getRevisionIdAllocatorForDocument(xmlDoc);
97
+ export function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines) {
98
+ const revisionIdAllocator = getRevisionIdAllocatorForDocument(xmlDoc);
99
99
  const tableData = parseTable(modifiedText);
100
100
  if (!tableData || (tableData.rows.length === 0 && tableData.headers.length === 0)) {
101
101
  log('[OxmlEngine] Failed to parse table data from Markdown');
102
102
  return noChanges(serializer, xmlDoc);
103
103
  }
104
104
 
105
- const tableOoxml = generateTableOoxml(tableData, { generateRedlines, author, revisionIdAllocator });
106
- const tableDoc = parseOoxmlSafe(`<root xmlns:w="${NS_W}">${tableOoxml}</root>`, 'application/xml').doc;
107
- if (!tableDoc) return noChanges(serializer, xmlDoc);
105
+ const tableOoxml = generateTableOoxml(tableData, { generateRedlines, author, revisionIdAllocator, trackAsBlock: true });
106
+ const tableDoc = parseOoxmlSafe(`<root xmlns:w="${NS_W}">${tableOoxml}</root>`, 'application/xml').doc;
107
+ if (!tableDoc) return noChanges(serializer, xmlDoc);
108
108
 
109
109
  const tableParseError = getXmlParseError(tableDoc);
110
110
  if (tableParseError) {
@@ -132,16 +132,16 @@ export function applyTextToTableTransformation(xmlDoc, modifiedText, serializer,
132
132
  let parent = firstParagraph.parentNode;
133
133
 
134
134
  if (parent && parent.nodeType === 9) {
135
- const wrappedDoc = parseOoxmlSafe(
136
- `<w:document xmlns:w="${NS_W}"><w:body/></w:document>`,
137
- 'application/xml'
138
- ).doc;
139
- if (!wrappedDoc) return noChanges(serializer, workingDoc);
135
+ const wrappedDoc = parseOoxmlSafe(
136
+ `<w:document xmlns:w="${NS_W}"><w:body/></w:document>`,
137
+ 'application/xml'
138
+ ).doc;
139
+ if (!wrappedDoc) return noChanges(serializer, workingDoc);
140
140
  const wrappedBody = getFirstElementByTagNS(wrappedDoc, NS_W, 'body');
141
141
  paragraphs.forEach(p => wrappedBody.appendChild(wrappedDoc.importNode(p, true)));
142
142
 
143
- workingDoc = wrappedDoc;
144
- setRevisionIdAllocatorForDocument(workingDoc, revisionIdAllocator);
143
+ workingDoc = wrappedDoc;
144
+ setRevisionIdAllocatorForDocument(workingDoc, revisionIdAllocator);
145
145
  paragraphs = getElementsByTagNS(workingDoc, NS_W, 'p');
146
146
  firstParagraph = paragraphs[0];
147
147
  parent = firstParagraph.parentNode;
@@ -149,26 +149,26 @@ export function applyTextToTableTransformation(xmlDoc, modifiedText, serializer,
149
149
 
150
150
  const importedTable = workingDoc.importNode(newTableElement, true);
151
151
 
152
- if (generateRedlines) {
153
- paragraphs.forEach(p => {
154
- markParagraphMarkDeleted(workingDoc, p, author);
155
- const runs = getElementsByTagNS(p, NS_W, 'r');
156
- runs.forEach(run => {
152
+ if (generateRedlines) {
153
+ paragraphs.forEach(p => {
154
+ markParagraphDeleted(p, { author, revisionIdAllocator: workingDoc });
155
+ const runs = getElementsByTagNS(p, NS_W, 'r');
156
+ runs.forEach(run => {
157
157
  const textNodes = getElementsByTagNS(run, NS_W, 't');
158
158
  textNodes.forEach(t => {
159
159
  const text = t.textContent || '';
160
160
  if (text.trim()) {
161
- const delText = createWordElement(workingDoc, 'w:delText');
162
- delText.textContent = text;
163
- t.parentNode.replaceChild(delText, t);
161
+ const delText = createWordElement(workingDoc, 'w:delText');
162
+ delText.textContent = text;
163
+ t.parentNode.replaceChild(delText, t);
164
164
  }
165
165
  });
166
-
167
- const del = createWordElement(workingDoc, 'w:del');
168
- const metadata = createRevisionMetadata(author, workingDoc, 'del');
169
- del.setAttribute('w:id', String(metadata.id));
170
- del.setAttribute('w:author', metadata.author);
171
- del.setAttribute('w:date', metadata.date);
166
+
167
+ const del = createWordElement(workingDoc, 'w:del');
168
+ const metadata = createRevisionMetadata(author, workingDoc, 'del');
169
+ del.setAttribute('w:id', String(metadata.id));
170
+ del.setAttribute('w:author', metadata.author);
171
+ del.setAttribute('w:date', metadata.date);
172
172
  run.parentNode.insertBefore(del, run);
173
173
  del.appendChild(run);
174
174
  });
@@ -183,5 +183,5 @@ export function applyTextToTableTransformation(xmlDoc, modifiedText, serializer,
183
183
  }
184
184
 
185
185
  log('[OxmlEngine] Text-to-table transformation complete');
186
- return withOoxmlSourceType({ oxml: serializer.serializeToString(workingDoc), hasChanges: true });
187
- }
186
+ return withOoxmlSourceType({ oxml: serializer.serializeToString(workingDoc), hasChanges: true });
187
+ }
package/index.d.ts CHANGED
@@ -30,6 +30,16 @@ export type {
30
30
  DocumentOperationBatchResult,
31
31
  HighlightDocumentOperation,
32
32
  InsertionAffinity,
33
+ ListConversionDiagnostic,
34
+ ListFormatDocumentOperation,
35
+ ListFormatRequest,
36
+ ListFormatConvertPlainRequest,
37
+ ListFormatChangeFormatRequest,
38
+ ListFormatJoinRequest,
39
+ ListFormatResult,
40
+ ListFormatConvertPlainResult,
41
+ ListFormatRebindResult,
42
+ ListNumberFormat,
33
43
  LocalizedReplacement,
34
44
  LocalizedReplacementChange,
35
45
  OperationConflict,
@@ -52,6 +62,13 @@ export interface RedlineError {
52
62
  | 'DIFF_TOKEN_LIMIT'
53
63
  | 'UNSUPPORTED_REVISION_VIEW_MUTATION'
54
64
  | 'UNSUPPORTED_INSERTION_AFFINITY'
65
+ | 'UNSUPPORTED_LIST_CONVERSION'
66
+ | 'UNSUPPORTED_LIST_FORMAT_CHANGE'
67
+ | 'LIST_MARKER_NOT_FOUND'
68
+ | 'LIST_MARKER_AMBIGUOUS'
69
+ | 'LIST_MARKER_UNSUPPORTED'
70
+ | 'LIST_FORMAT_TARGET_IS_LIST'
71
+ | 'LIST_FORMAT_STYLE_NUMBERED'
55
72
  | 'UNSAFE_PARAGRAPH_BOUNDARY'
56
73
  | 'FOREIGN_PARAGRAPH_MARK_DELETION'
57
74
  | 'RESTORATION_STATE_REQUIRED'
@@ -80,6 +97,11 @@ export interface RedlineError {
80
97
  recoveryVersion?: number;
81
98
  category?: 'request_fixable' | 'target_refresh_required' | 'candidate_selection_required' | 'policy_choice_required' | 'user_authorization_required' | 'source_conflict' | 'library_or_builder_failure' | 'manual_document_resolution' | string;
82
99
  context?: Record<string, unknown>;
100
+ /** List-conversion errors: the literal marker, the requested list kind/format and the kept source definition. */
101
+ listMarker?: string;
102
+ requestedList?: { kind: 'bullet' | 'numbered'; format: string };
103
+ sourceList?: { numId: string; level: number; kind: 'bullet' | 'numbered'; format: string };
104
+ targetExcerpt?: string;
83
105
  recovery?: {
84
106
  action: string;
85
107
  sameArgumentsSafe: boolean;
@@ -87,6 +109,8 @@ export interface RedlineError {
87
109
  requiresUserAuthorization: boolean;
88
110
  field?: string;
89
111
  recommendedValue?: unknown;
112
+ allowedValue?: unknown;
113
+ hint?: string;
90
114
  };
91
115
  expectedRevision?: RevisionToken;
92
116
  currentRevision?: RevisionToken;
@@ -105,6 +129,10 @@ export interface RedlineOptions {
105
129
  structuredContent?: boolean;
106
130
  pairReplacements?: boolean;
107
131
  insertionAffinity?: InsertionAffinity;
132
+ /** Source numbering part; generated list definitions are remapped to IDs that do not collide with it. */
133
+ numberingXml?: string | null;
134
+ /** Shared numbering ID state (from `createDynamicNumberingIdState`) for allocation across calls. */
135
+ numberingIdState?: unknown;
108
136
  [key: string]: unknown;
109
137
  }
110
138
 
@@ -433,7 +461,29 @@ export function overwriteParagraphNumIds(oxml: string, numId: string | number):
433
461
  export function extractFirstParagraphNumId(oxml: string): string | null;
434
462
  export function buildExplicitDecimalMultilevelNumberingXml(options?: Record<string, unknown>): string;
435
463
  export function remapNumberingPayloadForDocument(numberingXml: string, state: unknown): unknown;
436
- export function mergeNumberingXmlBySchemaOrder(baseXml: string, incomingXml: string): string;
464
+ export interface NumberingIdConflict { kind: 'abstractNum' | 'num'; id: number }
465
+ export interface NumberingMergeOptions { onConflict?: (conflict: NumberingIdConflict) => void }
466
+ export interface NumberingIntegrityIssue { code: string; severity: 'error'; message: string; numId?: number; abstractNumId?: number; ilvl?: number }
467
+ export function mergeNumberingXmlBySchemaOrder(baseXml: string, incomingXml: string, options?: NumberingMergeOptions): string;
468
+ export function mergeNumberingParts(existingXml: string | null | undefined, incomingParts: string | Array<string | null | undefined> | null | undefined, options?: NumberingMergeOptions): string;
469
+ export function allocateNumberingDefinition(params: {
470
+ numberingIdState: unknown;
471
+ sourceNumberingXml?: string | null;
472
+ sourceNumId?: number | string | null;
473
+ levels?: Array<{ ilvl: number; numFmt?: string; lvlText?: string; start?: number }>;
474
+ startOverrides?: Record<number | string, number>;
475
+ }): { numId: number; abstractNumId: number; numberingXml: string } | null;
476
+ export function allocateNumberingForReplacement(params: {
477
+ numberingXml: string | null;
478
+ replacementNodes: unknown[];
479
+ sourceParagraphs?: unknown[];
480
+ numberingIdState?: unknown;
481
+ allowContinueSource?: boolean;
482
+ }): { policy: 'continue-source' | 'allocate' | 'passthrough'; numberingXml: string | null; replacementNodes: unknown[]; numId?: number };
483
+ export function applyStartOverridesToNum(numElement: unknown, startOverrides: Record<number | string, number>): void;
484
+ export function copyLevelGeometry(sourceLvl: unknown, targetLvl: unknown): void;
485
+ export function canonicalNumberingDefinition(numbering: unknown, numId: string | number): string | null;
486
+ export function validateNumberingIntegrity(documentXml: unknown, numberingXml?: unknown): NumberingIntegrityIssue[];
437
487
 
438
488
  export function buildListMarkdown(items: unknown[], options?: Record<string, unknown>): string;
439
489
  export function inferNumberingStyleFromMarker(marker: string): string;
package/index.js CHANGED
@@ -21,10 +21,44 @@ import {
21
21
  executeSingleLineListStructuralFallback
22
22
  } from './orchestration/list-structural-fallback.js';
23
23
  import { withOoxmlSourceType } from './core/word-xml.js';
24
+ import { createDynamicNumberingIdState, remapNumberingPayloadForDocument } from './services/numbering-helpers.js';
24
25
  export { containsTrackedChanges, getTrackedChangeAuthors } from './core/word-xml.js';
25
26
  export { validateRedlineOoxml } from './core/redline-validation.js';
26
27
  export { analyzeStructuredContent, planStructuredReplacement } from './pipeline/structured-content.js';
27
28
 
29
+ /**
30
+ * When the caller supplies the source `numberingXml` (or a numbering ID state), generated list
31
+ * definitions are remapped to IDs that cannot collide with any existing `w:num`/`w:abstractNum`.
32
+ */
33
+ function remapGeneratedNumberingForSource(result, options) {
34
+ const sourceNumbering = typeof options?.numberingXml === 'string' && options.numberingXml.trim()
35
+ ? options.numberingXml
36
+ : null;
37
+ const state = options?.numberingIdState && typeof options.numberingIdState === 'object'
38
+ ? options.numberingIdState
39
+ : (sourceNumbering ? createDynamicNumberingIdState(sourceNumbering) : null);
40
+ if (!state || !result?.hasChanges || typeof result.oxml !== 'string' || !result.oxml.includes('/word/numbering.xml')) {
41
+ return result;
42
+ }
43
+ const pkgDoc = parseOoxml(result.oxml);
44
+ const parts = Array.from(pkgDoc.getElementsByTagNameNS('*', 'part'));
45
+ const payloadOf = name => {
46
+ const part = parts.find(candidate => (candidate.getAttribute('pkg:name') || candidate.getAttribute('name')) === name);
47
+ const xmlData = part?.getElementsByTagNameNS('*', 'xmlData')[0];
48
+ return xmlData ? Array.from(xmlData.childNodes).find(node => node.nodeType === 1) || null : null;
49
+ };
50
+ const numberingNode = payloadOf('/word/numbering.xml');
51
+ const body = payloadOf('/word/document.xml')?.getElementsByTagNameNS('*', 'body')[0];
52
+ if (!numberingNode || !body) return result;
53
+
54
+ const bodyNodes = Array.from(body.childNodes).filter(node => node.nodeType === 1 && node.localName !== 'sectPr');
55
+ const remapped = remapNumberingPayloadForDocument(serializeOoxml(numberingNode), bodyNodes, state);
56
+ const newNumbering = pkgDoc.importNode(parseOoxml(remapped.numberingXml).documentElement, true);
57
+ numberingNode.parentNode.replaceChild(newNumbering, numberingNode);
58
+ bodyNodes.forEach((node, index) => body.replaceChild(remapped.replacementNodes[index], node));
59
+ return { ...result, oxml: serializeOoxml(pkgDoc) };
60
+ }
61
+
28
62
  /**
29
63
  * Standalone-safe redline wrapper.
30
64
  *
@@ -33,7 +67,8 @@ export { analyzeStructuredContent, planStructuredReplacement } from './pipeline/
33
67
  * complete that native fallback path, so normalize to a no-op with warnings.
34
68
  */
35
69
  export async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}) {
36
- const result = await applyRedlineToOxmlEngine(oxml, originalText, modifiedText, options);
70
+ const rawResult = await applyRedlineToOxmlEngine(oxml, originalText, modifiedText, options);
71
+ const result = remapGeneratedNumberingForSource(rawResult, options);
37
72
  if (result?.useNativeApi && typeof result?.oxml !== 'string') {
38
73
  const existingWarnings = Array.isArray(result?.warnings) ? result.warnings : [];
39
74
  return withOoxmlSourceType({
@@ -225,9 +260,18 @@ export {
225
260
  overwriteParagraphNumIds,
226
261
  extractFirstParagraphNumId,
227
262
  buildExplicitDecimalMultilevelNumberingXml,
228
- remapNumberingPayloadForDocument,
229
- mergeNumberingXmlBySchemaOrder
263
+ remapNumberingPayloadForDocument
230
264
  } from './services/numbering-helpers.js';
265
+ export {
266
+ allocateNumberingDefinition,
267
+ allocateNumberingForReplacement,
268
+ applyStartOverridesToNum,
269
+ canonicalNumberingDefinition,
270
+ copyLevelGeometry,
271
+ mergeNumberingParts,
272
+ mergeNumberingXmlBySchemaOrder,
273
+ validateNumberingIntegrity
274
+ } from './services/numbering-allocation.js';
231
275
 
232
276
  // Pipeline components
233
277
  export { ReconciliationPipeline } from './pipeline/pipeline.js';
package/node/cli-help.js CHANGED
@@ -144,6 +144,7 @@ export const CLI_COMMAND_HELP = Object.freeze({
144
144
  option('textToComment', '--text-to-comment <text>', 'Anchor an inline comment to an exact subspan.'),
145
145
  option('targetRef', '--target-ref <N>', 'Disambiguate an inline target with a 1-based machine index.'),
146
146
  option('existingRevisions', '--existing-revisions <policy>', 'Select revision handling; cross-author slicing must be deliberate.'),
147
+ option('revisionFidelity', '--revision-fidelity <enforce|warn|off>', 'Reject All verification of each tracked operation (default enforce: refuse with REVISION_FIDELITY_VIOLATION); the agent profile rejects off.'),
147
148
  option('atomic', '--atomic[=true|false]', 'Choose all-or-nothing or progressive batch execution.'),
148
149
  option('generateRedlines', '--generate-redlines[=true|false]', 'Control tracked-change generation.'),
149
150
  option('noRedlines', '--no-redlines', 'Apply clean text without tracked-change markup.'),