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.
Files changed (306) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/PLAN-tables-and-postprocess.md +850 -0
  3. package/README.md +33 -0
  4. package/bin/rev.js +12 -131
  5. package/bin/rev.ts +145 -0
  6. package/dist/bin/rev.d.ts +9 -0
  7. package/dist/bin/rev.d.ts.map +1 -0
  8. package/dist/bin/rev.js +118 -0
  9. package/dist/bin/rev.js.map +1 -0
  10. package/dist/lib/annotations.d.ts +91 -0
  11. package/dist/lib/annotations.d.ts.map +1 -0
  12. package/dist/lib/annotations.js +554 -0
  13. package/dist/lib/annotations.js.map +1 -0
  14. package/dist/lib/build.d.ts +171 -0
  15. package/dist/lib/build.d.ts.map +1 -0
  16. package/dist/lib/build.js +755 -0
  17. package/dist/lib/build.js.map +1 -0
  18. package/dist/lib/citations.d.ts +34 -0
  19. package/dist/lib/citations.d.ts.map +1 -0
  20. package/dist/lib/citations.js +140 -0
  21. package/dist/lib/citations.js.map +1 -0
  22. package/dist/lib/commands/build.d.ts +13 -0
  23. package/dist/lib/commands/build.d.ts.map +1 -0
  24. package/dist/lib/commands/build.js +678 -0
  25. package/dist/lib/commands/build.js.map +1 -0
  26. package/dist/lib/commands/citations.d.ts +11 -0
  27. package/dist/lib/commands/citations.d.ts.map +1 -0
  28. package/dist/lib/commands/citations.js +428 -0
  29. package/dist/lib/commands/citations.js.map +1 -0
  30. package/dist/lib/commands/comments.d.ts +11 -0
  31. package/dist/lib/commands/comments.d.ts.map +1 -0
  32. package/dist/lib/commands/comments.js +883 -0
  33. package/dist/lib/commands/comments.js.map +1 -0
  34. package/dist/lib/commands/context.d.ts +35 -0
  35. package/dist/lib/commands/context.d.ts.map +1 -0
  36. package/dist/lib/commands/context.js +59 -0
  37. package/dist/lib/commands/context.js.map +1 -0
  38. package/dist/lib/commands/core.d.ts +11 -0
  39. package/dist/lib/commands/core.d.ts.map +1 -0
  40. package/dist/lib/commands/core.js +246 -0
  41. package/dist/lib/commands/core.js.map +1 -0
  42. package/dist/lib/commands/doi.d.ts +11 -0
  43. package/dist/lib/commands/doi.d.ts.map +1 -0
  44. package/dist/lib/commands/doi.js +373 -0
  45. package/dist/lib/commands/doi.js.map +1 -0
  46. package/dist/lib/commands/history.d.ts +11 -0
  47. package/dist/lib/commands/history.d.ts.map +1 -0
  48. package/dist/lib/commands/history.js +245 -0
  49. package/dist/lib/commands/history.js.map +1 -0
  50. package/dist/lib/commands/index.d.ts +28 -0
  51. package/dist/lib/commands/index.d.ts.map +1 -0
  52. package/dist/lib/commands/index.js +35 -0
  53. package/dist/lib/commands/index.js.map +1 -0
  54. package/dist/lib/commands/init.d.ts +11 -0
  55. package/dist/lib/commands/init.d.ts.map +1 -0
  56. package/dist/lib/commands/init.js +209 -0
  57. package/dist/lib/commands/init.js.map +1 -0
  58. package/dist/lib/commands/response.d.ts +11 -0
  59. package/dist/lib/commands/response.d.ts.map +1 -0
  60. package/dist/lib/commands/response.js +317 -0
  61. package/dist/lib/commands/response.js.map +1 -0
  62. package/dist/lib/commands/sections.d.ts +11 -0
  63. package/dist/lib/commands/sections.d.ts.map +1 -0
  64. package/dist/lib/commands/sections.js +1071 -0
  65. package/dist/lib/commands/sections.js.map +1 -0
  66. package/dist/lib/commands/utilities.d.ts +19 -0
  67. package/dist/lib/commands/utilities.d.ts.map +1 -0
  68. package/dist/lib/commands/utilities.js +2009 -0
  69. package/dist/lib/commands/utilities.js.map +1 -0
  70. package/dist/lib/comment-realign.d.ts +50 -0
  71. package/dist/lib/comment-realign.d.ts.map +1 -0
  72. package/dist/lib/comment-realign.js +372 -0
  73. package/dist/lib/comment-realign.js.map +1 -0
  74. package/dist/lib/config.d.ts +41 -0
  75. package/dist/lib/config.d.ts.map +1 -0
  76. package/dist/lib/config.js +76 -0
  77. package/dist/lib/config.js.map +1 -0
  78. package/dist/lib/crossref.d.ts +108 -0
  79. package/dist/lib/crossref.d.ts.map +1 -0
  80. package/dist/lib/crossref.js +597 -0
  81. package/dist/lib/crossref.js.map +1 -0
  82. package/dist/lib/dependencies.d.ts +30 -0
  83. package/dist/lib/dependencies.d.ts.map +1 -0
  84. package/dist/lib/dependencies.js +95 -0
  85. package/dist/lib/dependencies.js.map +1 -0
  86. package/dist/lib/doi-cache.d.ts +29 -0
  87. package/dist/lib/doi-cache.d.ts.map +1 -0
  88. package/dist/lib/doi-cache.js +104 -0
  89. package/dist/lib/doi-cache.js.map +1 -0
  90. package/dist/lib/doi.d.ts +65 -0
  91. package/dist/lib/doi.d.ts.map +1 -0
  92. package/dist/lib/doi.js +710 -0
  93. package/dist/lib/doi.js.map +1 -0
  94. package/dist/lib/equations.d.ts +61 -0
  95. package/dist/lib/equations.d.ts.map +1 -0
  96. package/dist/lib/equations.js +445 -0
  97. package/dist/lib/equations.js.map +1 -0
  98. package/dist/lib/errors.d.ts +60 -0
  99. package/dist/lib/errors.d.ts.map +1 -0
  100. package/dist/lib/errors.js +303 -0
  101. package/dist/lib/errors.js.map +1 -0
  102. package/dist/lib/format.d.ts +104 -0
  103. package/dist/lib/format.d.ts.map +1 -0
  104. package/dist/lib/format.js +416 -0
  105. package/dist/lib/format.js.map +1 -0
  106. package/dist/lib/git.d.ts +88 -0
  107. package/dist/lib/git.d.ts.map +1 -0
  108. package/dist/lib/git.js +304 -0
  109. package/dist/lib/git.js.map +1 -0
  110. package/dist/lib/grammar.d.ts +62 -0
  111. package/dist/lib/grammar.d.ts.map +1 -0
  112. package/dist/lib/grammar.js +244 -0
  113. package/dist/lib/grammar.js.map +1 -0
  114. package/dist/lib/image-registry.d.ts +68 -0
  115. package/dist/lib/image-registry.d.ts.map +1 -0
  116. package/dist/lib/image-registry.js +112 -0
  117. package/dist/lib/image-registry.js.map +1 -0
  118. package/dist/lib/import.d.ts +184 -0
  119. package/dist/lib/import.d.ts.map +1 -0
  120. package/dist/lib/import.js +1581 -0
  121. package/dist/lib/import.js.map +1 -0
  122. package/dist/lib/journals.d.ts +55 -0
  123. package/dist/lib/journals.d.ts.map +1 -0
  124. package/dist/lib/journals.js +417 -0
  125. package/dist/lib/journals.js.map +1 -0
  126. package/dist/lib/merge.d.ts +138 -0
  127. package/dist/lib/merge.d.ts.map +1 -0
  128. package/dist/lib/merge.js +603 -0
  129. package/dist/lib/merge.js.map +1 -0
  130. package/dist/lib/orcid.d.ts +36 -0
  131. package/dist/lib/orcid.d.ts.map +1 -0
  132. package/dist/lib/orcid.js +117 -0
  133. package/dist/lib/orcid.js.map +1 -0
  134. package/dist/lib/pdf-comments.d.ts +95 -0
  135. package/dist/lib/pdf-comments.d.ts.map +1 -0
  136. package/dist/lib/pdf-comments.js +192 -0
  137. package/dist/lib/pdf-comments.js.map +1 -0
  138. package/dist/lib/pdf-import.d.ts +118 -0
  139. package/dist/lib/pdf-import.d.ts.map +1 -0
  140. package/dist/lib/pdf-import.js +397 -0
  141. package/dist/lib/pdf-import.js.map +1 -0
  142. package/dist/lib/plugins.d.ts +76 -0
  143. package/dist/lib/plugins.d.ts.map +1 -0
  144. package/dist/lib/plugins.js +235 -0
  145. package/dist/lib/plugins.js.map +1 -0
  146. package/dist/lib/postprocess.d.ts +42 -0
  147. package/dist/lib/postprocess.d.ts.map +1 -0
  148. package/dist/lib/postprocess.js +138 -0
  149. package/dist/lib/postprocess.js.map +1 -0
  150. package/dist/lib/pptx-template.d.ts +59 -0
  151. package/dist/lib/pptx-template.d.ts.map +1 -0
  152. package/dist/lib/pptx-template.js +613 -0
  153. package/dist/lib/pptx-template.js.map +1 -0
  154. package/dist/lib/pptx-themes.d.ts +80 -0
  155. package/dist/lib/pptx-themes.d.ts.map +1 -0
  156. package/dist/lib/pptx-themes.js +818 -0
  157. package/dist/lib/pptx-themes.js.map +1 -0
  158. package/dist/lib/protect-restore.d.ts +137 -0
  159. package/dist/lib/protect-restore.d.ts.map +1 -0
  160. package/dist/lib/protect-restore.js +394 -0
  161. package/dist/lib/protect-restore.js.map +1 -0
  162. package/dist/lib/rate-limiter.d.ts +27 -0
  163. package/dist/lib/rate-limiter.d.ts.map +1 -0
  164. package/dist/lib/rate-limiter.js +79 -0
  165. package/dist/lib/rate-limiter.js.map +1 -0
  166. package/dist/lib/response.d.ts +41 -0
  167. package/dist/lib/response.d.ts.map +1 -0
  168. package/dist/lib/response.js +150 -0
  169. package/dist/lib/response.js.map +1 -0
  170. package/dist/lib/review.d.ts +35 -0
  171. package/dist/lib/review.d.ts.map +1 -0
  172. package/dist/lib/review.js +263 -0
  173. package/dist/lib/review.js.map +1 -0
  174. package/dist/lib/schema.d.ts +66 -0
  175. package/dist/lib/schema.d.ts.map +1 -0
  176. package/dist/lib/schema.js +339 -0
  177. package/dist/lib/schema.js.map +1 -0
  178. package/dist/lib/scientific-words.d.ts +6 -0
  179. package/dist/lib/scientific-words.d.ts.map +1 -0
  180. package/dist/lib/scientific-words.js +66 -0
  181. package/dist/lib/scientific-words.js.map +1 -0
  182. package/dist/lib/sections.d.ts +40 -0
  183. package/dist/lib/sections.d.ts.map +1 -0
  184. package/dist/lib/sections.js +288 -0
  185. package/dist/lib/sections.js.map +1 -0
  186. package/dist/lib/slides.d.ts +86 -0
  187. package/dist/lib/slides.d.ts.map +1 -0
  188. package/dist/lib/slides.js +676 -0
  189. package/dist/lib/slides.js.map +1 -0
  190. package/dist/lib/spelling.d.ts +76 -0
  191. package/dist/lib/spelling.d.ts.map +1 -0
  192. package/dist/lib/spelling.js +272 -0
  193. package/dist/lib/spelling.js.map +1 -0
  194. package/dist/lib/templates.d.ts +30 -0
  195. package/dist/lib/templates.d.ts.map +1 -0
  196. package/dist/lib/templates.js +504 -0
  197. package/dist/lib/templates.js.map +1 -0
  198. package/dist/lib/themes.d.ts +85 -0
  199. package/dist/lib/themes.d.ts.map +1 -0
  200. package/dist/lib/themes.js +652 -0
  201. package/dist/lib/themes.js.map +1 -0
  202. package/dist/lib/trackchanges.d.ts +51 -0
  203. package/dist/lib/trackchanges.d.ts.map +1 -0
  204. package/dist/lib/trackchanges.js +202 -0
  205. package/dist/lib/trackchanges.js.map +1 -0
  206. package/dist/lib/tui.d.ts +76 -0
  207. package/dist/lib/tui.d.ts.map +1 -0
  208. package/dist/lib/tui.js +377 -0
  209. package/dist/lib/tui.js.map +1 -0
  210. package/dist/lib/types.d.ts +447 -0
  211. package/dist/lib/types.d.ts.map +1 -0
  212. package/dist/lib/types.js +6 -0
  213. package/dist/lib/types.js.map +1 -0
  214. package/dist/lib/undo.d.ts +57 -0
  215. package/dist/lib/undo.d.ts.map +1 -0
  216. package/dist/lib/undo.js +185 -0
  217. package/dist/lib/undo.js.map +1 -0
  218. package/dist/lib/utils.d.ts +16 -0
  219. package/dist/lib/utils.d.ts.map +1 -0
  220. package/dist/lib/utils.js +40 -0
  221. package/dist/lib/utils.js.map +1 -0
  222. package/dist/lib/variables.d.ts +42 -0
  223. package/dist/lib/variables.d.ts.map +1 -0
  224. package/dist/lib/variables.js +141 -0
  225. package/dist/lib/variables.js.map +1 -0
  226. package/dist/lib/word.d.ts +80 -0
  227. package/dist/lib/word.d.ts.map +1 -0
  228. package/dist/lib/word.js +360 -0
  229. package/dist/lib/word.js.map +1 -0
  230. package/dist/lib/wordcomments.d.ts +51 -0
  231. package/dist/lib/wordcomments.d.ts.map +1 -0
  232. package/dist/lib/wordcomments.js +587 -0
  233. package/dist/lib/wordcomments.js.map +1 -0
  234. package/eslint.config.js +27 -0
  235. package/lib/annotations.ts +622 -0
  236. package/lib/apply-buildup-colors.py +88 -0
  237. package/lib/build.ts +1013 -0
  238. package/lib/{citations.js → citations.ts} +38 -27
  239. package/lib/commands/{build.js → build.ts} +80 -27
  240. package/lib/commands/{citations.js → citations.ts} +36 -18
  241. package/lib/commands/{comments.js → comments.ts} +187 -54
  242. package/lib/commands/{context.js → context.ts} +18 -8
  243. package/lib/commands/{core.js → core.ts} +34 -20
  244. package/lib/commands/{doi.js → doi.ts} +32 -16
  245. package/lib/commands/{history.js → history.ts} +25 -12
  246. package/lib/commands/{index.js → index.ts} +9 -5
  247. package/lib/commands/{init.js → init.ts} +20 -8
  248. package/lib/commands/{response.js → response.ts} +47 -20
  249. package/lib/commands/{sections.js → sections.ts} +273 -68
  250. package/lib/commands/{utilities.js → utilities.ts} +338 -158
  251. package/lib/{comment-realign.js → comment-realign.ts} +117 -45
  252. package/lib/config.ts +84 -0
  253. package/lib/{crossref.js → crossref.ts} +213 -138
  254. package/lib/dependencies.ts +106 -0
  255. package/lib/doi-cache.ts +115 -0
  256. package/lib/{doi.js → doi.ts} +115 -281
  257. package/lib/{equations.js → equations.ts} +60 -64
  258. package/lib/{errors.js → errors.ts} +56 -48
  259. package/lib/{format.js → format.ts} +137 -63
  260. package/lib/{git.js → git.ts} +66 -63
  261. package/lib/{grammar.js → grammar.ts} +45 -32
  262. package/lib/image-registry.ts +180 -0
  263. package/lib/import.ts +2060 -0
  264. package/lib/journals.ts +505 -0
  265. package/lib/{merge.js → merge.ts} +185 -135
  266. package/lib/{orcid.js → orcid.ts} +17 -22
  267. package/lib/{pdf-comments.js → pdf-comments.ts} +76 -18
  268. package/lib/{pdf-import.js → pdf-import.ts} +148 -70
  269. package/lib/{plugins.js → plugins.ts} +82 -39
  270. package/lib/postprocess.ts +188 -0
  271. package/lib/pptx-color-filter.lua +37 -0
  272. package/lib/pptx-template.ts +625 -0
  273. package/lib/pptx-themes/academic.pptx +0 -0
  274. package/lib/pptx-themes/corporate.pptx +0 -0
  275. package/lib/pptx-themes/dark.pptx +0 -0
  276. package/lib/pptx-themes/default.pptx +0 -0
  277. package/lib/pptx-themes/minimal.pptx +0 -0
  278. package/lib/pptx-themes/plant.pptx +0 -0
  279. package/lib/pptx-themes.ts +896 -0
  280. package/lib/protect-restore.ts +516 -0
  281. package/lib/rate-limiter.ts +94 -0
  282. package/lib/{response.js → response.ts} +36 -21
  283. package/lib/{review.js → review.ts} +53 -43
  284. package/lib/{schema.js → schema.ts} +70 -25
  285. package/lib/{sections.js → sections.ts} +71 -76
  286. package/lib/slides.ts +793 -0
  287. package/lib/{spelling.js → spelling.ts} +43 -59
  288. package/lib/{templates.js → templates.ts} +20 -17
  289. package/lib/themes.ts +742 -0
  290. package/lib/{trackchanges.js → trackchanges.ts} +52 -23
  291. package/lib/types.ts +509 -0
  292. package/lib/{undo.js → undo.ts} +75 -52
  293. package/lib/utils.ts +41 -0
  294. package/lib/{variables.js → variables.ts} +60 -54
  295. package/lib/word.ts +428 -0
  296. package/lib/{wordcomments.js → wordcomments.ts} +94 -40
  297. package/package.json +15 -5
  298. package/skill/REFERENCE.md +67 -0
  299. package/tsconfig.json +26 -0
  300. package/lib/annotations.js +0 -414
  301. package/lib/build.js +0 -639
  302. package/lib/config.js +0 -79
  303. package/lib/import.js +0 -1145
  304. package/lib/journals.js +0 -629
  305. package/lib/word.js +0 -225
  306. /package/lib/{scientific-words.js → scientific-words.ts} +0 -0
@@ -6,14 +6,33 @@
6
6
  import * as fs from 'fs';
7
7
  import * as path from 'path';
8
8
 
9
+ interface Reply {
10
+ author: string;
11
+ text: string;
12
+ }
13
+
14
+ interface CommentWithReplies {
15
+ author: string;
16
+ text: string;
17
+ replies: Reply[];
18
+ context: string;
19
+ file: string;
20
+ line: number;
21
+ }
22
+
23
+ interface ResponseOptions {
24
+ title?: string;
25
+ authorName?: string;
26
+ includeContext?: boolean;
27
+ includeLocation?: boolean;
28
+ }
29
+
9
30
  /**
10
31
  * Parse a comment with potential replies
11
32
  * Format: {>>Author: comment<<} {>>Reply Author: reply<<}
12
- * @param {string} text
13
- * @returns {Array<{author: string, text: string, replies: Array, context: string, file: string, line: number}>}
14
33
  */
15
- export function parseCommentsWithReplies(text, file = '') {
16
- const comments = [];
34
+ export function parseCommentsWithReplies(text: string, file: string = ''): CommentWithReplies[] {
35
+ const comments: CommentWithReplies[] = [];
17
36
  const lines = text.split('\n');
18
37
 
19
38
  // Pattern for comments: {>>Author: text<<}
@@ -21,6 +40,7 @@ export function parseCommentsWithReplies(text, file = '') {
21
40
 
22
41
  for (let lineNum = 0; lineNum < lines.length; lineNum++) {
23
42
  const line = lines[lineNum];
43
+ if (!line) continue;
24
44
  const matches = [...line.matchAll(commentPattern)];
25
45
 
26
46
  if (matches.length === 0) continue;
@@ -31,13 +51,14 @@ export function parseCommentsWithReplies(text, file = '') {
31
51
 
32
52
  // First match is the original comment, rest are replies
33
53
  const [first, ...rest] = matches;
54
+ if (!first || !first[1] || !first[2]) continue;
34
55
 
35
56
  comments.push({
36
57
  author: first[1].trim(),
37
58
  text: first[2].trim(),
38
59
  replies: rest.map(m => ({
39
- author: m[1].trim(),
40
- text: m[2].trim(),
60
+ author: m[1]?.trim() || '',
61
+ text: m[2]?.trim() || '',
41
62
  })),
42
63
  context,
43
64
  file,
@@ -50,18 +71,16 @@ export function parseCommentsWithReplies(text, file = '') {
50
71
 
51
72
  /**
52
73
  * Group comments by reviewer
53
- * @param {Array} comments
54
- * @returns {Map<string, Array>}
55
74
  */
56
- export function groupByReviewer(comments) {
57
- const grouped = new Map();
75
+ export function groupByReviewer(comments: CommentWithReplies[]): Map<string, CommentWithReplies[]> {
76
+ const grouped = new Map<string, CommentWithReplies[]>();
58
77
 
59
78
  for (const comment of comments) {
60
79
  const reviewer = comment.author;
61
80
  if (!grouped.has(reviewer)) {
62
81
  grouped.set(reviewer, []);
63
82
  }
64
- grouped.get(reviewer).push(comment);
83
+ grouped.get(reviewer)!.push(comment);
65
84
  }
66
85
 
67
86
  return grouped;
@@ -69,11 +88,8 @@ export function groupByReviewer(comments) {
69
88
 
70
89
  /**
71
90
  * Generate response letter in Markdown format
72
- * @param {Array} comments - All comments from all files
73
- * @param {object} options
74
- * @returns {string}
75
91
  */
76
- export function generateResponseLetter(comments, options = {}) {
92
+ export function generateResponseLetter(comments: CommentWithReplies[], options: ResponseOptions = {}): string {
77
93
  const {
78
94
  title = 'Response to Reviewers',
79
95
  authorName = 'Author',
@@ -81,7 +97,7 @@ export function generateResponseLetter(comments, options = {}) {
81
97
  includeLocation = true,
82
98
  } = options;
83
99
 
84
- const lines = [];
100
+ const lines: string[] = [];
85
101
  lines.push(`# ${title}`);
86
102
  lines.push('');
87
103
  lines.push(`We thank the reviewers for their constructive feedback. Below we address each comment.`);
@@ -105,12 +121,13 @@ export function generateResponseLetter(comments, options = {}) {
105
121
  if (reviewer.toLowerCase() === authorName.toLowerCase()) continue;
106
122
  if (reviewer.toLowerCase() === 'claude') continue;
107
123
 
108
- const reviewerComments = grouped.get(reviewer);
124
+ const reviewerComments = grouped.get(reviewer)!;
109
125
  lines.push(`## ${reviewer}`);
110
126
  lines.push('');
111
127
 
112
128
  for (let i = 0; i < reviewerComments.length; i++) {
113
129
  const c = reviewerComments[i];
130
+ if (!c) continue;
114
131
 
115
132
  lines.push(`### Comment ${i + 1}`);
116
133
  if (includeLocation) {
@@ -164,11 +181,9 @@ export function generateResponseLetter(comments, options = {}) {
164
181
 
165
182
  /**
166
183
  * Collect comments from multiple files
167
- * @param {string[]} files - Array of file paths
168
- * @returns {Array}
169
184
  */
170
- export function collectComments(files) {
171
- const allComments = [];
185
+ export function collectComments(files: string[]): CommentWithReplies[] {
186
+ const allComments: CommentWithReplies[] = [];
172
187
 
173
188
  for (const file of files) {
174
189
  if (!fs.existsSync(file)) continue;
@@ -4,45 +4,65 @@
4
4
 
5
5
  import * as readline from 'readline';
6
6
  import chalk from 'chalk';
7
+ import type { Annotation, Comment } from './types.js';
7
8
  import { getTrackChanges, getComments, applyDecision } from './annotations.js';
8
9
 
10
+ interface ReviewResult {
11
+ text: string;
12
+ accepted: number;
13
+ rejected: number;
14
+ skipped: number;
15
+ }
16
+
17
+ interface CommentReviewOptions {
18
+ author?: string;
19
+ addReply?: (text: string, comment: Comment, author: string, replyText: string) => string;
20
+ setCommentStatus?: (text: string, comment: Comment, resolved: boolean) => string;
21
+ }
22
+
23
+ interface CommentReviewResult {
24
+ text: string;
25
+ resolved: number;
26
+ replied: number;
27
+ skipped: number;
28
+ }
29
+
9
30
  /**
10
31
  * Format an annotation for display
11
- * @param {object} annotation
12
- * @param {number} index
13
- * @param {number} total
14
- * @returns {string}
15
32
  */
16
- function formatAnnotation(annotation, index, total) {
33
+ function formatAnnotation(annotation: Annotation, index: number, total: number): string {
17
34
  const header = chalk.dim(`─── Change ${index + 1}/${total} (line ${annotation.line}) ───`);
18
35
 
19
- let action;
20
- let display;
36
+ let action: string;
37
+ let display: string;
21
38
 
22
39
  switch (annotation.type) {
23
40
  case 'insert':
24
41
  action = chalk.green(`+ Insert: "${annotation.content}"`);
25
42
  display =
26
- chalk.dim(annotation.before) +
43
+ chalk.dim(annotation.before || '') +
27
44
  chalk.green.bold(`[${annotation.content}]`) +
28
- chalk.dim(annotation.after);
45
+ chalk.dim(annotation.after || '');
29
46
  break;
30
47
  case 'delete':
31
48
  action = chalk.red(`- Delete: "${annotation.content}"`);
32
49
  display =
33
- chalk.dim(annotation.before) +
50
+ chalk.dim(annotation.before || '') +
34
51
  chalk.red.strikethrough(`[${annotation.content}]`) +
35
- chalk.dim(annotation.after);
52
+ chalk.dim(annotation.after || '');
36
53
  break;
37
54
  case 'substitute':
38
55
  action = chalk.yellow(`~ Change: "${annotation.content}" → "${annotation.replacement}"`);
39
56
  display =
40
- chalk.dim(annotation.before) +
57
+ chalk.dim(annotation.before || '') +
41
58
  chalk.red.strikethrough(`[${annotation.content}]`) +
42
59
  chalk.dim(' → ') +
43
60
  chalk.green.bold(`[${annotation.replacement}]`) +
44
- chalk.dim(annotation.after);
61
+ chalk.dim(annotation.after || '');
45
62
  break;
63
+ default:
64
+ action = chalk.gray(`? Unknown: "${annotation.content}"`);
65
+ display = chalk.dim(annotation.before || '') + chalk.gray(`[${annotation.content}]`) + chalk.dim(annotation.after || '');
46
66
  }
47
67
 
48
68
  return `\n${header}\n\n ${action}\n\n ${display}\n`;
@@ -50,11 +70,8 @@ function formatAnnotation(annotation, index, total) {
50
70
 
51
71
  /**
52
72
  * Prompt for a single keypress
53
- * @param {string} prompt
54
- * @param {string[]} validKeys
55
- * @returns {Promise<string>}
56
73
  */
57
- function promptKey(prompt, validKeys) {
74
+ function promptKey(prompt: string, validKeys: string[]): Promise<string> {
58
75
  return new Promise((resolve) => {
59
76
  const rl = readline.createInterface({
60
77
  input: process.stdin,
@@ -69,7 +86,7 @@ function promptKey(prompt, validKeys) {
69
86
 
70
87
  process.stdout.write(prompt);
71
88
 
72
- process.stdin.once('data', (key) => {
89
+ process.stdin.once('data', (key: Buffer) => {
73
90
  const char = key.toString().toLowerCase();
74
91
 
75
92
  if (process.stdin.isTTY) {
@@ -95,10 +112,8 @@ function promptKey(prompt, validKeys) {
95
112
 
96
113
  /**
97
114
  * Run interactive review session
98
- * @param {string} text
99
- * @returns {Promise<{text: string, accepted: number, rejected: number, skipped: number}>}
100
115
  */
101
- export async function interactiveReview(text) {
116
+ export async function interactiveReview(text: string): Promise<ReviewResult> {
102
117
  const changes = getTrackChanges(text);
103
118
  const comments = getComments(text);
104
119
 
@@ -119,6 +134,7 @@ export async function interactiveReview(text) {
119
134
 
120
135
  for (let i = 0; i < changes.length; i++) {
121
136
  const change = changes[i];
137
+ if (!change) continue;
122
138
  console.log(formatAnnotation(change, i, changes.length));
123
139
 
124
140
  const prompt = chalk.dim('[a]ccept [r]eject [s]kip | accept [A]ll reject a[L]l [q]uit: ');
@@ -132,7 +148,8 @@ export async function interactiveReview(text) {
132
148
  case 'A':
133
149
  // Accept all remaining
134
150
  for (let j = i; j < changes.length; j++) {
135
- currentText = applyDecision(currentText, changes[j], true);
151
+ const ch = changes[j];
152
+ if (ch) currentText = applyDecision(currentText, ch, true);
136
153
  }
137
154
  accepted += changes.length - i;
138
155
  console.log(chalk.green(`\nAccepted all ${changes.length - i} remaining changes.`));
@@ -142,7 +159,8 @@ export async function interactiveReview(text) {
142
159
  case 'L':
143
160
  // Reject all remaining
144
161
  for (let j = i; j < changes.length; j++) {
145
- currentText = applyDecision(currentText, changes[j], false);
162
+ const ch = changes[j];
163
+ if (ch) currentText = applyDecision(currentText, ch, false);
146
164
  }
147
165
  rejected += changes.length - i;
148
166
  console.log(chalk.red(`\nRejected all ${changes.length - i} remaining changes.`));
@@ -179,9 +197,8 @@ export async function interactiveReview(text) {
179
197
 
180
198
  /**
181
199
  * List all comments
182
- * @param {string} text
183
200
  */
184
- export function listComments(text) {
201
+ export function listComments(text: string): void {
185
202
  const comments = getComments(text);
186
203
 
187
204
  if (comments.length === 0) {
@@ -193,15 +210,16 @@ export function listComments(text) {
193
210
 
194
211
  for (let i = 0; i < comments.length; i++) {
195
212
  const c = comments[i];
213
+ if (!c) continue;
196
214
  const author = c.author || 'Anonymous';
197
215
  const header = chalk.blue(`[${i + 1}] ${author}`) + chalk.dim(` (line ${c.line})`);
198
216
 
199
217
  console.log(header);
200
218
  console.log(` ${c.content}`);
201
219
  console.log(
202
- chalk.dim(` Context: ...${c.before.slice(-25)}`) +
220
+ chalk.dim(` Context: ...${(c.before || '').slice(-25)}`) +
203
221
  chalk.yellow('*') +
204
- chalk.dim(`${c.after.slice(0, 25)}...`)
222
+ chalk.dim(`${(c.after || '').slice(0, 25)}...`)
205
223
  );
206
224
  console.log();
207
225
  }
@@ -209,12 +227,8 @@ export function listComments(text) {
209
227
 
210
228
  /**
211
229
  * Format a comment for interactive display
212
- * @param {object} comment
213
- * @param {number} index
214
- * @param {number} total
215
- * @returns {string}
216
230
  */
217
- function formatComment(comment, index, total) {
231
+ function formatComment(comment: Comment, index: number, total: number): string {
218
232
  const statusIcon = comment.resolved ? chalk.green('✓') : chalk.yellow('○');
219
233
  const author = comment.author || 'Anonymous';
220
234
  const header = chalk.dim(`─── Comment ${index + 1}/${total} (line ${comment.line}) ───`);
@@ -227,16 +241,10 @@ function formatComment(comment, index, total) {
227
241
 
228
242
  /**
229
243
  * Run interactive comment review session
230
- * @param {string} text
231
- * @param {object} options
232
- * @param {string} options.author - Author name for replies
233
- * @param {Function} options.addReply - Function to add reply to comment
234
- * @param {Function} options.setCommentStatus - Function to set comment status
235
- * @returns {Promise<{text: string, resolved: number, replied: number, skipped: number}>}
236
244
  */
237
- export async function interactiveCommentReview(text, options = {}) {
245
+ export async function interactiveCommentReview(text: string, options: CommentReviewOptions = {}): Promise<CommentReviewResult> {
238
246
  const { author = 'Author', addReply, setCommentStatus } = options;
239
- const comments = getComments(text, { pendingOnly: true });
247
+ const comments = getComments(text, { pendingOnly: true }) as Comment[];
240
248
 
241
249
  if (comments.length === 0) {
242
250
  console.log(chalk.green('No pending comments found.'));
@@ -252,6 +260,7 @@ export async function interactiveCommentReview(text, options = {}) {
252
260
 
253
261
  for (let i = 0; i < comments.length; i++) {
254
262
  const comment = comments[i];
263
+ if (!comment) continue;
255
264
  console.log(formatComment(comment, i, comments.length));
256
265
 
257
266
  const prompt = chalk.dim('[r]eply [m]ark resolved [s]kip | resolve [A]ll [q]uit: ');
@@ -265,8 +274,9 @@ export async function interactiveCommentReview(text, options = {}) {
265
274
  case 'A':
266
275
  // Resolve all remaining
267
276
  for (let j = i; j < comments.length; j++) {
268
- if (setCommentStatus) {
269
- currentText = setCommentStatus(currentText, comments[j], true);
277
+ const c = comments[j];
278
+ if (setCommentStatus && c) {
279
+ currentText = setCommentStatus(currentText, c, true);
270
280
  }
271
281
  }
272
282
  resolved += comments.length - i;
@@ -288,7 +298,7 @@ export async function interactiveCommentReview(text, options = {}) {
288
298
  input: process.stdin,
289
299
  output: process.stdout,
290
300
  });
291
- const replyText = await new Promise((resolve) => {
301
+ const replyText = await new Promise<string>((resolve) => {
292
302
  rl.question(chalk.cyan(' Reply: '), resolve);
293
303
  });
294
304
  rl.close();
@@ -2,10 +2,59 @@
2
2
  * JSON Schema validation for rev.yaml configuration
3
3
  */
4
4
 
5
+ /**
6
+ * Validation error
7
+ */
8
+ interface ValidationError {
9
+ path: string;
10
+ message: string;
11
+ value?: unknown;
12
+ }
13
+
14
+ /**
15
+ * Validation warning
16
+ */
17
+ interface ValidationWarning {
18
+ path: string;
19
+ message: string;
20
+ }
21
+
22
+ /**
23
+ * Validation result
24
+ */
25
+ interface ValidationResult {
26
+ valid: boolean;
27
+ errors: ValidationError[];
28
+ warnings: ValidationWarning[];
29
+ }
30
+
31
+ /**
32
+ * JSON Schema type
33
+ */
34
+ interface Schema {
35
+ $schema?: string;
36
+ title?: string;
37
+ description?: string;
38
+ type?: string;
39
+ properties?: Record<string, Schema>;
40
+ required?: string[];
41
+ items?: Schema;
42
+ oneOf?: Schema[];
43
+ enum?: string[];
44
+ pattern?: string;
45
+ format?: string;
46
+ minimum?: number;
47
+ maximum?: number;
48
+ minItems?: number;
49
+ maxItems?: number;
50
+ additionalProperties?: boolean;
51
+ default?: unknown;
52
+ }
53
+
5
54
  /**
6
55
  * JSON Schema for rev.yaml
7
56
  */
8
- export const revYamlSchema = {
57
+ export const revYamlSchema: Schema = {
9
58
  $schema: 'http://json-schema.org/draft-07/schema#',
10
59
  title: 'rev.yaml configuration',
11
60
  description: 'Configuration file for docrev document workflow',
@@ -134,13 +183,9 @@ export const revYamlSchema = {
134
183
 
135
184
  /**
136
185
  * Validate a value against a simple schema
137
- * @param {*} value - Value to validate
138
- * @param {object} schema - JSON Schema
139
- * @param {string} path - Current path for error messages
140
- * @returns {object[]} Array of validation errors
141
186
  */
142
- function validateValue(value, schema, path = '') {
143
- const errors = [];
187
+ function validateValue(value: unknown, schema: Schema, path = ''): ValidationError[] {
188
+ const errors: ValidationError[] = [];
144
189
 
145
190
  // Handle oneOf
146
191
  if (schema.oneOf) {
@@ -228,23 +273,24 @@ function validateValue(value, schema, path = '') {
228
273
  }
229
274
  if (schema.items) {
230
275
  value.forEach((item, index) => {
231
- errors.push(...validateValue(item, schema.items, `${path}[${index}]`));
276
+ errors.push(...validateValue(item, schema.items!, `${path}[${index}]`));
232
277
  });
233
278
  }
234
279
  }
235
280
 
236
281
  // Object validation
237
282
  if (schema.type === 'object' && typeof value === 'object' && value !== null) {
283
+ const obj = value as Record<string, unknown>;
238
284
  if (schema.properties) {
239
285
  for (const [key, propSchema] of Object.entries(schema.properties)) {
240
- if (value[key] !== undefined) {
241
- errors.push(...validateValue(value[key], propSchema, path ? `${path}.${key}` : key));
286
+ if (obj[key] !== undefined) {
287
+ errors.push(...validateValue(obj[key], propSchema, path ? `${path}.${key}` : key));
242
288
  }
243
289
  }
244
290
  }
245
291
  if (schema.required) {
246
292
  for (const key of schema.required) {
247
- if (value[key] === undefined) {
293
+ if (obj[key] === undefined) {
248
294
  errors.push({
249
295
  path: path ? `${path}.${key}` : key,
250
296
  message: `Required property "${key}" is missing`,
@@ -260,29 +306,28 @@ function validateValue(value, schema, path = '') {
260
306
 
261
307
  /**
262
308
  * Validate rev.yaml configuration
263
- * @param {object} config - Parsed configuration object
264
- * @returns {{ valid: boolean, errors: object[], warnings: object[] }}
265
309
  */
266
- export function validateConfig(config) {
310
+ export function validateConfig(config: Record<string, unknown>): ValidationResult {
267
311
  const errors = validateValue(config, revYamlSchema);
268
- const warnings = [];
312
+ const warnings: ValidationWarning[] = [];
269
313
 
270
314
  // Additional semantic validations
271
- if (config.sections && config.sections.length === 0) {
315
+ if (config.sections && Array.isArray(config.sections) && config.sections.length === 0) {
272
316
  warnings.push({
273
317
  path: 'sections',
274
318
  message: 'No sections specified - build will auto-detect .md files',
275
319
  });
276
320
  }
277
321
 
278
- if (config.bibliography && !config.bibliography.endsWith('.bib')) {
322
+ if (config.bibliography && typeof config.bibliography === 'string' && !config.bibliography.endsWith('.bib')) {
279
323
  warnings.push({
280
324
  path: 'bibliography',
281
325
  message: 'Bibliography file should have .bib extension',
282
326
  });
283
327
  }
284
328
 
285
- if (config.pdf?.linestretch && (config.pdf.linestretch < 1 || config.pdf.linestretch > 3)) {
329
+ const pdf = config.pdf as { linestretch?: number } | undefined;
330
+ if (pdf?.linestretch && (pdf.linestretch < 1 || pdf.linestretch > 3)) {
286
331
  warnings.push({
287
332
  path: 'pdf.linestretch',
288
333
  message: 'Line stretch values outside 1-3 range may produce unexpected results',
@@ -290,7 +335,7 @@ export function validateConfig(config) {
290
335
  }
291
336
 
292
337
  // Check for common typos
293
- const knownKeys = Object.keys(revYamlSchema.properties);
338
+ const knownKeys = Object.keys(revYamlSchema.properties || {});
294
339
  for (const key of Object.keys(config)) {
295
340
  if (key.startsWith('_')) continue; // Internal keys
296
341
  if (!knownKeys.includes(key)) {
@@ -316,12 +361,12 @@ export function validateConfig(config) {
316
361
 
317
362
  /**
318
363
  * Format validation results for display
319
- * @param {{ valid: boolean, errors: object[], warnings: object[] }} result
320
- * @param {object} chalk - Chalk instance for coloring
321
- * @returns {string}
322
364
  */
323
- export function formatValidationResult(result, chalk) {
324
- const lines = [];
365
+ export function formatValidationResult(
366
+ result: ValidationResult,
367
+ chalk: { red: (s: string) => string; yellow: (s: string) => string; green: (s: string) => string }
368
+ ): string {
369
+ const lines: string[] = [];
325
370
 
326
371
  if (result.errors.length > 0) {
327
372
  lines.push(chalk.red('Configuration errors:'));
@@ -348,7 +393,7 @@ export function formatValidationResult(result, chalk) {
348
393
  /**
349
394
  * Levenshtein distance for typo detection
350
395
  */
351
- function levenshtein(a, b) {
396
+ function levenshtein(a: string, b: string): number {
352
397
  const matrix = Array(b.length + 1)
353
398
  .fill(null)
354
399
  .map(() => Array(a.length + 1).fill(null));