docrev 0.8.1 → 0.8.5
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/.claude/settings.local.json +9 -0
- package/PLAN-tables-and-postprocess.md +850 -0
- package/README.md +33 -0
- package/bin/rev.js +12 -131
- package/bin/rev.ts +145 -0
- package/dist/bin/rev.d.ts +9 -0
- package/dist/bin/rev.d.ts.map +1 -0
- package/dist/bin/rev.js +118 -0
- package/dist/bin/rev.js.map +1 -0
- package/dist/lib/annotations.d.ts +91 -0
- package/dist/lib/annotations.d.ts.map +1 -0
- package/dist/lib/annotations.js +554 -0
- package/dist/lib/annotations.js.map +1 -0
- package/dist/lib/build.d.ts +171 -0
- package/dist/lib/build.d.ts.map +1 -0
- package/dist/lib/build.js +755 -0
- package/dist/lib/build.js.map +1 -0
- package/dist/lib/citations.d.ts +34 -0
- package/dist/lib/citations.d.ts.map +1 -0
- package/dist/lib/citations.js +140 -0
- package/dist/lib/citations.js.map +1 -0
- package/dist/lib/commands/build.d.ts +13 -0
- package/dist/lib/commands/build.d.ts.map +1 -0
- package/dist/lib/commands/build.js +678 -0
- package/dist/lib/commands/build.js.map +1 -0
- package/dist/lib/commands/citations.d.ts +11 -0
- package/dist/lib/commands/citations.d.ts.map +1 -0
- package/dist/lib/commands/citations.js +428 -0
- package/dist/lib/commands/citations.js.map +1 -0
- package/dist/lib/commands/comments.d.ts +11 -0
- package/dist/lib/commands/comments.d.ts.map +1 -0
- package/dist/lib/commands/comments.js +883 -0
- package/dist/lib/commands/comments.js.map +1 -0
- package/dist/lib/commands/context.d.ts +35 -0
- package/dist/lib/commands/context.d.ts.map +1 -0
- package/dist/lib/commands/context.js +59 -0
- package/dist/lib/commands/context.js.map +1 -0
- package/dist/lib/commands/core.d.ts +11 -0
- package/dist/lib/commands/core.d.ts.map +1 -0
- package/dist/lib/commands/core.js +246 -0
- package/dist/lib/commands/core.js.map +1 -0
- package/dist/lib/commands/doi.d.ts +11 -0
- package/dist/lib/commands/doi.d.ts.map +1 -0
- package/dist/lib/commands/doi.js +373 -0
- package/dist/lib/commands/doi.js.map +1 -0
- package/dist/lib/commands/history.d.ts +11 -0
- package/dist/lib/commands/history.d.ts.map +1 -0
- package/dist/lib/commands/history.js +245 -0
- package/dist/lib/commands/history.js.map +1 -0
- package/dist/lib/commands/index.d.ts +28 -0
- package/dist/lib/commands/index.d.ts.map +1 -0
- package/dist/lib/commands/index.js +35 -0
- package/dist/lib/commands/index.js.map +1 -0
- package/dist/lib/commands/init.d.ts +11 -0
- package/dist/lib/commands/init.d.ts.map +1 -0
- package/dist/lib/commands/init.js +209 -0
- package/dist/lib/commands/init.js.map +1 -0
- package/dist/lib/commands/response.d.ts +11 -0
- package/dist/lib/commands/response.d.ts.map +1 -0
- package/dist/lib/commands/response.js +317 -0
- package/dist/lib/commands/response.js.map +1 -0
- package/dist/lib/commands/sections.d.ts +11 -0
- package/dist/lib/commands/sections.d.ts.map +1 -0
- package/dist/lib/commands/sections.js +1071 -0
- package/dist/lib/commands/sections.js.map +1 -0
- package/dist/lib/commands/utilities.d.ts +19 -0
- package/dist/lib/commands/utilities.d.ts.map +1 -0
- package/dist/lib/commands/utilities.js +2009 -0
- package/dist/lib/commands/utilities.js.map +1 -0
- package/dist/lib/comment-realign.d.ts +50 -0
- package/dist/lib/comment-realign.d.ts.map +1 -0
- package/dist/lib/comment-realign.js +372 -0
- package/dist/lib/comment-realign.js.map +1 -0
- package/dist/lib/config.d.ts +41 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +76 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/crossref.d.ts +108 -0
- package/dist/lib/crossref.d.ts.map +1 -0
- package/dist/lib/crossref.js +597 -0
- package/dist/lib/crossref.js.map +1 -0
- package/dist/lib/dependencies.d.ts +30 -0
- package/dist/lib/dependencies.d.ts.map +1 -0
- package/dist/lib/dependencies.js +95 -0
- package/dist/lib/dependencies.js.map +1 -0
- package/dist/lib/doi-cache.d.ts +29 -0
- package/dist/lib/doi-cache.d.ts.map +1 -0
- package/dist/lib/doi-cache.js +104 -0
- package/dist/lib/doi-cache.js.map +1 -0
- package/dist/lib/doi.d.ts +65 -0
- package/dist/lib/doi.d.ts.map +1 -0
- package/dist/lib/doi.js +710 -0
- package/dist/lib/doi.js.map +1 -0
- package/dist/lib/equations.d.ts +61 -0
- package/dist/lib/equations.d.ts.map +1 -0
- package/dist/lib/equations.js +445 -0
- package/dist/lib/equations.js.map +1 -0
- package/dist/lib/errors.d.ts +60 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +303 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/format.d.ts +104 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/format.js +416 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/lib/git.d.ts +88 -0
- package/dist/lib/git.d.ts.map +1 -0
- package/dist/lib/git.js +304 -0
- package/dist/lib/git.js.map +1 -0
- package/dist/lib/grammar.d.ts +62 -0
- package/dist/lib/grammar.d.ts.map +1 -0
- package/dist/lib/grammar.js +244 -0
- package/dist/lib/grammar.js.map +1 -0
- package/dist/lib/image-registry.d.ts +68 -0
- package/dist/lib/image-registry.d.ts.map +1 -0
- package/dist/lib/image-registry.js +112 -0
- package/dist/lib/image-registry.js.map +1 -0
- package/dist/lib/import.d.ts +184 -0
- package/dist/lib/import.d.ts.map +1 -0
- package/dist/lib/import.js +1581 -0
- package/dist/lib/import.js.map +1 -0
- package/dist/lib/journals.d.ts +55 -0
- package/dist/lib/journals.d.ts.map +1 -0
- package/dist/lib/journals.js +417 -0
- package/dist/lib/journals.js.map +1 -0
- package/dist/lib/merge.d.ts +138 -0
- package/dist/lib/merge.d.ts.map +1 -0
- package/dist/lib/merge.js +603 -0
- package/dist/lib/merge.js.map +1 -0
- package/dist/lib/orcid.d.ts +36 -0
- package/dist/lib/orcid.d.ts.map +1 -0
- package/dist/lib/orcid.js +117 -0
- package/dist/lib/orcid.js.map +1 -0
- package/dist/lib/pdf-comments.d.ts +95 -0
- package/dist/lib/pdf-comments.d.ts.map +1 -0
- package/dist/lib/pdf-comments.js +192 -0
- package/dist/lib/pdf-comments.js.map +1 -0
- package/dist/lib/pdf-import.d.ts +118 -0
- package/dist/lib/pdf-import.d.ts.map +1 -0
- package/dist/lib/pdf-import.js +397 -0
- package/dist/lib/pdf-import.js.map +1 -0
- package/dist/lib/plugins.d.ts +76 -0
- package/dist/lib/plugins.d.ts.map +1 -0
- package/dist/lib/plugins.js +235 -0
- package/dist/lib/plugins.js.map +1 -0
- package/dist/lib/postprocess.d.ts +42 -0
- package/dist/lib/postprocess.d.ts.map +1 -0
- package/dist/lib/postprocess.js +138 -0
- package/dist/lib/postprocess.js.map +1 -0
- package/dist/lib/pptx-template.d.ts +59 -0
- package/dist/lib/pptx-template.d.ts.map +1 -0
- package/dist/lib/pptx-template.js +613 -0
- package/dist/lib/pptx-template.js.map +1 -0
- package/dist/lib/pptx-themes.d.ts +80 -0
- package/dist/lib/pptx-themes.d.ts.map +1 -0
- package/dist/lib/pptx-themes.js +818 -0
- package/dist/lib/pptx-themes.js.map +1 -0
- package/dist/lib/protect-restore.d.ts +137 -0
- package/dist/lib/protect-restore.d.ts.map +1 -0
- package/dist/lib/protect-restore.js +394 -0
- package/dist/lib/protect-restore.js.map +1 -0
- package/dist/lib/rate-limiter.d.ts +27 -0
- package/dist/lib/rate-limiter.d.ts.map +1 -0
- package/dist/lib/rate-limiter.js +79 -0
- package/dist/lib/rate-limiter.js.map +1 -0
- package/dist/lib/response.d.ts +41 -0
- package/dist/lib/response.d.ts.map +1 -0
- package/dist/lib/response.js +150 -0
- package/dist/lib/response.js.map +1 -0
- package/dist/lib/review.d.ts +35 -0
- package/dist/lib/review.d.ts.map +1 -0
- package/dist/lib/review.js +263 -0
- package/dist/lib/review.js.map +1 -0
- package/dist/lib/schema.d.ts +66 -0
- package/dist/lib/schema.d.ts.map +1 -0
- package/dist/lib/schema.js +339 -0
- package/dist/lib/schema.js.map +1 -0
- package/dist/lib/scientific-words.d.ts +6 -0
- package/dist/lib/scientific-words.d.ts.map +1 -0
- package/dist/lib/scientific-words.js +66 -0
- package/dist/lib/scientific-words.js.map +1 -0
- package/dist/lib/sections.d.ts +40 -0
- package/dist/lib/sections.d.ts.map +1 -0
- package/dist/lib/sections.js +288 -0
- package/dist/lib/sections.js.map +1 -0
- package/dist/lib/slides.d.ts +86 -0
- package/dist/lib/slides.d.ts.map +1 -0
- package/dist/lib/slides.js +676 -0
- package/dist/lib/slides.js.map +1 -0
- package/dist/lib/spelling.d.ts +76 -0
- package/dist/lib/spelling.d.ts.map +1 -0
- package/dist/lib/spelling.js +272 -0
- package/dist/lib/spelling.js.map +1 -0
- package/dist/lib/templates.d.ts +30 -0
- package/dist/lib/templates.d.ts.map +1 -0
- package/dist/lib/templates.js +504 -0
- package/dist/lib/templates.js.map +1 -0
- package/dist/lib/themes.d.ts +85 -0
- package/dist/lib/themes.d.ts.map +1 -0
- package/dist/lib/themes.js +652 -0
- package/dist/lib/themes.js.map +1 -0
- package/dist/lib/trackchanges.d.ts +51 -0
- package/dist/lib/trackchanges.d.ts.map +1 -0
- package/dist/lib/trackchanges.js +202 -0
- package/dist/lib/trackchanges.js.map +1 -0
- package/dist/lib/tui.d.ts +76 -0
- package/dist/lib/tui.d.ts.map +1 -0
- package/dist/lib/tui.js +377 -0
- package/dist/lib/tui.js.map +1 -0
- package/dist/lib/types.d.ts +447 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +6 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/undo.d.ts +57 -0
- package/dist/lib/undo.d.ts.map +1 -0
- package/dist/lib/undo.js +185 -0
- package/dist/lib/undo.js.map +1 -0
- package/dist/lib/utils.d.ts +16 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +40 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/lib/variables.d.ts +42 -0
- package/dist/lib/variables.d.ts.map +1 -0
- package/dist/lib/variables.js +141 -0
- package/dist/lib/variables.js.map +1 -0
- package/dist/lib/word.d.ts +80 -0
- package/dist/lib/word.d.ts.map +1 -0
- package/dist/lib/word.js +360 -0
- package/dist/lib/word.js.map +1 -0
- package/dist/lib/wordcomments.d.ts +51 -0
- package/dist/lib/wordcomments.d.ts.map +1 -0
- package/dist/lib/wordcomments.js +587 -0
- package/dist/lib/wordcomments.js.map +1 -0
- package/eslint.config.js +27 -0
- package/lib/annotations.ts +622 -0
- package/lib/apply-buildup-colors.py +88 -0
- package/lib/build.ts +1013 -0
- package/lib/{citations.js → citations.ts} +38 -27
- package/lib/commands/{build.js → build.ts} +80 -27
- package/lib/commands/{citations.js → citations.ts} +36 -18
- package/lib/commands/{comments.js → comments.ts} +187 -54
- package/lib/commands/{context.js → context.ts} +18 -8
- package/lib/commands/{core.js → core.ts} +34 -20
- package/lib/commands/{doi.js → doi.ts} +32 -16
- package/lib/commands/{history.js → history.ts} +25 -12
- package/lib/commands/{index.js → index.ts} +9 -5
- package/lib/commands/{init.js → init.ts} +20 -8
- package/lib/commands/{response.js → response.ts} +47 -20
- package/lib/commands/{sections.js → sections.ts} +273 -68
- package/lib/commands/{utilities.js → utilities.ts} +338 -158
- package/lib/{comment-realign.js → comment-realign.ts} +117 -45
- package/lib/config.ts +84 -0
- package/lib/{crossref.js → crossref.ts} +213 -138
- package/lib/dependencies.ts +106 -0
- package/lib/doi-cache.ts +115 -0
- package/lib/{doi.js → doi.ts} +115 -281
- package/lib/{equations.js → equations.ts} +60 -64
- package/lib/{errors.js → errors.ts} +56 -48
- package/lib/{format.js → format.ts} +137 -63
- package/lib/{git.js → git.ts} +66 -63
- package/lib/{grammar.js → grammar.ts} +45 -32
- package/lib/image-registry.ts +180 -0
- package/lib/import.ts +2060 -0
- package/lib/journals.ts +505 -0
- package/lib/{merge.js → merge.ts} +185 -135
- package/lib/{orcid.js → orcid.ts} +17 -22
- package/lib/{pdf-comments.js → pdf-comments.ts} +76 -18
- package/lib/{pdf-import.js → pdf-import.ts} +148 -70
- package/lib/{plugins.js → plugins.ts} +82 -39
- package/lib/postprocess.ts +188 -0
- package/lib/pptx-color-filter.lua +37 -0
- package/lib/pptx-template.ts +625 -0
- package/lib/pptx-themes/academic.pptx +0 -0
- package/lib/pptx-themes/corporate.pptx +0 -0
- package/lib/pptx-themes/dark.pptx +0 -0
- package/lib/pptx-themes/default.pptx +0 -0
- package/lib/pptx-themes/minimal.pptx +0 -0
- package/lib/pptx-themes/plant.pptx +0 -0
- package/lib/pptx-themes.ts +896 -0
- package/lib/protect-restore.ts +516 -0
- package/lib/rate-limiter.ts +94 -0
- package/lib/{response.js → response.ts} +36 -21
- package/lib/{review.js → review.ts} +53 -43
- package/lib/{schema.js → schema.ts} +70 -25
- package/lib/{sections.js → sections.ts} +71 -76
- package/lib/slides.ts +793 -0
- package/lib/{spelling.js → spelling.ts} +43 -59
- package/lib/{templates.js → templates.ts} +20 -17
- package/lib/themes.ts +742 -0
- package/lib/{trackchanges.js → trackchanges.ts} +52 -23
- package/lib/types.ts +509 -0
- package/lib/{undo.js → undo.ts} +75 -52
- package/lib/utils.ts +41 -0
- package/lib/{variables.js → variables.ts} +60 -54
- package/lib/word.ts +428 -0
- package/lib/{wordcomments.js → wordcomments.ts} +94 -40
- package/package.json +15 -5
- package/skill/REFERENCE.md +67 -0
- package/tsconfig.json +26 -0
- package/lib/annotations.js +0 -414
- package/lib/build.js +0 -639
- package/lib/config.js +0 -79
- package/lib/import.js +0 -1145
- package/lib/journals.js +0 -629
- package/lib/word.js +0 -225
- /package/lib/{scientific-words.js → scientific-words.ts} +0 -0
|
@@ -10,42 +10,79 @@ import * as path from 'path';
|
|
|
10
10
|
import * as crypto from 'crypto';
|
|
11
11
|
import { diffWords, diffSentences } from 'diff';
|
|
12
12
|
import { extractFromWord, extractWordComments } from './import.js';
|
|
13
|
+
import type { ReviewerChange, Conflict, MergeResult } from './types.js';
|
|
13
14
|
|
|
14
|
-
//
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// Constants
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
/** Directory for revision tracking data */
|
|
15
20
|
const REV_DIR = '.rev';
|
|
21
|
+
|
|
22
|
+
/** Path to base document for three-way merge */
|
|
16
23
|
const BASE_FILE = '.rev/base.docx';
|
|
24
|
+
|
|
25
|
+
/** Path to conflict resolution state */
|
|
17
26
|
const CONFLICTS_FILE = '.rev/conflicts.json';
|
|
18
27
|
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
* @typedef {Object} ReviewerChange
|
|
22
|
-
* @property {string} reviewer - Reviewer name/identifier
|
|
23
|
-
* @property {string} type - 'insert' | 'delete' | 'replace'
|
|
24
|
-
* @property {number} start - Start position in original text
|
|
25
|
-
* @property {number} end - End position in original text
|
|
26
|
-
* @property {string} oldText - Original text (for delete/replace)
|
|
27
|
-
* @property {string} newText - New text (for insert/replace)
|
|
28
|
-
* @property {string} [date] - Date of change (from Word track changes)
|
|
29
|
-
*/
|
|
28
|
+
/** Minimum word length for similarity calculations */
|
|
29
|
+
const MIN_WORD_LENGTH = 2;
|
|
30
30
|
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
31
|
+
/** Similarity threshold below which changes are considered conflicts */
|
|
32
|
+
const CONFLICT_SIMILARITY_THRESHOLD = 0.8;
|
|
33
|
+
|
|
34
|
+
/** Characters of context for change attribution */
|
|
35
|
+
const CHANGE_CONTEXT_SIZE = 50;
|
|
36
|
+
|
|
37
|
+
// =============================================================================
|
|
38
|
+
// Interfaces
|
|
39
|
+
// =============================================================================
|
|
40
|
+
|
|
41
|
+
interface ReviewerDoc {
|
|
42
|
+
path: string;
|
|
43
|
+
name: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface ReviewerComment {
|
|
47
|
+
text: string;
|
|
48
|
+
reviewer: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface MergeOptions {
|
|
52
|
+
diffLevel?: 'sentence' | 'word';
|
|
53
|
+
autoResolve?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface CheckMatchResult {
|
|
57
|
+
matches: boolean;
|
|
58
|
+
similarity: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface ConflictDetectionResult {
|
|
62
|
+
conflicts: Conflict[];
|
|
63
|
+
nonConflicting: ReviewerChange[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface ConflictsData {
|
|
67
|
+
base: string;
|
|
68
|
+
merged: string;
|
|
69
|
+
conflicts: Conflict[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// =============================================================================
|
|
73
|
+
// Public API
|
|
74
|
+
// =============================================================================
|
|
43
75
|
|
|
44
76
|
/**
|
|
45
|
-
* Initialize .rev directory
|
|
46
|
-
* @param
|
|
77
|
+
* Initialize .rev directory for revision tracking
|
|
78
|
+
* @param projectDir - Project directory path
|
|
79
|
+
* @throws {TypeError} If projectDir is not a string
|
|
47
80
|
*/
|
|
48
|
-
export function initRevDir(projectDir) {
|
|
81
|
+
export function initRevDir(projectDir: string): void {
|
|
82
|
+
if (typeof projectDir !== 'string') {
|
|
83
|
+
throw new TypeError(`projectDir must be a string, got ${typeof projectDir}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
49
86
|
const revDir = path.join(projectDir, REV_DIR);
|
|
50
87
|
if (!fs.existsSync(revDir)) {
|
|
51
88
|
fs.mkdirSync(revDir, { recursive: true });
|
|
@@ -55,10 +92,22 @@ export function initRevDir(projectDir) {
|
|
|
55
92
|
/**
|
|
56
93
|
* Store the base document for three-way merge
|
|
57
94
|
* Overwrites any previous base document
|
|
58
|
-
* @param
|
|
59
|
-
* @param
|
|
95
|
+
* @param projectDir - Project directory path
|
|
96
|
+
* @param docxPath - Path to the built docx to store as base
|
|
97
|
+
* @throws {TypeError} If arguments are not strings
|
|
98
|
+
* @throws {Error} If docxPath does not exist
|
|
60
99
|
*/
|
|
61
|
-
export function storeBaseDocument(projectDir, docxPath) {
|
|
100
|
+
export function storeBaseDocument(projectDir: string, docxPath: string): void {
|
|
101
|
+
if (typeof projectDir !== 'string') {
|
|
102
|
+
throw new TypeError(`projectDir must be a string, got ${typeof projectDir}`);
|
|
103
|
+
}
|
|
104
|
+
if (typeof docxPath !== 'string') {
|
|
105
|
+
throw new TypeError(`docxPath must be a string, got ${typeof docxPath}`);
|
|
106
|
+
}
|
|
107
|
+
if (!fs.existsSync(docxPath)) {
|
|
108
|
+
throw new Error(`Source document not found: ${docxPath}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
62
111
|
initRevDir(projectDir);
|
|
63
112
|
const basePath = path.join(projectDir, BASE_FILE);
|
|
64
113
|
fs.copyFileSync(docxPath, basePath);
|
|
@@ -66,10 +115,15 @@ export function storeBaseDocument(projectDir, docxPath) {
|
|
|
66
115
|
|
|
67
116
|
/**
|
|
68
117
|
* Get the base document path if it exists
|
|
69
|
-
* @param
|
|
70
|
-
* @returns
|
|
118
|
+
* @param projectDir - Project directory path
|
|
119
|
+
* @returns Path to base document or null if not found
|
|
120
|
+
* @throws {TypeError} If projectDir is not a string
|
|
71
121
|
*/
|
|
72
|
-
export function getBaseDocument(projectDir) {
|
|
122
|
+
export function getBaseDocument(projectDir: string): string | null {
|
|
123
|
+
if (typeof projectDir !== 'string') {
|
|
124
|
+
throw new TypeError(`projectDir must be a string, got ${typeof projectDir}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
73
127
|
const basePath = path.join(projectDir, BASE_FILE);
|
|
74
128
|
if (fs.existsSync(basePath)) {
|
|
75
129
|
return basePath;
|
|
@@ -79,22 +133,31 @@ export function getBaseDocument(projectDir) {
|
|
|
79
133
|
|
|
80
134
|
/**
|
|
81
135
|
* Check if base document exists
|
|
82
|
-
* @param
|
|
83
|
-
* @returns
|
|
136
|
+
* @param projectDir - Project directory path
|
|
137
|
+
* @returns True if base document exists
|
|
138
|
+
* @throws {TypeError} If projectDir is not a string
|
|
84
139
|
*/
|
|
85
|
-
export function hasBaseDocument(projectDir) {
|
|
140
|
+
export function hasBaseDocument(projectDir: string): boolean {
|
|
141
|
+
if (typeof projectDir !== 'string') {
|
|
142
|
+
throw new TypeError(`projectDir must be a string, got ${typeof projectDir}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
86
145
|
return fs.existsSync(path.join(projectDir, BASE_FILE));
|
|
87
146
|
}
|
|
88
147
|
|
|
89
148
|
/**
|
|
90
|
-
* Compute text similarity between two strings
|
|
91
|
-
* @param
|
|
92
|
-
* @param
|
|
93
|
-
* @returns
|
|
149
|
+
* Compute text similarity between two strings using Jaccard-like coefficient
|
|
150
|
+
* @param text1 - First text to compare
|
|
151
|
+
* @param text2 - Second text to compare
|
|
152
|
+
* @returns Similarity score 0-1 (0 = no similarity, 1 = identical)
|
|
94
153
|
*/
|
|
95
|
-
export function computeSimilarity(text1, text2) {
|
|
96
|
-
|
|
97
|
-
|
|
154
|
+
export function computeSimilarity(text1: string, text2: string): number {
|
|
155
|
+
if (typeof text1 !== 'string' || typeof text2 !== 'string') {
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const words1 = new Set(text1.toLowerCase().split(/\s+/).filter(w => w.length > MIN_WORD_LENGTH));
|
|
160
|
+
const words2 = text2.toLowerCase().split(/\s+/).filter(w => w.length > MIN_WORD_LENGTH);
|
|
98
161
|
if (words1.size === 0 || words2.length === 0) return 0;
|
|
99
162
|
const common = words2.filter(w => words1.has(w)).length;
|
|
100
163
|
return common / Math.max(words1.size, words2.length);
|
|
@@ -102,11 +165,8 @@ export function computeSimilarity(text1, text2) {
|
|
|
102
165
|
|
|
103
166
|
/**
|
|
104
167
|
* Check if base document matches reviewer document (similarity check)
|
|
105
|
-
* @param {string} basePath
|
|
106
|
-
* @param {string} reviewerPath
|
|
107
|
-
* @returns {Promise<{matches: boolean, similarity: number}>}
|
|
108
168
|
*/
|
|
109
|
-
export async function checkBaseMatch(basePath, reviewerPath) {
|
|
169
|
+
export async function checkBaseMatch(basePath: string, reviewerPath: string): Promise<CheckMatchResult> {
|
|
110
170
|
try {
|
|
111
171
|
const { text: baseText } = await extractFromWord(basePath);
|
|
112
172
|
const { text: reviewerText } = await extractFromWord(reviewerPath);
|
|
@@ -120,13 +180,12 @@ export async function checkBaseMatch(basePath, reviewerPath) {
|
|
|
120
180
|
/**
|
|
121
181
|
* Extract changes from a Word document compared to original
|
|
122
182
|
* Uses sentence-level diffing for better conflict detection
|
|
123
|
-
* @param
|
|
124
|
-
* @param
|
|
125
|
-
* @param
|
|
126
|
-
* @returns {ReviewerChange[]}
|
|
183
|
+
* @param originalText - Original text (from base document)
|
|
184
|
+
* @param wordText - Text extracted from reviewer's Word doc
|
|
185
|
+
* @param reviewer - Reviewer identifier
|
|
127
186
|
*/
|
|
128
|
-
export function extractChanges(originalText, wordText, reviewer) {
|
|
129
|
-
const changes = [];
|
|
187
|
+
export function extractChanges(originalText: string, wordText: string, reviewer: string): ReviewerChange[] {
|
|
188
|
+
const changes: ReviewerChange[] = [];
|
|
130
189
|
|
|
131
190
|
// Use sentence-level diff for better granularity
|
|
132
191
|
const diffs = diffSentences(originalText, wordText);
|
|
@@ -136,6 +195,7 @@ export function extractChanges(originalText, wordText, reviewer) {
|
|
|
136
195
|
|
|
137
196
|
while (i < diffs.length) {
|
|
138
197
|
const part = diffs[i];
|
|
198
|
+
if (!part) break;
|
|
139
199
|
|
|
140
200
|
if (!part.added && !part.removed) {
|
|
141
201
|
// Unchanged
|
|
@@ -143,13 +203,15 @@ export function extractChanges(originalText, wordText, reviewer) {
|
|
|
143
203
|
i++;
|
|
144
204
|
} else if (part.removed && diffs[i + 1]?.added) {
|
|
145
205
|
// Replacement: removed followed by added
|
|
206
|
+
const nextPart = diffs[i + 1];
|
|
207
|
+
if (!nextPart) break;
|
|
146
208
|
changes.push({
|
|
147
209
|
reviewer,
|
|
148
210
|
type: 'replace',
|
|
149
211
|
start: originalPos,
|
|
150
212
|
end: originalPos + part.value.length,
|
|
151
213
|
oldText: part.value,
|
|
152
|
-
newText:
|
|
214
|
+
newText: nextPart.value,
|
|
153
215
|
});
|
|
154
216
|
originalPos += part.value.length;
|
|
155
217
|
i += 2;
|
|
@@ -184,13 +246,9 @@ export function extractChanges(originalText, wordText, reviewer) {
|
|
|
184
246
|
|
|
185
247
|
/**
|
|
186
248
|
* Extract changes using word-level diff (more fine-grained)
|
|
187
|
-
* @param {string} originalText
|
|
188
|
-
* @param {string} wordText
|
|
189
|
-
* @param {string} reviewer
|
|
190
|
-
* @returns {ReviewerChange[]}
|
|
191
249
|
*/
|
|
192
|
-
export function extractChangesWordLevel(originalText, wordText, reviewer) {
|
|
193
|
-
const changes = [];
|
|
250
|
+
export function extractChangesWordLevel(originalText: string, wordText: string, reviewer: string): ReviewerChange[] {
|
|
251
|
+
const changes: ReviewerChange[] = [];
|
|
194
252
|
const diffs = diffWords(originalText, wordText);
|
|
195
253
|
|
|
196
254
|
let originalPos = 0;
|
|
@@ -198,18 +256,21 @@ export function extractChangesWordLevel(originalText, wordText, reviewer) {
|
|
|
198
256
|
|
|
199
257
|
while (i < diffs.length) {
|
|
200
258
|
const part = diffs[i];
|
|
259
|
+
if (!part) break;
|
|
201
260
|
|
|
202
261
|
if (!part.added && !part.removed) {
|
|
203
262
|
originalPos += part.value.length;
|
|
204
263
|
i++;
|
|
205
264
|
} else if (part.removed && diffs[i + 1]?.added) {
|
|
265
|
+
const nextPart = diffs[i + 1];
|
|
266
|
+
if (!nextPart) break;
|
|
206
267
|
changes.push({
|
|
207
268
|
reviewer,
|
|
208
269
|
type: 'replace',
|
|
209
270
|
start: originalPos,
|
|
210
271
|
end: originalPos + part.value.length,
|
|
211
272
|
oldText: part.value,
|
|
212
|
-
newText:
|
|
273
|
+
newText: nextPart.value,
|
|
213
274
|
});
|
|
214
275
|
originalPos += part.value.length;
|
|
215
276
|
i += 2;
|
|
@@ -242,11 +303,8 @@ export function extractChangesWordLevel(originalText, wordText, reviewer) {
|
|
|
242
303
|
|
|
243
304
|
/**
|
|
244
305
|
* Check if two changes overlap
|
|
245
|
-
* @param {ReviewerChange} a
|
|
246
|
-
* @param {ReviewerChange} b
|
|
247
|
-
* @returns {boolean}
|
|
248
306
|
*/
|
|
249
|
-
function changesOverlap(a, b) {
|
|
307
|
+
function changesOverlap(a: ReviewerChange, b: ReviewerChange): boolean {
|
|
250
308
|
// Insertions at same point conflict
|
|
251
309
|
if (a.type === 'insert' && b.type === 'insert' && a.start === b.start) {
|
|
252
310
|
return a.newText !== b.newText; // Same insertion is not a conflict
|
|
@@ -274,22 +332,22 @@ function changesOverlap(a, b) {
|
|
|
274
332
|
|
|
275
333
|
/**
|
|
276
334
|
* Detect conflicts between changes from multiple reviewers
|
|
277
|
-
* @param
|
|
278
|
-
* @returns {{conflicts: Conflict[], nonConflicting: ReviewerChange[]}}
|
|
335
|
+
* @param allChanges - Array of change arrays, one per reviewer
|
|
279
336
|
*/
|
|
280
|
-
export function detectConflicts(allChanges) {
|
|
337
|
+
export function detectConflicts(allChanges: ReviewerChange[][]): ConflictDetectionResult {
|
|
281
338
|
// Flatten and sort all changes by position
|
|
282
339
|
const flat = allChanges.flat().sort((a, b) => a.start - b.start || a.end - b.end);
|
|
283
340
|
|
|
284
|
-
const conflicts = [];
|
|
285
|
-
const nonConflicting = [];
|
|
286
|
-
const usedIndices = new Set();
|
|
341
|
+
const conflicts: Conflict[] = [];
|
|
342
|
+
const nonConflicting: ReviewerChange[] = [];
|
|
343
|
+
const usedIndices = new Set<number>();
|
|
287
344
|
let conflictId = 0;
|
|
288
345
|
|
|
289
346
|
for (let i = 0; i < flat.length; i++) {
|
|
290
347
|
if (usedIndices.has(i)) continue;
|
|
291
348
|
|
|
292
349
|
const change = flat[i];
|
|
350
|
+
if (!change) continue;
|
|
293
351
|
const conflictingChanges = [change];
|
|
294
352
|
|
|
295
353
|
// Find all changes that conflict with this one
|
|
@@ -297,6 +355,7 @@ export function detectConflicts(allChanges) {
|
|
|
297
355
|
if (usedIndices.has(j)) continue;
|
|
298
356
|
|
|
299
357
|
const other = flat[j];
|
|
358
|
+
if (!other) continue;
|
|
300
359
|
|
|
301
360
|
// Stop if we're past the range
|
|
302
361
|
if (other.start > change.end && change.type !== 'insert') break;
|
|
@@ -309,15 +368,16 @@ export function detectConflicts(allChanges) {
|
|
|
309
368
|
|
|
310
369
|
if (conflictingChanges.length > 1) {
|
|
311
370
|
// Multiple reviewers changed the same region
|
|
312
|
-
const start = Math.min(...conflictingChanges.map(c => c
|
|
313
|
-
const end = Math.max(...conflictingChanges.map(c => c
|
|
371
|
+
const start = Math.min(...conflictingChanges.map(c => c?.start ?? 0).filter(s => s !== undefined));
|
|
372
|
+
const end = Math.max(...conflictingChanges.map(c => c?.end ?? 0).filter(e => e !== undefined));
|
|
373
|
+
const firstChange = conflictingChanges[0];
|
|
314
374
|
|
|
315
375
|
conflicts.push({
|
|
316
376
|
id: `c${++conflictId}`,
|
|
317
377
|
start,
|
|
318
378
|
end,
|
|
319
|
-
original:
|
|
320
|
-
changes: conflictingChanges,
|
|
379
|
+
original: firstChange?.oldText || '',
|
|
380
|
+
changes: conflictingChanges.filter((c): c is ReviewerChange => c !== undefined),
|
|
321
381
|
resolved: null,
|
|
322
382
|
});
|
|
323
383
|
usedIndices.add(i);
|
|
@@ -329,8 +389,8 @@ export function detectConflicts(allChanges) {
|
|
|
329
389
|
}
|
|
330
390
|
|
|
331
391
|
// Deduplicate identical non-conflicting changes
|
|
332
|
-
const seen = new Map();
|
|
333
|
-
const dedupedNonConflicting = [];
|
|
392
|
+
const seen = new Map<string, boolean>();
|
|
393
|
+
const dedupedNonConflicting: ReviewerChange[] = [];
|
|
334
394
|
|
|
335
395
|
for (const change of nonConflicting) {
|
|
336
396
|
const key = `${change.start}:${change.end}:${change.type}:${change.newText}`;
|
|
@@ -345,11 +405,10 @@ export function detectConflicts(allChanges) {
|
|
|
345
405
|
|
|
346
406
|
/**
|
|
347
407
|
* Apply non-conflicting changes to text
|
|
348
|
-
* @param
|
|
349
|
-
* @param
|
|
350
|
-
* @returns {string}
|
|
408
|
+
* @param originalText
|
|
409
|
+
* @param changes - Must be sorted by position
|
|
351
410
|
*/
|
|
352
|
-
export function applyChanges(originalText, changes) {
|
|
411
|
+
export function applyChanges(originalText: string, changes: ReviewerChange[]): string {
|
|
353
412
|
// Sort by position descending to apply from end to start
|
|
354
413
|
const sorted = [...changes].sort((a, b) => b.start - a.start);
|
|
355
414
|
|
|
@@ -370,11 +429,8 @@ export function applyChanges(originalText, changes) {
|
|
|
370
429
|
|
|
371
430
|
/**
|
|
372
431
|
* Apply changes as CriticMarkup annotations
|
|
373
|
-
* @param {string} originalText
|
|
374
|
-
* @param {ReviewerChange[]} changes
|
|
375
|
-
* @returns {string}
|
|
376
432
|
*/
|
|
377
|
-
export function applyChangesAsAnnotations(originalText, changes) {
|
|
433
|
+
export function applyChangesAsAnnotations(originalText: string, changes: ReviewerChange[]): string {
|
|
378
434
|
const sorted = [...changes].sort((a, b) => b.start - a.start);
|
|
379
435
|
|
|
380
436
|
let result = originalText;
|
|
@@ -397,18 +453,15 @@ export function applyChangesAsAnnotations(originalText, changes) {
|
|
|
397
453
|
|
|
398
454
|
/**
|
|
399
455
|
* Apply changes as git-style conflict markers
|
|
400
|
-
* @param {string} originalText
|
|
401
|
-
* @param {Conflict[]} conflicts
|
|
402
|
-
* @returns {string}
|
|
403
456
|
*/
|
|
404
|
-
export function applyConflictMarkers(originalText, conflicts) {
|
|
457
|
+
export function applyConflictMarkers(originalText: string, conflicts: Conflict[]): string {
|
|
405
458
|
// Sort by position descending
|
|
406
459
|
const sorted = [...conflicts].sort((a, b) => b.start - a.start);
|
|
407
460
|
|
|
408
461
|
let result = originalText;
|
|
409
462
|
|
|
410
463
|
for (const conflict of sorted) {
|
|
411
|
-
const markers = [];
|
|
464
|
+
const markers: string[] = [];
|
|
412
465
|
markers.push(`<<<<<<< CONFLICT ${conflict.id}`);
|
|
413
466
|
|
|
414
467
|
for (const change of conflict.changes) {
|
|
@@ -433,12 +486,9 @@ export function applyConflictMarkers(originalText, conflicts) {
|
|
|
433
486
|
|
|
434
487
|
/**
|
|
435
488
|
* Format a conflict for display
|
|
436
|
-
* @param {Conflict} conflict
|
|
437
|
-
* @param {string} originalText
|
|
438
|
-
* @returns {string}
|
|
439
489
|
*/
|
|
440
|
-
export function formatConflict(conflict, originalText) {
|
|
441
|
-
const lines = [];
|
|
490
|
+
export function formatConflict(conflict: Conflict, originalText: string): string {
|
|
491
|
+
const lines: string[] = [];
|
|
442
492
|
const context = 50;
|
|
443
493
|
|
|
444
494
|
// Show context
|
|
@@ -473,13 +523,10 @@ export function formatConflict(conflict, originalText) {
|
|
|
473
523
|
|
|
474
524
|
/**
|
|
475
525
|
* Save conflicts to file for later resolution
|
|
476
|
-
* @param {string} projectDir
|
|
477
|
-
* @param {Conflict[]} conflicts
|
|
478
|
-
* @param {string} baseDoc - Base document path
|
|
479
526
|
*/
|
|
480
|
-
export function saveConflicts(projectDir, conflicts, baseDoc) {
|
|
527
|
+
export function saveConflicts(projectDir: string, conflicts: Conflict[], baseDoc: string): void {
|
|
481
528
|
const conflictsPath = path.join(projectDir, CONFLICTS_FILE);
|
|
482
|
-
const data = {
|
|
529
|
+
const data: ConflictsData = {
|
|
483
530
|
base: baseDoc,
|
|
484
531
|
merged: new Date().toISOString(),
|
|
485
532
|
conflicts,
|
|
@@ -496,22 +543,19 @@ export function saveConflicts(projectDir, conflicts, baseDoc) {
|
|
|
496
543
|
|
|
497
544
|
/**
|
|
498
545
|
* Load conflicts from file
|
|
499
|
-
* @param {string} projectDir
|
|
500
|
-
* @returns {{base: string, merged: string, conflicts: Conflict[]}|null}
|
|
501
546
|
*/
|
|
502
|
-
export function loadConflicts(projectDir) {
|
|
547
|
+
export function loadConflicts(projectDir: string): ConflictsData | null {
|
|
503
548
|
const conflictsPath = path.join(projectDir, CONFLICTS_FILE);
|
|
504
549
|
if (!fs.existsSync(conflictsPath)) {
|
|
505
550
|
return null;
|
|
506
551
|
}
|
|
507
|
-
return JSON.parse(fs.readFileSync(conflictsPath, 'utf-8'));
|
|
552
|
+
return JSON.parse(fs.readFileSync(conflictsPath, 'utf-8')) as ConflictsData;
|
|
508
553
|
}
|
|
509
554
|
|
|
510
555
|
/**
|
|
511
556
|
* Clear conflicts file after resolution
|
|
512
|
-
* @param {string} projectDir
|
|
513
557
|
*/
|
|
514
|
-
export function clearConflicts(projectDir) {
|
|
558
|
+
export function clearConflicts(projectDir: string): void {
|
|
515
559
|
const conflictsPath = path.join(projectDir, CONFLICTS_FILE);
|
|
516
560
|
if (fs.existsSync(conflictsPath)) {
|
|
517
561
|
fs.unlinkSync(conflictsPath);
|
|
@@ -520,12 +564,12 @@ export function clearConflicts(projectDir) {
|
|
|
520
564
|
|
|
521
565
|
/**
|
|
522
566
|
* Merge multiple Word documents using three-way merge
|
|
523
|
-
* @param {string} basePath - Path to base document (original sent to reviewers)
|
|
524
|
-
* @param {Array<{path: string, name: string}>} reviewerDocs - Reviewer Word docs
|
|
525
|
-
* @param {Object} options
|
|
526
|
-
* @returns {Promise<{merged: string, conflicts: Conflict[], stats: Object, baseText: string}>}
|
|
527
567
|
*/
|
|
528
|
-
export async function mergeThreeWay(
|
|
568
|
+
export async function mergeThreeWay(
|
|
569
|
+
basePath: string,
|
|
570
|
+
reviewerDocs: ReviewerDoc[],
|
|
571
|
+
options: MergeOptions = {}
|
|
572
|
+
): Promise<MergeResult & { baseText: string }> {
|
|
529
573
|
const { diffLevel = 'sentence' } = options;
|
|
530
574
|
|
|
531
575
|
if (!fs.existsSync(basePath)) {
|
|
@@ -536,8 +580,8 @@ export async function mergeThreeWay(basePath, reviewerDocs, options = {}) {
|
|
|
536
580
|
const { text: baseText } = await extractFromWord(basePath);
|
|
537
581
|
|
|
538
582
|
// Extract changes from each reviewer relative to base
|
|
539
|
-
const allChanges = [];
|
|
540
|
-
const allComments = [];
|
|
583
|
+
const allChanges: ReviewerChange[][] = [];
|
|
584
|
+
const allComments: ReviewerComment[] = [];
|
|
541
585
|
|
|
542
586
|
for (const doc of reviewerDocs) {
|
|
543
587
|
if (!fs.existsSync(doc.path)) {
|
|
@@ -557,8 +601,11 @@ export async function mergeThreeWay(basePath, reviewerDocs, options = {}) {
|
|
|
557
601
|
try {
|
|
558
602
|
const comments = await extractWordComments(doc.path);
|
|
559
603
|
allComments.push(...comments.map(c => ({ ...c, reviewer: doc.name })));
|
|
560
|
-
} catch {
|
|
561
|
-
|
|
604
|
+
} catch (e) {
|
|
605
|
+
if (process.env.DEBUG) {
|
|
606
|
+
const error = e as Error;
|
|
607
|
+
console.warn(`merge: Failed to extract comments:`, error.message);
|
|
608
|
+
}
|
|
562
609
|
}
|
|
563
610
|
}
|
|
564
611
|
|
|
@@ -581,18 +628,18 @@ export async function mergeThreeWay(basePath, reviewerDocs, options = {}) {
|
|
|
581
628
|
comments: allComments.length,
|
|
582
629
|
};
|
|
583
630
|
|
|
584
|
-
return { merged, conflicts, stats, baseText };
|
|
631
|
+
return { merged, conflicts, stats, baseText, originalText: baseText };
|
|
585
632
|
}
|
|
586
633
|
|
|
587
634
|
/**
|
|
588
635
|
* Merge multiple Word documents against an original markdown file
|
|
589
636
|
* Legacy function - use mergeThreeWay for proper three-way merge
|
|
590
|
-
* @param {string} originalPath - Path to original markdown
|
|
591
|
-
* @param {Array<{path: string, name: string}>} reviewerDocs - Reviewer Word docs
|
|
592
|
-
* @param {Object} options
|
|
593
|
-
* @returns {Promise<{merged: string, conflicts: Conflict[], stats: Object}>}
|
|
594
637
|
*/
|
|
595
|
-
export async function mergeReviewerDocs(
|
|
638
|
+
export async function mergeReviewerDocs(
|
|
639
|
+
originalPath: string,
|
|
640
|
+
reviewerDocs: ReviewerDoc[],
|
|
641
|
+
options: MergeOptions = {}
|
|
642
|
+
): Promise<MergeResult> {
|
|
596
643
|
const { autoResolve = false } = options;
|
|
597
644
|
|
|
598
645
|
if (!fs.existsSync(originalPath)) {
|
|
@@ -602,8 +649,8 @@ export async function mergeReviewerDocs(originalPath, reviewerDocs, options = {}
|
|
|
602
649
|
const originalText = fs.readFileSync(originalPath, 'utf-8');
|
|
603
650
|
|
|
604
651
|
// Extract changes from each reviewer
|
|
605
|
-
const allChanges = [];
|
|
606
|
-
const allComments = [];
|
|
652
|
+
const allChanges: ReviewerChange[][] = [];
|
|
653
|
+
const allComments: ReviewerComment[] = [];
|
|
607
654
|
|
|
608
655
|
for (const doc of reviewerDocs) {
|
|
609
656
|
if (!fs.existsSync(doc.path)) {
|
|
@@ -618,8 +665,11 @@ export async function mergeReviewerDocs(originalPath, reviewerDocs, options = {}
|
|
|
618
665
|
try {
|
|
619
666
|
const comments = await extractWordComments(doc.path);
|
|
620
667
|
allComments.push(...comments.map(c => ({ ...c, reviewer: doc.name })));
|
|
621
|
-
} catch {
|
|
622
|
-
|
|
668
|
+
} catch (e) {
|
|
669
|
+
if (process.env.DEBUG) {
|
|
670
|
+
const error = e as Error;
|
|
671
|
+
console.warn(`merge: Failed to extract comments:`, error.message);
|
|
672
|
+
}
|
|
623
673
|
}
|
|
624
674
|
}
|
|
625
675
|
|
|
@@ -648,26 +698,26 @@ export async function mergeReviewerDocs(originalPath, reviewerDocs, options = {}
|
|
|
648
698
|
|
|
649
699
|
/**
|
|
650
700
|
* Resolve a conflict by choosing one option
|
|
651
|
-
* @param
|
|
652
|
-
* @param
|
|
653
|
-
* @returns {ReviewerChange}
|
|
701
|
+
* @param conflict
|
|
702
|
+
* @param choice - Index of chosen change (0-based)
|
|
654
703
|
*/
|
|
655
|
-
export function resolveConflict(conflict, choice) {
|
|
704
|
+
export function resolveConflict(conflict: Conflict, choice: number): ReviewerChange {
|
|
656
705
|
if (choice < 0 || choice >= conflict.changes.length) {
|
|
657
706
|
throw new Error(`Invalid choice: ${choice}. Must be 0-${conflict.changes.length - 1}`);
|
|
658
707
|
}
|
|
659
|
-
|
|
660
|
-
|
|
708
|
+
const selectedChange = conflict.changes[choice];
|
|
709
|
+
if (!selectedChange) {
|
|
710
|
+
throw new Error(`Invalid choice: ${choice}. Change not found`);
|
|
711
|
+
}
|
|
712
|
+
conflict.resolved = selectedChange.reviewer;
|
|
713
|
+
return selectedChange;
|
|
661
714
|
}
|
|
662
715
|
|
|
663
716
|
/**
|
|
664
717
|
* Get list of unresolved conflicts
|
|
665
|
-
* @param {string} projectDir
|
|
666
|
-
* @returns {Conflict[]}
|
|
667
718
|
*/
|
|
668
|
-
export function getUnresolvedConflicts(projectDir) {
|
|
719
|
+
export function getUnresolvedConflicts(projectDir: string): Conflict[] {
|
|
669
720
|
const data = loadConflicts(projectDir);
|
|
670
721
|
if (!data) return [];
|
|
671
722
|
return data.conflicts.filter(c => c.resolved === null);
|
|
672
723
|
}
|
|
673
|
-
|