@adeu/core 1.21.0 → 1.22.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.
- package/dist/index.cjs +169 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -5
- package/dist/index.d.ts +43 -5
- package/dist/index.js +162 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/domain.ts +10 -1
- package/src/engine.ts +103 -20
- package/src/index.ts +3 -2
- package/src/mapper.ts +16 -9
- package/src/markup.test.ts +2 -1
- package/src/markup.ts +3 -1
- package/src/repro_qa_report_v5.test.ts +399 -0
- package/src/sanitize/transforms.ts +26 -2
- package/src/utils/safe-regex.ts +90 -0
package/dist/index.cjs
CHANGED
|
@@ -34,10 +34,13 @@ __export(index_exports, {
|
|
|
34
34
|
DocumentMapper: () => DocumentMapper,
|
|
35
35
|
DocumentObject: () => DocumentObject,
|
|
36
36
|
RedlineEngine: () => RedlineEngine,
|
|
37
|
+
RegexTimeoutError: () => RegexTimeoutError,
|
|
38
|
+
USER_PATTERN_TIMEOUT_MS: () => USER_PATTERN_TIMEOUT_MS,
|
|
37
39
|
_extractTextFromDoc: () => _extractTextFromDoc,
|
|
38
40
|
apply_edits_to_markdown: () => apply_edits_to_markdown,
|
|
39
41
|
create_unified_diff: () => create_unified_diff,
|
|
40
42
|
create_word_patch_diff: () => create_word_patch_diff,
|
|
43
|
+
describe_illegal_control_chars: () => describe_illegal_control_chars,
|
|
41
44
|
extractTextFromBuffer: () => extractTextFromBuffer,
|
|
42
45
|
extract_outline: () => extract_outline,
|
|
43
46
|
finalize_document: () => finalize_document,
|
|
@@ -45,7 +48,10 @@ __export(index_exports, {
|
|
|
45
48
|
identifyEngine: () => identifyEngine,
|
|
46
49
|
paginate: () => paginate,
|
|
47
50
|
split_structural_appendix: () => split_structural_appendix,
|
|
48
|
-
trim_common_context: () => trim_common_context
|
|
51
|
+
trim_common_context: () => trim_common_context,
|
|
52
|
+
userFindAllMatches: () => userFindAllMatches,
|
|
53
|
+
userSearch: () => userSearch,
|
|
54
|
+
validate_edit_strings: () => validate_edit_strings
|
|
49
55
|
});
|
|
50
56
|
module.exports = __toCommonJS(index_exports);
|
|
51
57
|
|
|
@@ -794,6 +800,62 @@ function extract_comments_data(pkg) {
|
|
|
794
800
|
return data;
|
|
795
801
|
}
|
|
796
802
|
|
|
803
|
+
// src/utils/safe-regex.ts
|
|
804
|
+
var vm = __toESM(require("vm"), 1);
|
|
805
|
+
var USER_PATTERN_TIMEOUT_MS = 2e3;
|
|
806
|
+
var RegexTimeoutError = class extends Error {
|
|
807
|
+
pattern;
|
|
808
|
+
constructor(pattern) {
|
|
809
|
+
super(
|
|
810
|
+
`Regular expression exceeded the ${USER_PATTERN_TIMEOUT_MS / 1e3}s matching time budget (catastrophic backtracking). Simplify the pattern \u2014 nested quantifiers like (a+)+ are the usual cause \u2014 or use a literal target instead.`
|
|
811
|
+
);
|
|
812
|
+
this.name = "RegexTimeoutError";
|
|
813
|
+
this.pattern = pattern;
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
function runBudgeted(pattern, script, sandbox) {
|
|
817
|
+
try {
|
|
818
|
+
return vm.runInNewContext(script, sandbox, {
|
|
819
|
+
timeout: USER_PATTERN_TIMEOUT_MS
|
|
820
|
+
});
|
|
821
|
+
} catch (e) {
|
|
822
|
+
if (e && e.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") {
|
|
823
|
+
throw new RegexTimeoutError(pattern);
|
|
824
|
+
}
|
|
825
|
+
throw e;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
function userFindAllMatches(pattern, text, flags = "") {
|
|
829
|
+
const normalized = flags.includes("g") ? flags : flags + "g";
|
|
830
|
+
const re = new RegExp(pattern, normalized);
|
|
831
|
+
const raw = runBudgeted(
|
|
832
|
+
pattern,
|
|
833
|
+
`{
|
|
834
|
+
const out = [];
|
|
835
|
+
let m;
|
|
836
|
+
while ((m = re.exec(text)) !== null) {
|
|
837
|
+
out.push({ start: m.index, end: m.index + m[0].length });
|
|
838
|
+
if (m.index === re.lastIndex) re.lastIndex++;
|
|
839
|
+
}
|
|
840
|
+
out;
|
|
841
|
+
}`,
|
|
842
|
+
{ re, text }
|
|
843
|
+
);
|
|
844
|
+
return raw.map((r) => ({ start: r.start, end: r.end }));
|
|
845
|
+
}
|
|
846
|
+
function userSearch(pattern, text, flags = "") {
|
|
847
|
+
const re = new RegExp(pattern, flags.replace("g", ""));
|
|
848
|
+
const raw = runBudgeted(
|
|
849
|
+
pattern,
|
|
850
|
+
`{
|
|
851
|
+
const m = re.exec(text);
|
|
852
|
+
m ? { start: m.index, end: m.index + m[0].length } : null;
|
|
853
|
+
}`,
|
|
854
|
+
{ re, text }
|
|
855
|
+
);
|
|
856
|
+
return raw ? { start: raw.start, end: raw.end } : null;
|
|
857
|
+
}
|
|
858
|
+
|
|
797
859
|
// src/utils/docx.ts
|
|
798
860
|
var QN_W_P = "w:p";
|
|
799
861
|
var QN_W_R = "w:r";
|
|
@@ -1868,10 +1930,10 @@ ${header}`;
|
|
|
1868
1930
|
find_match_index(target_text, is_regex = false) {
|
|
1869
1931
|
if (is_regex) {
|
|
1870
1932
|
try {
|
|
1871
|
-
const
|
|
1872
|
-
|
|
1873
|
-
if (match) return [match.index, match[0].length];
|
|
1933
|
+
const match = userSearch(target_text, this.full_text);
|
|
1934
|
+
if (match) return [match.start, match.end - match.start];
|
|
1874
1935
|
} catch (e) {
|
|
1936
|
+
if (e instanceof RegexTimeoutError) throw e;
|
|
1875
1937
|
}
|
|
1876
1938
|
return [-1, 0];
|
|
1877
1939
|
}
|
|
@@ -1900,11 +1962,10 @@ ${header}`;
|
|
|
1900
1962
|
if (!target_text) return [];
|
|
1901
1963
|
if (is_regex) {
|
|
1902
1964
|
try {
|
|
1903
|
-
const
|
|
1904
|
-
|
|
1905
|
-
if (matches2.length > 0)
|
|
1906
|
-
return matches2.map((m) => [m.index, m[0].length]);
|
|
1965
|
+
const matches2 = userFindAllMatches(target_text, this.full_text);
|
|
1966
|
+
if (matches2.length > 0) return matches2.map((m) => [m.start, m.end - m.start]);
|
|
1907
1967
|
} catch (e) {
|
|
1968
|
+
if (e instanceof RegexTimeoutError) throw e;
|
|
1908
1969
|
}
|
|
1909
1970
|
return [];
|
|
1910
1971
|
}
|
|
@@ -2882,7 +2943,9 @@ function apply_edits_to_markdown(markdown_text, edits, include_index = false, hi
|
|
|
2882
2943
|
isolated_target,
|
|
2883
2944
|
isolated_new,
|
|
2884
2945
|
edit.comment,
|
|
2885
|
-
|
|
2946
|
+
// 1-based, matching apply's "Edit N" reports and batch validation
|
|
2947
|
+
// errors (QA 2026-07-17 F10; mirrors Python).
|
|
2948
|
+
orig_idx + 1,
|
|
2886
2949
|
include_index,
|
|
2887
2950
|
highlight_only
|
|
2888
2951
|
);
|
|
@@ -3027,12 +3090,36 @@ function sequential_context_hint(applied_so_far) {
|
|
|
3027
3090
|
Note: ${applied_so_far} earlier edit(s) in this batch were already applied. Batches apply sequentially \u2014 each edit must target the document text as it reads AFTER the preceding edits (e.g. target the replacement text an earlier edit introduced, not the original wording).`;
|
|
3028
3091
|
}
|
|
3029
3092
|
var TRANSACTIONAL_NOT_APPLIED_ERROR = "Not applied: the batch is transactional and other edits failed validation (see their errors). Fix or remove those edits and re-run.";
|
|
3093
|
+
var XML_ILLEGAL_CHARS_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f]/g;
|
|
3094
|
+
function describe_illegal_control_chars(text) {
|
|
3095
|
+
if (!text) return null;
|
|
3096
|
+
const found = text.match(XML_ILLEGAL_CHARS_RE);
|
|
3097
|
+
if (!found) return null;
|
|
3098
|
+
const codes = Array.from(new Set(found.map((c) => `0x${c.charCodeAt(0).toString(16).padStart(2, "0")}`))).sort();
|
|
3099
|
+
return codes.join(", ");
|
|
3100
|
+
}
|
|
3030
3101
|
function validate_edit_strings(edits, index_offset = 0) {
|
|
3031
3102
|
const errors = [];
|
|
3032
3103
|
for (let i = 0; i < edits.length; i++) {
|
|
3033
3104
|
const edit = edits[i];
|
|
3034
3105
|
const t_text = edit.target_text || "";
|
|
3035
3106
|
const n_text = edit.new_text || "";
|
|
3107
|
+
const checked_fields = [
|
|
3108
|
+
["target_text", t_text],
|
|
3109
|
+
["new_text", n_text]
|
|
3110
|
+
];
|
|
3111
|
+
if (edit.comment) checked_fields.push(["comment", edit.comment]);
|
|
3112
|
+
(edit.cells || []).forEach((cell, cell_idx) => {
|
|
3113
|
+
checked_fields.push([`cells[${cell_idx}]`, cell || ""]);
|
|
3114
|
+
});
|
|
3115
|
+
for (const [field_name, field_value] of checked_fields) {
|
|
3116
|
+
const described = describe_illegal_control_chars(field_value);
|
|
3117
|
+
if (described) {
|
|
3118
|
+
errors.push(
|
|
3119
|
+
`- Edit ${i + 1 + index_offset} Failed: \`${field_name}\` contains control character(s) (${described}) that cannot be stored in a DOCX. Remove them and re-submit.`
|
|
3120
|
+
);
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3036
3123
|
if (n_text.includes("{++") || n_text.includes("{--") || n_text.includes("{>>") || n_text.includes("{==")) {
|
|
3037
3124
|
errors.push(
|
|
3038
3125
|
`- Edit ${i + 1 + index_offset} Failed: Do not manually write CriticMarkup tags ({++, {--, {>>, {==) in \`new_text\`. The engine handles redlining automatically. To add a comment, use the \`comment\` parameter.`
|
|
@@ -3339,8 +3426,8 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
3339
3426
|
* Snapshots the document text around a resolved edit BEFORE anything is
|
|
3340
3427
|
* applied. Previews rendered after the batch mutates the DOM cannot slice
|
|
3341
3428
|
* full_text at the stored offsets: applied edits shift offsets and inject
|
|
3342
|
-
* tracked-change markup,
|
|
3343
|
-
*
|
|
3429
|
+
* tracked-change markup, garbling previews with unrelated edits and
|
|
3430
|
+
* internal scaffolding (QA H1).
|
|
3344
3431
|
*/
|
|
3345
3432
|
_capture_preview_context(edit) {
|
|
3346
3433
|
if (edit.type !== "modify") return;
|
|
@@ -3441,8 +3528,8 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
3441
3528
|
}
|
|
3442
3529
|
/**
|
|
3443
3530
|
* The "new text" a batch report should show for an edit. InsertTableRow has
|
|
3444
|
-
* no new_text field — surface its cell contents
|
|
3445
|
-
* empty string
|
|
3531
|
+
* no new_text field — surface its cell contents rather than a misleading
|
|
3532
|
+
* empty string (QA M4).
|
|
3446
3533
|
*/
|
|
3447
3534
|
static _report_new_text(edit) {
|
|
3448
3535
|
if (edit && edit.type === "insert_row" && Array.isArray(edit.cells)) {
|
|
@@ -4727,7 +4814,13 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
4727
4814
|
const reports_by_input = new Array(edits.length);
|
|
4728
4815
|
const validation_failed_idx = /* @__PURE__ */ new Set();
|
|
4729
4816
|
for (const { edit, i } of ordered_edits) {
|
|
4730
|
-
let single_errors
|
|
4817
|
+
let single_errors;
|
|
4818
|
+
try {
|
|
4819
|
+
single_errors = this.validate_edits([edit], i);
|
|
4820
|
+
} catch (e) {
|
|
4821
|
+
if (!(e instanceof RegexTimeoutError)) throw e;
|
|
4822
|
+
single_errors = [`- Edit ${i + 1} Failed: ${e.message}`];
|
|
4823
|
+
}
|
|
4731
4824
|
if (single_errors.length > 0) {
|
|
4732
4825
|
if (applied_edits > 0) {
|
|
4733
4826
|
const hint = sequential_context_hint(applied_edits);
|
|
@@ -4812,7 +4905,13 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
4812
4905
|
const sequential_errors = [];
|
|
4813
4906
|
let applied_so_far = 0;
|
|
4814
4907
|
for (const { edit, i } of ordered_edits) {
|
|
4815
|
-
let single_errors
|
|
4908
|
+
let single_errors;
|
|
4909
|
+
try {
|
|
4910
|
+
single_errors = this.validate_edits([edit], i);
|
|
4911
|
+
} catch (e) {
|
|
4912
|
+
if (!(e instanceof RegexTimeoutError)) throw e;
|
|
4913
|
+
single_errors = [`- Edit ${i + 1} Failed: ${e.message}`];
|
|
4914
|
+
}
|
|
4816
4915
|
if (single_errors.length > 0) {
|
|
4817
4916
|
if (applied_so_far > 0) {
|
|
4818
4917
|
const hint = sequential_context_hint(applied_so_far);
|
|
@@ -4926,7 +5025,18 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
4926
5025
|
edit._error_msg = msg;
|
|
4927
5026
|
}
|
|
4928
5027
|
} else {
|
|
4929
|
-
|
|
5028
|
+
let resolved;
|
|
5029
|
+
try {
|
|
5030
|
+
resolved = this._pre_resolve_heuristic_edit(edit);
|
|
5031
|
+
} catch (e) {
|
|
5032
|
+
if (!(e instanceof RegexTimeoutError)) throw e;
|
|
5033
|
+
skipped++;
|
|
5034
|
+
edit._applied_status = false;
|
|
5035
|
+
const msg = `- Failed to apply edit targeting: '${(edit.target_text || "").substring(0, 40)}...' (${e.message})`;
|
|
5036
|
+
this.skipped_details.push(msg);
|
|
5037
|
+
edit._error_msg = msg;
|
|
5038
|
+
continue;
|
|
5039
|
+
}
|
|
4930
5040
|
if (resolved) {
|
|
4931
5041
|
if (Array.isArray(resolved)) {
|
|
4932
5042
|
for (const r of resolved) {
|
|
@@ -5085,6 +5195,16 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
5085
5195
|
);
|
|
5086
5196
|
continue;
|
|
5087
5197
|
}
|
|
5198
|
+
const dup_authors = Array.from(
|
|
5199
|
+
new Set(all_nodes.map((n) => n.getAttribute("w:author") || "Unknown"))
|
|
5200
|
+
).sort();
|
|
5201
|
+
if (dup_authors.length > 1) {
|
|
5202
|
+
skipped++;
|
|
5203
|
+
this.skipped_details.push(
|
|
5204
|
+
`- Failed to apply action: ${type} on Chg:${target_id} is ambiguous. The document contains ${all_nodes.length} tracked-change elements sharing w:id=${target_id} from different authors (${dup_authors.join(", ")}) \u2014 duplicate revision IDs produced outside this engine (e.g. by a document merge or copy-paste). Acting on this ID would resolve all of them at once. Resolve these changes individually in Word, or apply the intended outcome as an explicit text edit instead.`
|
|
5205
|
+
);
|
|
5206
|
+
continue;
|
|
5207
|
+
}
|
|
5088
5208
|
for (const node of all_nodes) {
|
|
5089
5209
|
const is_ins = node.tagName === "w:ins";
|
|
5090
5210
|
const parent_tag = node.parentNode ? node.parentNode.tagName : "";
|
|
@@ -6400,6 +6520,27 @@ function scrub_doc_properties(doc) {
|
|
|
6400
6520
|
c.textContent = "";
|
|
6401
6521
|
}
|
|
6402
6522
|
});
|
|
6523
|
+
const titles = findDescendantsByLocalName(corePart._element, "title");
|
|
6524
|
+
titles.forEach((c) => {
|
|
6525
|
+
if (c.textContent) {
|
|
6526
|
+
lines.push(`Title kept (review manually): "${c.textContent}"`);
|
|
6527
|
+
}
|
|
6528
|
+
});
|
|
6529
|
+
const leakFields = [
|
|
6530
|
+
["category", "Category"],
|
|
6531
|
+
["keywords", "Keywords"],
|
|
6532
|
+
["subject", "Subject"],
|
|
6533
|
+
["contentStatus", "Content status"],
|
|
6534
|
+
["description", "Description/comments"]
|
|
6535
|
+
];
|
|
6536
|
+
for (const [local, label] of leakFields) {
|
|
6537
|
+
findDescendantsByLocalName(corePart._element, local).forEach((c) => {
|
|
6538
|
+
if (c.textContent) {
|
|
6539
|
+
lines.push(`${label}: ${c.textContent}`);
|
|
6540
|
+
c.textContent = "";
|
|
6541
|
+
}
|
|
6542
|
+
});
|
|
6543
|
+
}
|
|
6403
6544
|
const revisions = findDescendantsByLocalName(corePart._element, "revision");
|
|
6404
6545
|
revisions.forEach((c) => {
|
|
6405
6546
|
if (c.textContent && parseInt(c.textContent) > 1) {
|
|
@@ -6605,7 +6746,11 @@ function extract_all_domain_metadata(doc, base_text) {
|
|
|
6605
6746
|
for (const term of def_keys) {
|
|
6606
6747
|
if (definitions[term].count === 0) {
|
|
6607
6748
|
delete definitions[term];
|
|
6608
|
-
duplicates.
|
|
6749
|
+
if (!duplicates.has(term)) {
|
|
6750
|
+
diagnostics.push(
|
|
6751
|
+
`[Warning] Unused Definition: '${term}' is defined but never used.`
|
|
6752
|
+
);
|
|
6753
|
+
}
|
|
6609
6754
|
}
|
|
6610
6755
|
}
|
|
6611
6756
|
}
|
|
@@ -7941,10 +8086,13 @@ function identifyEngine() {
|
|
|
7941
8086
|
DocumentMapper,
|
|
7942
8087
|
DocumentObject,
|
|
7943
8088
|
RedlineEngine,
|
|
8089
|
+
RegexTimeoutError,
|
|
8090
|
+
USER_PATTERN_TIMEOUT_MS,
|
|
7944
8091
|
_extractTextFromDoc,
|
|
7945
8092
|
apply_edits_to_markdown,
|
|
7946
8093
|
create_unified_diff,
|
|
7947
8094
|
create_word_patch_diff,
|
|
8095
|
+
describe_illegal_control_chars,
|
|
7948
8096
|
extractTextFromBuffer,
|
|
7949
8097
|
extract_outline,
|
|
7950
8098
|
finalize_document,
|
|
@@ -7952,6 +8100,9 @@ function identifyEngine() {
|
|
|
7952
8100
|
identifyEngine,
|
|
7953
8101
|
paginate,
|
|
7954
8102
|
split_structural_appendix,
|
|
7955
|
-
trim_common_context
|
|
8103
|
+
trim_common_context,
|
|
8104
|
+
userFindAllMatches,
|
|
8105
|
+
userSearch,
|
|
8106
|
+
validate_edit_strings
|
|
7956
8107
|
});
|
|
7957
8108
|
//# sourceMappingURL=index.cjs.map
|