@adeu/core 1.17.2 → 1.18.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adeu/core",
3
- "version": "1.17.2",
3
+ "version": "1.18.1",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -418,7 +418,7 @@ describe("Resolved Bugs Core Engine Verification", () => {
418
418
  expect(pTarget.parentNode).toBeNull();
419
419
  });
420
420
 
421
- it("BUG-EXPLORE-2: Nested redline validation error includes actionable hint", async () => {
421
+ it("BUG-EXPLORE-2: a strict edit inside a foreign author's insertion applies (nested change)", async () => {
422
422
  const doc = await createTestDocument();
423
423
  addParagraph(doc, "Original baseline.");
424
424
 
@@ -432,20 +432,18 @@ describe("Resolved Bugs Core Engine Verification", () => {
432
432
  },
433
433
  ]);
434
434
 
435
- // Author B tries to modify Author A's pending insertion
435
+ // Author B modifies Author A's pending insertion — this now applies: the
436
+ // enclosing <w:ins> is split and Author B's change nested inside it.
436
437
  const engineB = new RedlineEngine(doc, "Author B");
437
-
438
- expect(() => {
439
- engineB.process_batch([
440
- {
441
- type: "modify",
442
- target_text: "Inserted by A.",
443
- new_text: "Modified by B.",
444
- },
445
- ]);
446
- }).toThrowError(
447
- /Accept that change first or scope your edit outside of it/,
448
- );
438
+ const result = engineB.process_batch([
439
+ {
440
+ type: "modify",
441
+ target_text: "Inserted by A.",
442
+ new_text: "Modified by B.",
443
+ },
444
+ ]);
445
+ expect(result.edits_applied).toBe(1);
446
+ expect(result.edits_skipped).toBe(0);
449
447
  });
450
448
 
451
449
  it("BUG-CROSS-PARA-1: Cross-paragraph modify coalesces paragraphs and tracks para-mark deletion", async () => {
@@ -31,7 +31,11 @@ describe("Feedback Layer & Dry Run Verification", () => {
31
31
  expect(stats.version).toBeDefined();
32
32
  });
33
33
 
34
- it("punctuation anchor triggers warning", async () => {
34
+ it("punctuation anchor: no warning on clean apply", async () => {
35
+ // A punctuated anchor that matches and applies cleanly must NOT raise the
36
+ // tokenization-splitting warning. The redline preview already reports the
37
+ // change; a warning here is a false positive that pushes agents into
38
+ // needless "cleaner anchor" retries.
35
39
  const doc = await createTestDocument();
36
40
  addParagraph(doc, "Refer to sample_term_name in Section 4.");
37
41
  const engine = new RedlineEngine(doc, "Reviewer TS");
@@ -41,9 +45,37 @@ describe("Feedback Layer & Dry Run Verification", () => {
41
45
  ]);
42
46
 
43
47
  const report = stats.edits[0];
48
+ expect(report.status).toBe("applied");
49
+ expect(report.warning).toBeNull();
50
+
51
+ // Same expectation under dry_run.
52
+ const doc2 = await createTestDocument();
53
+ addParagraph(doc2, "Refer to sample_term_name in Section 4.");
54
+ const engine2 = new RedlineEngine(doc2, "Reviewer TS");
55
+ const dryStats = (engine2 as any).process_batch([
56
+ { type: "modify", target_text: "sample_term_name", new_text: "validated_term_name" }
57
+ ], true);
58
+ const dryReport = dryStats.edits[0];
59
+ expect(dryReport.status).toBe("applied");
60
+ expect(dryReport.warning).toBeNull();
61
+ });
62
+
63
+ it("punctuation anchor: warns only when match fails", async () => {
64
+ // When a punctuated anchor fails to match, the warning IS surfaced as
65
+ // recovery context (the punctuation may be why the match missed).
66
+ const doc = await createTestDocument();
67
+ addParagraph(doc, "Refer to sample_term_name in Section 4.");
68
+ const engine = new RedlineEngine(doc, "Reviewer TS");
69
+
70
+ const stats = (engine as any).process_batch([
71
+ { type: "modify", target_text: "phantom_term-x", new_text: "anything" }
72
+ ], true);
73
+
74
+ const report = stats.edits[0];
75
+ expect(report.status).toBe("failed");
44
76
  expect(report.warning).not.toBeNull();
45
77
  expect(report.warning.toLowerCase()).toContain("punctuation");
46
- expect(report.warning).toContain("sample_term_name");
78
+ expect(report.warning).toContain("phantom_term-x");
47
79
  });
48
80
 
49
81
  it("dry_run does not mutate and reports safely", async () => {
@@ -422,31 +422,36 @@ describe("BUG-23-4: multi-paragraph target_text must produce actionable feedback
422
422
  }
423
423
  });
424
424
 
425
- it("BUG-23-4-NN: rejects plain-paragraph N->N modifications spanning a paragraph boundary to prevent silent corruption", async () => {
425
+ it("BUG-23-4-NN: applies balanced plain-paragraph N->N modifications per paragraph, preserving the boundary", async () => {
426
426
  const doc = await createTestDocument();
427
427
  addParagraph(doc, "Clause 1 ends here.");
428
428
  addParagraph(doc, "Clause 2 begins here.");
429
429
 
430
430
  const engine = new RedlineEngine(doc, "Test Author");
431
431
 
432
- let raised: any = null;
433
- try {
434
- engine.process_batch([
435
- {
436
- type: "modify",
437
- target_text: "ends here.\n\nClause 2 begins",
438
- new_text: "ends here. MERGED\n\nClause 2 begins CHANGED",
439
- },
440
- ]);
441
- } catch (e: any) {
442
- raised = e;
443
- }
432
+ // Both target and replacement carry the same number of \n\n breaks, so the
433
+ // paragraph structure is preserved. The engine splits this into one sub-edit
434
+ // per paragraph and applies it as a single logical edit. (Unbalanced
435
+ // merges/splits are still rejected — see the regex case in engine.qa.test.)
436
+ const result = engine.process_batch([
437
+ {
438
+ type: "modify",
439
+ target_text: "ends here.\n\nClause 2 begins",
440
+ new_text: "ends here. MERGED\n\nClause 2 begins CHANGED",
441
+ },
442
+ ]);
444
443
 
445
- expect(raised).not.toBeNull();
446
- if (raised) {
447
- expect(raised.name).toBe("BatchValidationError");
448
- expect(raised.message.toLowerCase()).toContain("paragraph boundary");
449
- }
444
+ expect(result.edits_applied).toBe(1);
445
+ expect(result.edits_skipped).toBe(0);
446
+
447
+ const buf = await doc.save();
448
+ const text = await extractTextFromBuffer(buf);
449
+ // The paragraph boundary must survive — the two clauses are NOT merged.
450
+ expect(text).not.toContain("ends here.Clause 2 begins");
451
+ expect(text).not.toContain("ends here. Clause 2 begins");
452
+ // Both per-paragraph insertions land.
453
+ expect(text).toContain("MERGED");
454
+ expect(text).toContain("CHANGED");
450
455
  });
451
456
  });
452
457