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,6 +6,7 @@
6
6
  * Miscellaneous utility commands for project management.
7
7
  */
8
8
 
9
+ import type { Command } from 'commander';
9
10
  import {
10
11
  chalk,
11
12
  fs,
@@ -19,14 +20,211 @@ import {
19
20
  stripAnnotations,
20
21
  parseAnnotations,
21
22
  getUserName,
23
+ countWords,
22
24
  } from './context.js';
23
25
 
26
+ // Use the actual BuildConfig from build.ts which allows string|Author[]
27
+ type BuildConfig = ReturnType<typeof loadBuildConfig>;
28
+
29
+ // Type definitions for package.json
30
+ interface PackageJson {
31
+ version?: string;
32
+ name?: string;
33
+ [key: string]: unknown;
34
+ }
35
+
36
+ // Options interfaces
37
+ interface WordCountOptions {
38
+ limit?: number;
39
+ journal?: string;
40
+ }
41
+
42
+ interface StatsOptions {
43
+ // No options currently
44
+ }
45
+
46
+ interface SearchOptions {
47
+ ignoreCase?: boolean;
48
+ context?: number;
49
+ }
50
+
51
+ interface BackupOptions {
52
+ name?: string;
53
+ output?: string;
54
+ }
55
+
56
+ interface ArchiveOptions {
57
+ dir?: string;
58
+ by?: string;
59
+ rename?: boolean;
60
+ dryRun?: boolean;
61
+ }
62
+
63
+ interface ExportOptions {
64
+ output?: string;
65
+ includeOutput?: boolean;
66
+ }
67
+
68
+ interface PreviewOptions {
69
+ // No options currently
70
+ }
71
+
72
+ interface WatchOptions {
73
+ open?: boolean;
74
+ }
75
+
76
+ interface LintOptions {
77
+ fix?: boolean;
78
+ }
79
+
80
+ interface GrammarOptions {
81
+ learn?: string;
82
+ forget?: string;
83
+ list?: boolean;
84
+ rules?: boolean;
85
+ scientific?: boolean;
86
+ severity?: string;
87
+ }
88
+
89
+ interface AnnotateOptions {
90
+ message?: string;
91
+ search?: string;
92
+ author?: string;
93
+ }
94
+
95
+ interface ApplyOptions {
96
+ author?: string;
97
+ }
98
+
99
+ interface CommentOptions {
100
+ author?: string;
101
+ }
102
+
103
+ interface CleanOptions {
104
+ dryRun?: boolean;
105
+ all?: boolean;
106
+ }
107
+
108
+ interface CheckOptions {
109
+ fix?: boolean;
110
+ severity?: string;
111
+ }
112
+
113
+ interface OpenOptions {
114
+ // No options currently
115
+ }
116
+
117
+ interface SpellingOptions {
118
+ learn?: string;
119
+ learnProject?: string;
120
+ forget?: string;
121
+ forgetProject?: string;
122
+ list?: boolean;
123
+ listProject?: boolean;
124
+ listAll?: boolean;
125
+ british?: boolean;
126
+ addNames?: boolean;
127
+ }
128
+
129
+ interface UpgradeOptions {
130
+ check?: boolean;
131
+ }
132
+
133
+ interface BatchOptions {
134
+ parallel?: boolean;
135
+ dryRun?: boolean;
136
+ all?: boolean;
137
+ }
138
+
139
+ // Comment interface
140
+ interface Comment {
141
+ text: string;
142
+ resolved: boolean;
143
+ author?: string;
144
+ }
145
+
146
+ // Annotation interface
147
+ interface Annotation {
148
+ type: string;
149
+ text: string;
150
+ }
151
+
152
+ // Grammar issue interface (from grammar.ts)
153
+ interface GrammarIssue {
154
+ rule: string;
155
+ severity: 'error' | 'warning' | 'info';
156
+ message: string;
157
+ line: number;
158
+ column: number;
159
+ match: string;
160
+ context: string;
161
+ file?: string;
162
+ }
163
+
164
+ // Grammar summary interface (from grammar.ts)
165
+ interface GrammarSummary {
166
+ total: number;
167
+ errors: number;
168
+ warnings: number;
169
+ info: number;
170
+ byRule?: Record<string, number>;
171
+ }
172
+
173
+ // Lint issue interface
174
+ interface LintIssue {
175
+ type: 'error' | 'warning';
176
+ message: string;
177
+ fix?: string | null;
178
+ file?: string;
179
+ }
180
+
181
+ // Build result interface - matches build.ts export
182
+ interface BuildResult {
183
+ format: string;
184
+ success: boolean;
185
+ outputPath?: string;
186
+ error?: string;
187
+ }
188
+
189
+ // Full build result interface - matches build.ts FullBuildResult
190
+ interface FullBuildResult {
191
+ results: BuildResult[];
192
+ paperPath: string;
193
+ warnings: string[];
194
+ forwardRefsResolved: number;
195
+ refsAutoInjected?: boolean;
196
+ }
197
+
198
+ // Spelling issue interface
199
+ interface SpellingIssue {
200
+ word: string;
201
+ line: number;
202
+ suggestions: string[];
203
+ }
204
+
205
+ // Spelling result interface
206
+ interface SpellingResult {
207
+ misspelled: SpellingIssue[];
208
+ possibleNames: SpellingIssue[];
209
+ }
210
+
211
+ // Batch result interface
212
+ interface BatchResult {
213
+ file: string;
214
+ status: string;
215
+ annotations?: number;
216
+ comments?: number;
217
+ pending?: number;
218
+ total?: number;
219
+ resolved?: number;
220
+ stripped?: boolean;
221
+ error?: string;
222
+ }
223
+
24
224
  /**
25
225
  * Register utility commands with the program
26
- * @param {import('commander').Command} program
27
- * @param {object} [pkg] - Package.json object for version info
28
226
  */
29
- export function register(program, pkg) {
227
+ export function register(program: Command, pkg?: PackageJson): void {
30
228
  // ==========================================================================
31
229
  // HELP command - Comprehensive help
32
230
  // ==========================================================================
@@ -35,7 +233,7 @@ export function register(program, pkg) {
35
233
  .command('help')
36
234
  .description('Show detailed help and workflow guide')
37
235
  .argument('[topic]', 'Help topic: workflow, syntax, commands')
38
- .action((topic) => {
236
+ .action((topic?: string) => {
39
237
  if (!topic || topic === 'all') {
40
238
  showFullHelp(pkg);
41
239
  } else if (topic === 'workflow') {
@@ -58,7 +256,7 @@ export function register(program, pkg) {
58
256
  .command('completions')
59
257
  .description('Output shell completions')
60
258
  .argument('<shell>', 'Shell type: bash, zsh, powershell')
61
- .action((shell) => {
259
+ .action((shell: string) => {
62
260
  const completionsDir = path.join(import.meta.dirname, '..', '..', 'completions');
63
261
 
64
262
  if (shell === 'bash') {
@@ -102,10 +300,10 @@ export function register(program, pkg) {
102
300
  .description('Show word counts per section')
103
301
  .option('-l, --limit <number>', 'Warn if total exceeds limit', parseInt)
104
302
  .option('-j, --journal <name>', 'Use journal word limit')
105
- .action(async (options) => {
106
- let config = {};
303
+ .action(async (options: WordCountOptions) => {
304
+ let config: Partial<BuildConfig> = {};
107
305
  try {
108
- config = loadBuildConfig() || {};
306
+ config = loadBuildConfig('.') || {};
109
307
  } catch {
110
308
  // Not in a rev project, that's ok
111
309
  }
@@ -123,26 +321,8 @@ export function register(program, pkg) {
123
321
  sections.push(...mdFiles);
124
322
  }
125
323
 
126
- const countWords = (text) => {
127
- return text
128
- .replace(/^---[\s\S]*?---/m, '')
129
- .replace(/!\[.*?\]\(.*?\)/g, '')
130
- .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
131
- .replace(/#+\s*/g, '')
132
- .replace(/\*\*|__|[*_`]/g, '')
133
- .replace(/```[\s\S]*?```/g, '')
134
- .replace(/\{[^}]+\}/g, '')
135
- .replace(/@\w+:\w+/g, '')
136
- .replace(/@\w+/g, '')
137
- .replace(/\|[^|]+\|/g, ' ')
138
- .replace(/\n+/g, ' ')
139
- .trim()
140
- .split(/\s+/)
141
- .filter(w => w.length > 0).length;
142
- };
143
-
144
324
  let total = 0;
145
- const rows = [];
325
+ const rows: string[][] = [];
146
326
 
147
327
  for (const section of sections) {
148
328
  if (!fs.existsSync(section)) continue;
@@ -183,10 +363,10 @@ export function register(program, pkg) {
183
363
  program
184
364
  .command('stats')
185
365
  .description('Show project statistics dashboard')
186
- .action(async () => {
187
- let config = {};
366
+ .action(async (_options: StatsOptions) => {
367
+ let config: Partial<BuildConfig> = {};
188
368
  try {
189
- config = loadBuildConfig() || {};
369
+ config = loadBuildConfig('.') || {};
190
370
  } catch {
191
371
  // Not in a rev project, that's ok
192
372
  }
@@ -198,26 +378,12 @@ export function register(program, pkg) {
198
378
  );
199
379
  }
200
380
 
201
- const countWords = (text) => {
202
- return text
203
- .replace(/^---[\s\S]*?---/m, '')
204
- .replace(/!\[.*?\]\(.*?\)/g, '')
205
- .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
206
- .replace(/[#*_`]/g, '')
207
- .replace(/\{[^}]+\}/g, '')
208
- .replace(/@\w+/g, '')
209
- .replace(/\n+/g, ' ')
210
- .trim()
211
- .split(/\s+/)
212
- .filter(w => w.length > 0).length;
213
- };
214
-
215
381
  let totalWords = 0;
216
382
  let totalFigures = 0;
217
383
  let totalTables = 0;
218
384
  let totalComments = 0;
219
385
  let pendingComments = 0;
220
- const citations = new Set();
386
+ const citations = new Set<string>();
221
387
 
222
388
  for (const section of sections) {
223
389
  if (!fs.existsSync(section)) continue;
@@ -238,7 +404,7 @@ export function register(program, pkg) {
238
404
  console.log(fmt.header('Project Statistics'));
239
405
  console.log();
240
406
 
241
- const stats = [
407
+ const stats: [string, string | number][] = [
242
408
  ['Sections', sections.length],
243
409
  ['Words', totalWords.toLocaleString()],
244
410
  ['Figures', Math.round(totalFigures)],
@@ -272,10 +438,10 @@ export function register(program, pkg) {
272
438
  .argument('<query>', 'Search query (supports regex)')
273
439
  .option('-i, --ignore-case', 'Case-insensitive search')
274
440
  .option('-c, --context <lines>', 'Show context lines', parseInt, 1)
275
- .action((query, options) => {
276
- let config = {};
441
+ .action((query: string, options: SearchOptions) => {
442
+ let config: Partial<BuildConfig> = {};
277
443
  try {
278
- config = loadBuildConfig() || {};
444
+ config = loadBuildConfig('.') || {};
279
445
  } catch {
280
446
  // Not in a rev project, that's ok
281
447
  }
@@ -288,7 +454,7 @@ export function register(program, pkg) {
288
454
  }
289
455
 
290
456
  const flags = options.ignoreCase ? 'gi' : 'g';
291
- let pattern;
457
+ let pattern: RegExp;
292
458
  try {
293
459
  pattern = new RegExp(query, flags);
294
460
  } catch {
@@ -302,7 +468,7 @@ export function register(program, pkg) {
302
468
  const text = fs.readFileSync(section, 'utf-8');
303
469
  const lines = text.split('\n');
304
470
 
305
- const matches = [];
471
+ const matches: { line: number; text: string }[] = [];
306
472
  for (let i = 0; i < lines.length; i++) {
307
473
  if (pattern.test(lines[i])) {
308
474
  matches.push({ line: i + 1, text: lines[i] });
@@ -336,13 +502,13 @@ export function register(program, pkg) {
336
502
  .description('Create timestamped project backup')
337
503
  .option('-n, --name <name>', 'Custom backup name')
338
504
  .option('-o, --output <dir>', 'Output directory', '.')
339
- .action(async (options) => {
505
+ .action(async (options: BackupOptions) => {
340
506
  const { default: AdmZip } = await import('adm-zip');
341
507
  const zip = new AdmZip();
342
508
 
343
509
  const date = new Date().toISOString().slice(0, 10);
344
510
  const name = options.name || `backup-${date}`;
345
- const outputPath = path.join(options.output, `${name}.zip`);
511
+ const outputPath = path.join(options.output || '.', `${name}.zip`);
346
512
 
347
513
  // Files to exclude
348
514
  const excludePatterns = [
@@ -350,14 +516,14 @@ export function register(program, pkg) {
350
516
  'paper.md' // Generated file
351
517
  ];
352
518
 
353
- const shouldInclude = (file) => {
519
+ const shouldInclude = (file: string): boolean => {
354
520
  for (const pattern of excludePatterns) {
355
521
  if (file.includes(pattern.replace('*', ''))) return false;
356
522
  }
357
523
  return true;
358
524
  };
359
525
 
360
- const addDir = (dir, zipPath = '') => {
526
+ const addDir = (dir: string, zipPath = ''): void => {
361
527
  const entries = fs.readdirSync(dir, { withFileTypes: true });
362
528
  for (const entry of entries) {
363
529
  const fullPath = path.join(dir, entry.name);
@@ -401,7 +567,7 @@ export function register(program, pkg) {
401
567
  .option('--by <name>', 'Reviewer name (auto-detected if single commenter)')
402
568
  .option('--no-rename', 'Keep original filenames')
403
569
  .option('--dry-run', 'Preview without moving files')
404
- .action(async (files, options) => {
570
+ .action(async (files: string[] | undefined, options: ArchiveOptions) => {
405
571
  const { extractWordComments } = await import('../import.js');
406
572
  const { default: YAML } = await import('yaml');
407
573
 
@@ -411,7 +577,7 @@ export function register(program, pkg) {
411
577
  : findFiles('.docx');
412
578
 
413
579
  // Exclude our own build outputs
414
- let projectSlug = null;
580
+ let projectSlug: string | null = null;
415
581
  const configPath = path.join(process.cwd(), 'rev.yaml');
416
582
  if (fs.existsSync(configPath)) {
417
583
  try {
@@ -429,7 +595,7 @@ export function register(program, pkg) {
429
595
  }
430
596
 
431
597
  // Filter out build outputs
432
- if (projectSlug && files.length === 0) {
598
+ if (projectSlug && (!files || files.length === 0)) {
433
599
  const buildPatterns = [
434
600
  `${projectSlug}.docx`,
435
601
  `${projectSlug}_comments.docx`,
@@ -438,7 +604,7 @@ export function register(program, pkg) {
438
604
  'paper_comments.docx',
439
605
  'paper-changes.docx',
440
606
  ];
441
- const excluded = [];
607
+ const excluded: string[] = [];
442
608
  docxFiles = docxFiles.filter(f => {
443
609
  const base = path.basename(f).toLowerCase();
444
610
  const isBuilt = buildPatterns.includes(base);
@@ -459,7 +625,7 @@ export function register(program, pkg) {
459
625
  const projectTitle = projectSlug;
460
626
 
461
627
  // Create archive folder
462
- const archiveDir = path.resolve(options.dir);
628
+ const archiveDir = path.resolve(options.dir || 'archive');
463
629
  if (!options.dryRun && !fs.existsSync(archiveDir)) {
464
630
  fs.mkdirSync(archiveDir, { recursive: true });
465
631
  }
@@ -467,14 +633,14 @@ export function register(program, pkg) {
467
633
  console.log(fmt.header('Archive'));
468
634
  console.log();
469
635
 
470
- const moved = [];
636
+ const moved: string[] = [];
471
637
  for (const file of docxFiles) {
472
638
  const stat = fs.statSync(file);
473
639
  const mtime = stat.mtime;
474
640
  const timestamp = mtime.toISOString().slice(0, 19).replace(/[-:]/g, '').replace('T', '_');
475
641
 
476
642
  // Determine reviewer name
477
- let reviewer = options.by || null;
643
+ let reviewer: string | null = options.by || null;
478
644
  if (!reviewer && options.rename !== false) {
479
645
  try {
480
646
  const comments = await extractWordComments(file);
@@ -488,7 +654,7 @@ export function register(program, pkg) {
488
654
  }
489
655
 
490
656
  // Generate new name
491
- let newName;
657
+ let newName: string;
492
658
  if (options.rename === false) {
493
659
  newName = path.basename(file);
494
660
  } else {
@@ -508,7 +674,7 @@ export function register(program, pkg) {
508
674
  const destPath = path.join(archiveDir, newName);
509
675
 
510
676
  if (options.dryRun) {
511
- console.log(` ${chalk.dim(file)} → ${chalk.cyan(path.join(options.dir, newName))}`);
677
+ console.log(` ${chalk.dim(file)} → ${chalk.cyan(path.join(options.dir || 'archive', newName))}`);
512
678
  } else {
513
679
  // Handle name collision
514
680
  let finalPath = destPath;
@@ -529,7 +695,7 @@ export function register(program, pkg) {
529
695
  if (options.dryRun) {
530
696
  console.log(fmt.status('info', `Would archive ${moved.length} file(s). Run without --dry-run to proceed.`));
531
697
  } else {
532
- console.log(fmt.status('success', `Archived ${moved.length} file(s) to ${options.dir}/`));
698
+ console.log(fmt.status('success', `Archived ${moved.length} file(s) to ${options.dir || 'archive'}/`));
533
699
  }
534
700
  });
535
701
 
@@ -542,13 +708,13 @@ export function register(program, pkg) {
542
708
  .description('Export project as distributable zip')
543
709
  .option('-o, --output <file>', 'Output filename')
544
710
  .option('--include-output', 'Include built PDF/DOCX files')
545
- .action(async (options) => {
711
+ .action(async (options: ExportOptions) => {
546
712
  const { default: AdmZip } = await import('adm-zip');
547
713
  const { build } = await import('../build.js');
548
714
 
549
- let config = {};
715
+ let config: Partial<BuildConfig> = {};
550
716
  try {
551
- config = loadBuildConfig() || {};
717
+ config = loadBuildConfig('.') || {};
552
718
  } catch {
553
719
  // Not in a rev project, that's ok
554
720
  }
@@ -556,7 +722,7 @@ export function register(program, pkg) {
556
722
  // Build first if including output
557
723
  if (options.includeOutput) {
558
724
  console.log(chalk.dim('Building documents...'));
559
- await build(['pdf', 'docx']);
725
+ await build('.', ['pdf', 'docx']);
560
726
  }
561
727
 
562
728
  const zip = new AdmZip();
@@ -565,7 +731,7 @@ export function register(program, pkg) {
565
731
 
566
732
  const exclude = ['node_modules', '.git', '.DS_Store', '*.zip'];
567
733
 
568
- const shouldInclude = (name) => {
734
+ const shouldInclude = (name: string): boolean => {
569
735
  if (!options.includeOutput && (name.endsWith('.pdf') || name.endsWith('.docx'))) {
570
736
  return false;
571
737
  }
@@ -575,7 +741,7 @@ export function register(program, pkg) {
575
741
  return true;
576
742
  };
577
743
 
578
- const addDir = (dir, zipPath = '') => {
744
+ const addDir = (dir: string, zipPath = ''): void => {
579
745
  const entries = fs.readdirSync(dir, { withFileTypes: true });
580
746
  for (const entry of entries) {
581
747
  const fullPath = path.join(dir, entry.name);
@@ -614,29 +780,30 @@ export function register(program, pkg) {
614
780
  .command('preview')
615
781
  .description('Build and open document in default app')
616
782
  .argument('[format]', 'Format to preview: pdf, docx', 'pdf')
617
- .action(async (format) => {
783
+ .action(async (format: string, _options: PreviewOptions) => {
618
784
  const { exec } = await import('child_process');
619
785
  const { build } = await import('../build.js');
620
786
 
621
- let config = {};
787
+ let config: Partial<BuildConfig> = {};
622
788
  try {
623
- config = loadBuildConfig() || {};
789
+ config = loadBuildConfig('.') || {};
624
790
  } catch (err) {
625
791
  console.error(chalk.red('Not in a rev project directory (no rev.yaml found)'));
626
792
  process.exit(1);
627
793
  }
628
794
 
629
795
  console.log(chalk.dim(`Building ${format}...`));
630
- const results = await build([format]);
796
+ const result = await build('.', [format]);
631
797
 
632
- const result = results.find(r => r.format === format);
633
- if (!result?.success) {
634
- console.error(chalk.red(`Build failed: ${result?.error || 'Unknown error'}`));
798
+ const buildResult = result.results.find(r => r.format === format);
799
+ if (!buildResult?.success) {
800
+ const errorMsg = buildResult?.error || 'Unknown error';
801
+ console.error(chalk.red(`Build failed: ${errorMsg}`));
635
802
  process.exit(1);
636
803
  }
637
804
 
638
- const outputFile = result.output;
639
- if (!fs.existsSync(outputFile)) {
805
+ const outputFile = buildResult.outputPath;
806
+ if (!outputFile || !fs.existsSync(outputFile)) {
640
807
  console.error(chalk.red(`Output file not found: ${outputFile}`));
641
808
  process.exit(1);
642
809
  }
@@ -663,13 +830,13 @@ export function register(program, pkg) {
663
830
  .description('Watch files and auto-rebuild on changes')
664
831
  .argument('[format]', 'Format to build: pdf, docx, all', 'pdf')
665
832
  .option('--no-open', 'Do not open after first build')
666
- .action(async (format, options) => {
833
+ .action(async (format: string, options: WatchOptions) => {
667
834
  const { exec } = await import('child_process');
668
835
  const { build } = await import('../build.js');
669
836
 
670
- let config = {};
837
+ let config: Partial<BuildConfig> = {};
671
838
  try {
672
- config = loadBuildConfig() || {};
839
+ config = loadBuildConfig('.') || {};
673
840
  } catch (err) {
674
841
  console.error(chalk.red('Not in a rev project directory (no rev.yaml found)'));
675
842
  process.exit(1);
@@ -695,7 +862,7 @@ export function register(program, pkg) {
695
862
  let building = false;
696
863
  let pendingBuild = false;
697
864
 
698
- const doBuild = async () => {
865
+ const doBuild = async (): Promise<void> => {
699
866
  if (building) {
700
867
  pendingBuild = true;
701
868
  return;
@@ -706,17 +873,17 @@ export function register(program, pkg) {
706
873
 
707
874
  try {
708
875
  const formats = format === 'all' ? ['pdf', 'docx'] : [format];
709
- const results = await build(formats);
876
+ const result = await build('.', formats);
710
877
 
711
- for (const r of results) {
878
+ for (const r of result.results) {
712
879
  if (r.success) {
713
- console.log(chalk.green(` ✓ ${r.format}: ${r.output}`));
880
+ console.log(chalk.green(` ✓ ${r.format}: ${r.outputPath}`));
714
881
  } else {
715
882
  console.log(chalk.red(` ✗ ${r.format}: ${r.error}`));
716
883
  }
717
884
  }
718
885
  } catch (err) {
719
- console.error(chalk.red(` Build error: ${err.message}`));
886
+ console.error(chalk.red(` Build error: ${(err as Error).message}`));
720
887
  }
721
888
 
722
889
  building = false;
@@ -760,10 +927,10 @@ export function register(program, pkg) {
760
927
  .command('lint')
761
928
  .description('Check for common issues in the project')
762
929
  .option('--fix', 'Auto-fix issues where possible')
763
- .action(async (options) => {
764
- let config = {};
930
+ .action(async (_options: LintOptions) => {
931
+ let config: Partial<BuildConfig> = {};
765
932
  try {
766
- config = loadBuildConfig() || {};
933
+ config = loadBuildConfig('.') || {};
767
934
  } catch {
768
935
  // Not in a rev project, that's ok
769
936
  }
@@ -775,8 +942,8 @@ export function register(program, pkg) {
775
942
  );
776
943
  }
777
944
 
778
- const issues = [];
779
- const warnings = [];
945
+ const issues: LintIssue[] = [];
946
+ const warnings: LintIssue[] = [];
780
947
 
781
948
  // Collect all content
782
949
  let allText = '';
@@ -787,12 +954,12 @@ export function register(program, pkg) {
787
954
  }
788
955
 
789
956
  // Check 1: Broken cross-references
790
- const figAnchors = new Set();
791
- const tblAnchors = new Set();
792
- const eqAnchors = new Set();
957
+ const figAnchors = new Set<string>();
958
+ const tblAnchors = new Set<string>();
959
+ const eqAnchors = new Set<string>();
793
960
 
794
961
  const anchorPattern = /\{#(fig|tbl|eq):([^}]+)\}/g;
795
- let match;
962
+ let match: RegExpExecArray | null;
796
963
  while ((match = anchorPattern.exec(allText)) !== null) {
797
964
  if (match[1] === 'fig') figAnchors.add(match[2]);
798
965
  else if (match[1] === 'tbl') tblAnchors.add(match[2]);
@@ -828,7 +995,7 @@ export function register(program, pkg) {
828
995
  const bibPath = config.bibliography || 'references.bib';
829
996
  if (fs.existsSync(bibPath)) {
830
997
  const bibContent = fs.readFileSync(bibPath, 'utf-8');
831
- const bibKeys = new Set();
998
+ const bibKeys = new Set<string>();
832
999
  const bibPattern = /@\w+\s*\{\s*([^,]+)/g;
833
1000
  while ((match = bibPattern.exec(bibContent)) !== null) {
834
1001
  bibKeys.add(match[1].trim());
@@ -908,7 +1075,7 @@ export function register(program, pkg) {
908
1075
  .option('--rules', 'List available grammar rules')
909
1076
  .option('--no-scientific', 'Disable scientific writing rules')
910
1077
  .option('-s, --severity <level>', 'Minimum severity: error, warning, info', 'info')
911
- .action(async (files, options) => {
1078
+ .action(async (files: string[] | undefined, options: GrammarOptions) => {
912
1079
  const {
913
1080
  checkGrammar,
914
1081
  getGrammarSummary,
@@ -974,9 +1141,9 @@ export function register(program, pkg) {
974
1141
  // Get files to check
975
1142
  let mdFiles = files;
976
1143
  if (!mdFiles || mdFiles.length === 0) {
977
- let config = {};
1144
+ let config: Partial<BuildConfig> = {};
978
1145
  try {
979
- config = loadBuildConfig() || {};
1146
+ config = loadBuildConfig('.') || {};
980
1147
  } catch {
981
1148
  // Not in a rev project
982
1149
  }
@@ -997,10 +1164,10 @@ export function register(program, pkg) {
997
1164
  console.log(fmt.header('Grammar Check'));
998
1165
  console.log();
999
1166
 
1000
- const severityLevels = { error: 3, warning: 2, info: 1 };
1001
- const minSeverity = severityLevels[options.severity] || 1;
1167
+ const severityLevels: Record<string, number> = { error: 3, warning: 2, info: 1 };
1168
+ const minSeverity = severityLevels[options.severity || 'info'] || 1;
1002
1169
 
1003
- let allIssues = [];
1170
+ let allIssues: GrammarIssue[] = [];
1004
1171
 
1005
1172
  for (const file of mdFiles) {
1006
1173
  if (!fs.existsSync(file)) continue;
@@ -1009,7 +1176,7 @@ export function register(program, pkg) {
1009
1176
  const issues = checkGrammar(text, { scientific: options.scientific });
1010
1177
 
1011
1178
  // Filter by severity
1012
- const filtered = issues.filter(i => severityLevels[i.severity] >= minSeverity);
1179
+ const filtered = issues.filter((i: GrammarIssue) => severityLevels[i.severity] >= minSeverity);
1013
1180
 
1014
1181
  if (filtered.length > 0) {
1015
1182
  console.log(chalk.cyan.bold(file));
@@ -1049,7 +1216,7 @@ export function register(program, pkg) {
1049
1216
  .option('-m, --message <text>', 'Comment text')
1050
1217
  .option('-s, --search <text>', 'Text to attach comment to')
1051
1218
  .option('-a, --author <name>', 'Comment author')
1052
- .action(async (docxPath, options) => {
1219
+ .action(async (docxPath: string, options: AnnotateOptions) => {
1053
1220
  if (!fs.existsSync(docxPath)) {
1054
1221
  console.error(chalk.red(`File not found: ${docxPath}`));
1055
1222
  process.exit(1);
@@ -1074,14 +1241,14 @@ export function register(program, pkg) {
1074
1241
 
1075
1242
  // Read or create comments.xml
1076
1243
  let commentsEntry = zip.getEntry('word/comments.xml');
1077
- let commentsXml;
1244
+ let commentsXml: string;
1078
1245
  let nextCommentId = 1;
1079
1246
 
1080
1247
  if (commentsEntry) {
1081
1248
  commentsXml = zip.readAsText(commentsEntry);
1082
1249
  const idMatches = commentsXml.match(/w:id="(\d+)"/g) || [];
1083
1250
  for (const m of idMatches) {
1084
- const id = parseInt(m.match(/\d+/)[0]);
1251
+ const id = parseInt(m.match(/\d+/)![0]);
1085
1252
  if (id >= nextCommentId) nextCommentId = id + 1;
1086
1253
  }
1087
1254
  } else {
@@ -1107,7 +1274,7 @@ export function register(program, pkg) {
1107
1274
  const textPattern = new RegExp(`(<w:t[^>]*>)([^<]*${searchText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[^<]*)(<\/w:t>)`, 'i');
1108
1275
 
1109
1276
  if (textPattern.test(docXml)) {
1110
- docXml = docXml.replace(textPattern, (match, start, text, end) => {
1277
+ docXml = docXml.replace(textPattern, (_match, start, text, end) => {
1111
1278
  return `<w:commentRangeStart w:id="${commentId}"/>${start}${text}${end}<w:commentRangeEnd w:id="${commentId}"/><w:r><w:commentReference w:id="${commentId}"/></w:r>`;
1112
1279
  });
1113
1280
  } else {
@@ -1162,7 +1329,7 @@ export function register(program, pkg) {
1162
1329
  .argument('<md>', 'Markdown file with annotations')
1163
1330
  .argument('<docx>', 'Output Word document')
1164
1331
  .option('-a, --author <name>', 'Author name for track changes')
1165
- .action(async (mdPath, docxPath, options) => {
1332
+ .action(async (mdPath: string, docxPath: string, options: ApplyOptions) => {
1166
1333
  if (!fs.existsSync(mdPath)) {
1167
1334
  console.error(chalk.red(`File not found: ${mdPath}`));
1168
1335
  process.exit(1);
@@ -1191,7 +1358,7 @@ export function register(program, pkg) {
1191
1358
  process.exit(1);
1192
1359
  }
1193
1360
  } catch (err) {
1194
- console.error(chalk.red(`Error: ${err.message}`));
1361
+ console.error(chalk.red(`Error: ${(err as Error).message}`));
1195
1362
  process.exit(1);
1196
1363
  }
1197
1364
  });
@@ -1205,19 +1372,20 @@ export function register(program, pkg) {
1205
1372
  .description('Add comments to Word document interactively')
1206
1373
  .argument('<docx>', 'Word document')
1207
1374
  .option('-a, --author <name>', 'Comment author')
1208
- .action(async (docxPath, options) => {
1375
+ .action(async (docxPath: string, options: CommentOptions) => {
1209
1376
  if (!fs.existsSync(docxPath)) {
1210
1377
  console.error(chalk.red(`File not found: ${docxPath}`));
1211
1378
  process.exit(1);
1212
1379
  }
1213
1380
 
1214
1381
  const { default: AdmZip } = await import('adm-zip');
1215
- const rl = (await import('readline')).createInterface({
1382
+ const readline = await import('readline');
1383
+ const rl = readline.createInterface({
1216
1384
  input: process.stdin,
1217
1385
  output: process.stdout,
1218
1386
  });
1219
1387
 
1220
- const ask = (prompt) => new Promise((resolve) => rl.question(prompt, resolve));
1388
+ const ask = (prompt: string): Promise<string> => new Promise((resolve) => rl.question(prompt, resolve));
1221
1389
 
1222
1390
  const author = options.author || getUserName() || 'Reviewer';
1223
1391
 
@@ -1252,14 +1420,14 @@ export function register(program, pkg) {
1252
1420
 
1253
1421
  // Read or create comments.xml
1254
1422
  let commentsEntry = zip.getEntry('word/comments.xml');
1255
- let commentsXml;
1423
+ let commentsXml: string;
1256
1424
  let nextCommentId = 1;
1257
1425
 
1258
1426
  if (commentsEntry) {
1259
1427
  commentsXml = zip.readAsText(commentsEntry);
1260
1428
  const idMatches = commentsXml.match(/w:id="(\d+)"/g) || [];
1261
1429
  for (const m of idMatches) {
1262
- const id = parseInt(m.match(/\d+/)[0]);
1430
+ const id = parseInt(m.match(/\d+/)![0]);
1263
1431
  if (id >= nextCommentId) nextCommentId = id + 1;
1264
1432
  }
1265
1433
  } else {
@@ -1284,7 +1452,7 @@ export function register(program, pkg) {
1284
1452
  const textPattern = new RegExp(`(<w:t[^>]*>)([^<]*${escapedSearch}[^<]*)(<\/w:t>)`, 'i');
1285
1453
 
1286
1454
  if (textPattern.test(docXml)) {
1287
- docXml = docXml.replace(textPattern, (match, start, text, end) => {
1455
+ docXml = docXml.replace(textPattern, (_match, start, text, end) => {
1288
1456
  return `<w:commentRangeStart w:id="${commentId}"/>${start}${text}${end}<w:commentRangeEnd w:id="${commentId}"/><w:r><w:commentReference w:id="${commentId}"/></w:r>`;
1289
1457
  });
1290
1458
  console.log(chalk.green(` ✓ Comment added at "${searchText}"`));
@@ -1351,10 +1519,10 @@ export function register(program, pkg) {
1351
1519
  .description('Remove generated files (paper.md, PDFs, DOCXs)')
1352
1520
  .option('-n, --dry-run', 'Show what would be deleted without deleting')
1353
1521
  .option('--all', 'Also remove backup and export zips')
1354
- .action((options) => {
1355
- let config = {};
1522
+ .action((options: CleanOptions) => {
1523
+ let config: Partial<BuildConfig> = {};
1356
1524
  try {
1357
- config = loadBuildConfig() || {};
1525
+ config = loadBuildConfig('.') || {};
1358
1526
  } catch {
1359
1527
  // Not in a rev project, that's ok
1360
1528
  }
@@ -1375,7 +1543,7 @@ export function register(program, pkg) {
1375
1543
  patterns.push('*.zip', 'backup-*.zip', '*-export.zip');
1376
1544
  }
1377
1545
 
1378
- const toDelete = [];
1546
+ const toDelete: string[] = [];
1379
1547
 
1380
1548
  for (const pattern of patterns) {
1381
1549
  if (pattern.includes('*')) {
@@ -1421,7 +1589,7 @@ export function register(program, pkg) {
1421
1589
  .description('Run all checks before submission (lint + grammar + citations)')
1422
1590
  .option('--fix', 'Auto-fix issues where possible')
1423
1591
  .option('-s, --severity <level>', 'Minimum grammar severity', 'warning')
1424
- .action(async (options) => {
1592
+ .action(async (options: CheckOptions) => {
1425
1593
  const { validateCitations } = await import('../citations.js');
1426
1594
  const { checkGrammar, getGrammarSummary } = await import('../grammar.js');
1427
1595
 
@@ -1433,9 +1601,9 @@ export function register(program, pkg) {
1433
1601
 
1434
1602
  // 1. Run lint
1435
1603
  console.log(chalk.cyan.bold('1. Linting...'));
1436
- let config = {};
1604
+ let config: Partial<BuildConfig> = {};
1437
1605
  try {
1438
- config = loadBuildConfig() || {};
1606
+ config = loadBuildConfig('.') || {};
1439
1607
  } catch {
1440
1608
  // Not in a rev project
1441
1609
  }
@@ -1447,8 +1615,8 @@ export function register(program, pkg) {
1447
1615
  );
1448
1616
  }
1449
1617
 
1450
- const lintIssues = [];
1451
- const lintWarnings = [];
1618
+ const lintIssues: { file: string; message: string }[] = [];
1619
+ const lintWarnings: { file: string; message: string }[] = [];
1452
1620
 
1453
1621
  for (const file of sections) {
1454
1622
  if (!fs.existsSync(file)) continue;
@@ -1457,7 +1625,7 @@ export function register(program, pkg) {
1457
1625
  // Check for broken cross-references
1458
1626
  const refs = content.match(/@(fig|tbl|eq|sec):\w+/g) || [];
1459
1627
  const anchors = content.match(/\{#(fig|tbl|eq|sec):[^}]+\}/g) || [];
1460
- const anchorLabels = anchors.map(a => a.match(/#([^}]+)/)[1]);
1628
+ const anchorLabels = anchors.map(a => a.match(/#([^}]+)/)![1]);
1461
1629
 
1462
1630
  for (const ref of refs) {
1463
1631
  const label = ref.slice(1);
@@ -1493,15 +1661,15 @@ export function register(program, pkg) {
1493
1661
  // 2. Run grammar check
1494
1662
  console.log(chalk.cyan.bold('2. Grammar check...'));
1495
1663
 
1496
- const severityLevels = { error: 3, warning: 2, info: 1 };
1497
- const minSeverity = severityLevels[options.severity] || 2;
1498
- let grammarIssues = [];
1664
+ const severityLevels: Record<string, number> = { error: 3, warning: 2, info: 1 };
1665
+ const minSeverity = severityLevels[options.severity || 'warning'] || 2;
1666
+ let grammarIssues: GrammarIssue[] = [];
1499
1667
 
1500
1668
  for (const file of sections) {
1501
1669
  if (!fs.existsSync(file)) continue;
1502
1670
  const text = fs.readFileSync(file, 'utf-8');
1503
1671
  const issues = checkGrammar(text, { scientific: true });
1504
- const filtered = issues.filter(i => severityLevels[i.severity] >= minSeverity);
1672
+ const filtered = issues.filter((i: GrammarIssue) => severityLevels[i.severity] >= minSeverity);
1505
1673
  grammarIssues.push(...filtered.map(i => ({ ...i, file })));
1506
1674
  }
1507
1675
 
@@ -1522,13 +1690,9 @@ export function register(program, pkg) {
1522
1690
  console.log(chalk.cyan.bold('3. Citation check...'));
1523
1691
  const bibFile = config.bibliography || 'references.bib';
1524
1692
  if (fs.existsSync(bibFile)) {
1525
- const allContent = sections
1526
- .filter(f => fs.existsSync(f))
1527
- .map(f => fs.readFileSync(f, 'utf-8'))
1528
- .join('\n');
1529
- const bibContent = fs.readFileSync(bibFile, 'utf-8');
1693
+ const existingSections = sections.filter(f => fs.existsSync(f));
1530
1694
 
1531
- const result = validateCitations(allContent, bibContent);
1695
+ const result = validateCitations(existingSections, bibFile);
1532
1696
 
1533
1697
  if (result.missing.length > 0) {
1534
1698
  console.log(chalk.red(` ✗ ${result.missing.length} missing citation(s): ${result.missing.slice(0, 3).join(', ')}${result.missing.length > 3 ? '...' : ''}`));
@@ -1567,7 +1731,7 @@ export function register(program, pkg) {
1567
1731
  .command('open')
1568
1732
  .description('Open project folder or file in default app')
1569
1733
  .argument('[file]', 'File to open (default: project folder)')
1570
- .action(async (file) => {
1734
+ .action(async (file?: string, _options?: OpenOptions) => {
1571
1735
  const { exec } = await import('child_process');
1572
1736
  const target = file || '.';
1573
1737
 
@@ -1577,7 +1741,7 @@ export function register(program, pkg) {
1577
1741
  }
1578
1742
 
1579
1743
  const platform = process.platform;
1580
- let command;
1744
+ let command: string;
1581
1745
 
1582
1746
  if (platform === 'darwin') {
1583
1747
  command = `open "${target}"`;
@@ -1605,6 +1769,10 @@ export function register(program, pkg) {
1605
1769
  .description('Install docrev skill for Claude Code')
1606
1770
  .action(() => {
1607
1771
  const homedir = process.env.HOME || process.env.USERPROFILE;
1772
+ if (!homedir) {
1773
+ console.error(chalk.red('Could not determine home directory'));
1774
+ process.exit(1);
1775
+ }
1608
1776
  const skillDir = path.join(homedir, '.claude', 'skills', 'docrev');
1609
1777
  const sourceDir = path.join(import.meta.dirname, '..', '..', 'skill');
1610
1778
 
@@ -1638,6 +1806,10 @@ export function register(program, pkg) {
1638
1806
  .description('Remove docrev skill from Claude Code')
1639
1807
  .action(() => {
1640
1808
  const homedir = process.env.HOME || process.env.USERPROFILE;
1809
+ if (!homedir) {
1810
+ console.error(chalk.red('Could not determine home directory'));
1811
+ process.exit(1);
1812
+ }
1641
1813
  const skillDir = path.join(homedir, '.claude', 'skills', 'docrev');
1642
1814
 
1643
1815
  if (fs.existsSync(skillDir)) {
@@ -1665,7 +1837,7 @@ export function register(program, pkg) {
1665
1837
  .option('--list-all', 'List all custom words (global + project)')
1666
1838
  .option('--british', 'Use British English dictionary')
1667
1839
  .option('--add-names', 'Add detected names to global dictionary')
1668
- .action(async (files, options) => {
1840
+ .action(async (files: string[] | undefined, options: SpellingOptions) => {
1669
1841
  const spelling = await import('../spelling.js');
1670
1842
 
1671
1843
  // Handle dictionary management
@@ -1766,12 +1938,20 @@ export function register(program, pkg) {
1766
1938
  }
1767
1939
 
1768
1940
  // Check spelling in files
1769
- let filesToCheck = files;
1941
+ let filesToCheck = files || [];
1770
1942
 
1771
1943
  if (filesToCheck.length === 0) {
1772
1944
  if (fs.existsSync('rev.yaml')) {
1773
- const { getSectionFiles } = await import('../sections.js');
1774
- filesToCheck = getSectionFiles('.');
1945
+ try {
1946
+ const config = loadBuildConfig('.');
1947
+ filesToCheck = config?.sections || [];
1948
+ } catch {
1949
+ // Ignore errors
1950
+ }
1951
+ if (filesToCheck.length === 0) {
1952
+ filesToCheck = fs.readdirSync('.')
1953
+ .filter(f => f.endsWith('.md') && !f.startsWith('.'));
1954
+ }
1775
1955
  } else {
1776
1956
  filesToCheck = fs.readdirSync('.')
1777
1957
  .filter(f => f.endsWith('.md') && !f.startsWith('.'));
@@ -1786,7 +1966,7 @@ export function register(program, pkg) {
1786
1966
  const lang = options.british ? 'en-gb' : 'en';
1787
1967
  console.log(fmt.header(`Spelling Check (${options.british ? 'British' : 'US'} English)`));
1788
1968
  let totalMisspelled = 0;
1789
- const allNames = new Set();
1969
+ const allNames = new Set<string>();
1790
1970
 
1791
1971
  for (const file of filesToCheck) {
1792
1972
  if (!fs.existsSync(file)) {
@@ -1794,7 +1974,7 @@ export function register(program, pkg) {
1794
1974
  continue;
1795
1975
  }
1796
1976
 
1797
- const result = await spelling.checkFile(file, { projectDir: '.', lang });
1977
+ const result = await spelling.checkFile(file, { projectDir: '.', lang }) as SpellingResult;
1798
1978
  const { misspelled, possibleNames } = result;
1799
1979
 
1800
1980
  // Collect names
@@ -1853,7 +2033,7 @@ export function register(program, pkg) {
1853
2033
  .command('upgrade')
1854
2034
  .description('Check for updates and upgrade docrev')
1855
2035
  .option('--check', 'Only check for updates, do not install')
1856
- .action(async (options) => {
2036
+ .action(async (options: UpgradeOptions) => {
1857
2037
  const { execSync, spawn } = await import('child_process');
1858
2038
 
1859
2039
  console.log(chalk.cyan('Checking for updates...'));
@@ -1863,7 +2043,7 @@ export function register(program, pkg) {
1863
2043
  const currentVersion = pkg?.version || 'unknown';
1864
2044
 
1865
2045
  // Get latest version from npm
1866
- let latestVersion;
2046
+ let latestVersion: string;
1867
2047
  try {
1868
2048
  latestVersion = execSync('npm view docrev version', { encoding: 'utf-8' }).trim();
1869
2049
  } catch {
@@ -1911,7 +2091,7 @@ export function register(program, pkg) {
1911
2091
  process.exit(1);
1912
2092
  });
1913
2093
  } catch (err) {
1914
- console.error(chalk.red(`Error: ${err.message}`));
2094
+ console.error(chalk.red(`Error: ${(err as Error).message}`));
1915
2095
  process.exit(1);
1916
2096
  }
1917
2097
  });
@@ -1928,7 +2108,7 @@ export function register(program, pkg) {
1928
2108
  .option('--parallel', 'Run operations in parallel')
1929
2109
  .option('--dry-run', 'Preview files without running')
1930
2110
  .option('-a, --all', 'Include all .md files (not just sections)')
1931
- .action(async (command, pattern, options) => {
2111
+ .action(async (command: string, pattern: string | undefined, options: BatchOptions) => {
1932
2112
  const validCommands = ['status', 'strip', 'resolve', 'comments'];
1933
2113
 
1934
2114
  if (!validCommands.includes(command)) {
@@ -1938,7 +2118,7 @@ export function register(program, pkg) {
1938
2118
  }
1939
2119
 
1940
2120
  // Find files
1941
- let files = [];
2121
+ let files: string[] = [];
1942
2122
  if (pattern) {
1943
2123
  if (pattern.includes('*')) {
1944
2124
  files = fs.readdirSync('.').filter(f =>
@@ -1971,7 +2151,7 @@ export function register(program, pkg) {
1971
2151
  }
1972
2152
 
1973
2153
  // Process files
1974
- const results = [];
2154
+ const results: BatchResult[] = [];
1975
2155
  const progressBar = fmt.progressBar(files.length, 'Processing');
1976
2156
  progressBar.update(0);
1977
2157
 
@@ -1986,7 +2166,7 @@ export function register(program, pkg) {
1986
2166
 
1987
2167
  try {
1988
2168
  const text = fs.readFileSync(file, 'utf-8');
1989
- let result = { file, status: 'ok' };
2169
+ let result: BatchResult = { file, status: 'ok' };
1990
2170
 
1991
2171
  switch (command) {
1992
2172
  case 'status': {
@@ -2037,7 +2217,7 @@ export function register(program, pkg) {
2037
2217
 
2038
2218
  results.push(result);
2039
2219
  } catch (err) {
2040
- results.push({ file, status: 'error', error: err.message });
2220
+ results.push({ file, status: 'error', error: (err as Error).message });
2041
2221
  }
2042
2222
  }
2043
2223
 
@@ -2064,7 +2244,7 @@ export function register(program, pkg) {
2064
2244
  details = chalk.dim(`${r.total || 0} total, ${r.pending || 0} pending`);
2065
2245
  break;
2066
2246
  case 'resolve':
2067
- details = r.resolved > 0
2247
+ details = r.resolved && r.resolved > 0
2068
2248
  ? chalk.green(`${r.resolved} resolved`)
2069
2249
  : chalk.dim('no pending');
2070
2250
  break;
@@ -2091,7 +2271,7 @@ export function register(program, pkg) {
2091
2271
 
2092
2272
  // Helper functions for help text
2093
2273
 
2094
- function showFullHelp(pkg) {
2274
+ function showFullHelp(pkg?: PackageJson): void {
2095
2275
  console.log(`
2096
2276
  ${chalk.bold.cyan('rev')} ${chalk.dim(`v${pkg?.version || 'unknown'}`)} - Revision workflow for Word ↔ Markdown round-trips
2097
2277
 
@@ -2124,7 +2304,7 @@ ${chalk.bold('MORE HELP')}
2124
2304
  `);
2125
2305
  }
2126
2306
 
2127
- function showWorkflowHelp() {
2307
+ function showWorkflowHelp(): void {
2128
2308
  console.log(`
2129
2309
  ${chalk.bold.cyan('rev')} ${chalk.dim('- Workflow Guide')}
2130
2310
 
@@ -2178,7 +2358,7 @@ ${chalk.bold('STEP 7: ARCHIVE & REPEAT')}
2178
2358
  `);
2179
2359
  }
2180
2360
 
2181
- function showSyntaxHelp() {
2361
+ function showSyntaxHelp(): void {
2182
2362
  console.log(`
2183
2363
  ${chalk.bold.cyan('rev')} ${chalk.dim('- Annotation Syntax (CriticMarkup)')}
2184
2364
 
@@ -2222,7 +2402,7 @@ ${chalk.bold('COMMENTS')}
2222
2402
  `);
2223
2403
  }
2224
2404
 
2225
- function showCommandsHelp() {
2405
+ function showCommandsHelp(): void {
2226
2406
  console.log(`
2227
2407
  ${chalk.bold.cyan('rev')} ${chalk.dim('- Command Reference')}
2228
2408