docrev 0.11.2 → 0.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/dist/lib/annotations.d.ts.map +1 -1
- package/dist/lib/annotations.js +28 -0
- package/dist/lib/annotations.js.map +1 -1
- package/dist/lib/commands/comments.d.ts.map +1 -1
- package/dist/lib/commands/comments.js +58 -28
- package/dist/lib/commands/comments.js.map +1 -1
- package/dist/lib/commands/context.d.ts +1 -0
- package/dist/lib/commands/context.d.ts.map +1 -1
- package/dist/lib/commands/context.js +1 -0
- package/dist/lib/commands/context.js.map +1 -1
- package/dist/lib/commands/core.d.ts.map +1 -1
- package/dist/lib/commands/core.js +25 -8
- package/dist/lib/commands/core.js.map +1 -1
- package/dist/lib/commands/response.js +1 -1
- package/dist/lib/commands/response.js.map +1 -1
- package/dist/lib/import.d.ts +14 -0
- package/dist/lib/import.d.ts.map +1 -1
- package/dist/lib/import.js +23 -0
- package/dist/lib/import.js.map +1 -1
- package/dist/lib/input.d.ts +67 -0
- package/dist/lib/input.d.ts.map +1 -0
- package/dist/lib/input.js +164 -0
- package/dist/lib/input.js.map +1 -0
- package/dist/lib/response.d.ts +1 -1
- package/dist/lib/response.d.ts.map +1 -1
- package/dist/lib/response.js +5 -2
- package/dist/lib/response.js.map +1 -1
- package/lib/annotations.ts +31 -0
- package/lib/commands/comments.ts +56 -29
- package/lib/commands/context.ts +9 -0
- package/lib/commands/core.ts +28 -9
- package/lib/commands/response.ts +1 -1
- package/lib/import.ts +26 -0
- package/lib/input.ts +174 -0
- package/lib/response.ts +5 -2
- package/package.json +1 -1
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input routing: read a command's `[file]` argument as CriticMarkup Markdown,
|
|
3
|
+
* transparently handling Word documents.
|
|
4
|
+
*
|
|
5
|
+
* `rev status`/`rev comments` and friends were written to `readFileSync(file,
|
|
6
|
+
* 'utf-8')` every argument and regex it for CriticMarkup. A `.docx` is a binary
|
|
7
|
+
* ZIP; decoded as UTF-8 it occasionally yields a stray `{~~..~>..~~}` byte
|
|
8
|
+
* sequence, so the tool reported a small, plausible, wrong count with no error
|
|
9
|
+
* (gcol33/docrev#8). This module is the single front door that both:
|
|
10
|
+
*
|
|
11
|
+
* - detects a Word document by extension AND by ZIP magic + `word/document.xml`
|
|
12
|
+
* (so a mis-extensioned `.docx` renamed to `.md`/`.txt` is still caught), and
|
|
13
|
+
* - converts it to the same annotated Markdown `rev import` produces, so every
|
|
14
|
+
* downstream reader (`countAnnotations`, `getComments`, ...) sees real tags.
|
|
15
|
+
*
|
|
16
|
+
* Commands that only read report through `readAnnotatedInput`; commands that
|
|
17
|
+
* edit in place call `assertEditableMarkdown` first and refuse a `.docx` with a
|
|
18
|
+
* pointer to `rev import`, since CriticMarkup cannot be written back into a
|
|
19
|
+
* binary ZIP.
|
|
20
|
+
*/
|
|
21
|
+
import * as fs from 'fs';
|
|
22
|
+
import * as path from 'path';
|
|
23
|
+
import { openDocx } from './ooxml.js';
|
|
24
|
+
import { exitWithError, requireFile } from './errors.js';
|
|
25
|
+
/** ZIP local-file-header magic: the first four bytes of any `.docx`. */
|
|
26
|
+
const ZIP_MAGIC = Buffer.from([0x50, 0x4b, 0x03, 0x04]);
|
|
27
|
+
/** OOXML Word extensions (all ZIP-backed, none Markdown). */
|
|
28
|
+
const WORD_EXTENSIONS = new Set(['.docx', '.docm', '.dotx', '.dotm']);
|
|
29
|
+
/**
|
|
30
|
+
* A file cannot be read as (or converted to) CriticMarkup Markdown. Carries
|
|
31
|
+
* actionable suggestions so the command layer can render a helpful error.
|
|
32
|
+
*/
|
|
33
|
+
export class InputError extends Error {
|
|
34
|
+
suggestions;
|
|
35
|
+
constructor(message, suggestions = []) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = 'InputError';
|
|
38
|
+
this.suggestions = suggestions;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Read the first bytes of a file, or an empty buffer if unreadable. */
|
|
42
|
+
function readMagic(file, length = 4) {
|
|
43
|
+
let fd;
|
|
44
|
+
try {
|
|
45
|
+
fd = fs.openSync(file, 'r');
|
|
46
|
+
const buf = Buffer.alloc(length);
|
|
47
|
+
const n = fs.readSync(fd, buf, 0, length, 0);
|
|
48
|
+
return buf.subarray(0, n);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return Buffer.alloc(0);
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
if (fd !== undefined)
|
|
55
|
+
fs.closeSync(fd);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** True when the file begins with the ZIP local-file-header magic. */
|
|
59
|
+
export function looksLikeZip(file) {
|
|
60
|
+
return readMagic(file).equals(ZIP_MAGIC);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* True when `file` is a Word document: it carries a Word extension, or it is a
|
|
64
|
+
* ZIP whose package contains `word/document.xml`. The content sniff catches a
|
|
65
|
+
* `.docx` renamed to `.md`/`.txt`, so those never get regexed as CriticMarkup.
|
|
66
|
+
*/
|
|
67
|
+
export function isWordDocument(file) {
|
|
68
|
+
if (WORD_EXTENSIONS.has(path.extname(file).toLowerCase()))
|
|
69
|
+
return true;
|
|
70
|
+
if (!looksLikeZip(file))
|
|
71
|
+
return false;
|
|
72
|
+
try {
|
|
73
|
+
return openDocx(file).getEntry('word/document.xml') !== null;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** A NUL byte in the head reliably marks binary; UTF-8 Markdown never has one. */
|
|
80
|
+
function isBinaryBuffer(buf) {
|
|
81
|
+
if (buf.length >= 4 && buf.subarray(0, 4).equals(ZIP_MAGIC))
|
|
82
|
+
return true;
|
|
83
|
+
return buf.subarray(0, Math.min(buf.length, 8192)).includes(0x00);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Read a file as the CriticMarkup Markdown a command expects. A Word document
|
|
87
|
+
* is converted through the existing OOXML/pandoc reader (real insertions,
|
|
88
|
+
* deletions, substitutions, and comments); a Markdown/text file is read as
|
|
89
|
+
* UTF-8. A non-Word binary (image, PDF, unknown ZIP) is rejected rather than
|
|
90
|
+
* silently miscounted.
|
|
91
|
+
*
|
|
92
|
+
* @throws InputError if the file is a binary that is not a Word document, or a
|
|
93
|
+
* Word document that cannot be parsed.
|
|
94
|
+
*/
|
|
95
|
+
export async function readAnnotatedInput(file) {
|
|
96
|
+
if (isWordDocument(file)) {
|
|
97
|
+
const { readDocxAsAnnotatedMarkdown } = await import('./import.js');
|
|
98
|
+
try {
|
|
99
|
+
return await readDocxAsAnnotatedMarkdown(file);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
throw new InputError(`Failed to read Word document ${path.basename(file)}: ${err.message}`, ['Run "rev import <docx>" to convert it to annotated Markdown first']);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const buf = fs.readFileSync(file);
|
|
106
|
+
if (isBinaryBuffer(buf)) {
|
|
107
|
+
throw new InputError(`${path.basename(file)} is not a text or Markdown file (binary content).`, [
|
|
108
|
+
'Word documents: run "rev import <docx>" first, or pass the imported .md',
|
|
109
|
+
'This command reads CriticMarkup Markdown, not binary files',
|
|
110
|
+
]);
|
|
111
|
+
}
|
|
112
|
+
return buf.toString('utf-8');
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Guard for commands that edit the file in place (accept/reject/resolve/reply/
|
|
116
|
+
* review). A `.docx` cannot hold CriticMarkup, so refuse it with a pointer to
|
|
117
|
+
* `rev import` instead of corrupting the document.
|
|
118
|
+
*
|
|
119
|
+
* @throws InputError if `file` is a Word document.
|
|
120
|
+
*/
|
|
121
|
+
export function assertEditableMarkdown(file) {
|
|
122
|
+
if (isWordDocument(file)) {
|
|
123
|
+
const base = path.basename(file);
|
|
124
|
+
throw new InputError(`${base} is a Word document; this command edits Markdown in place.`, [
|
|
125
|
+
`Run "rev import ${base}" to get an editable Markdown file, then edit that`,
|
|
126
|
+
'Read-only inspection works directly: "rev status" / "rev comments"',
|
|
127
|
+
]);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Command-facing wrappers: translate InputError into the CLI's error surface.
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
/**
|
|
134
|
+
* Command front door: verify the file exists, then read it as annotated
|
|
135
|
+
* Markdown (converting a `.docx` on the way). On a bad input, print a friendly
|
|
136
|
+
* error and exit — matching how the rest of the CLI reports failures.
|
|
137
|
+
*/
|
|
138
|
+
export async function loadAnnotated(file, fileType = 'Markdown file') {
|
|
139
|
+
requireFile(file, fileType);
|
|
140
|
+
try {
|
|
141
|
+
return await readAnnotatedInput(file);
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
if (err instanceof InputError)
|
|
145
|
+
exitWithError(err.message, err.suggestions);
|
|
146
|
+
throw err;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Command front door for editing commands: verify existence and refuse a Word
|
|
151
|
+
* document (which cannot be edited in place), printing guidance and exiting.
|
|
152
|
+
*/
|
|
153
|
+
export function requireEditableMarkdown(file, fileType = 'Markdown file') {
|
|
154
|
+
requireFile(file, fileType);
|
|
155
|
+
try {
|
|
156
|
+
assertEditableMarkdown(file);
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
if (err instanceof InputError)
|
|
160
|
+
exitWithError(err.message, err.suggestions);
|
|
161
|
+
throw err;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.js","sourceRoot":"","sources":["../../lib/input.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/response.d.ts
CHANGED
|
@@ -36,6 +36,6 @@ export declare function generateResponseLetter(comments: CommentWithReplies[], o
|
|
|
36
36
|
/**
|
|
37
37
|
* Collect comments from multiple files
|
|
38
38
|
*/
|
|
39
|
-
export declare function collectComments(files: string[]): CommentWithReplies[]
|
|
39
|
+
export declare function collectComments(files: string[]): Promise<CommentWithReplies[]>;
|
|
40
40
|
export {};
|
|
41
41
|
//# sourceMappingURL=response.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../lib/response.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,UAAU,KAAK;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,kBAAkB;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,KAAK,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,eAAe;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAW,GAAG,kBAAkB,EAAE,CAoC9F;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,kBAAkB,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,kBAAkB,EAAE,CAAC,CAYjG;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,kBAAkB,EAAE,EAAE,OAAO,GAAE,eAAoB,GAAG,MAAM,CAwF5G;AAED;;GAEG;AACH,
|
|
1
|
+
{"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../lib/response.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,UAAU,KAAK;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,kBAAkB;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,KAAK,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,eAAe;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAW,GAAG,kBAAkB,EAAE,CAoC9F;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,kBAAkB,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,kBAAkB,EAAE,CAAC,CAYjG;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,kBAAkB,EAAE,EAAE,OAAO,GAAE,eAAoB,GAAG,MAAM,CAwF5G;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAepF"}
|
package/dist/lib/response.js
CHANGED
|
@@ -136,12 +136,15 @@ export function generateResponseLetter(comments, options = {}) {
|
|
|
136
136
|
/**
|
|
137
137
|
* Collect comments from multiple files
|
|
138
138
|
*/
|
|
139
|
-
export function collectComments(files) {
|
|
139
|
+
export async function collectComments(files) {
|
|
140
|
+
const { readAnnotatedInput } = await import('./input.js');
|
|
140
141
|
const allComments = [];
|
|
141
142
|
for (const file of files) {
|
|
142
143
|
if (!fs.existsSync(file))
|
|
143
144
|
continue;
|
|
144
|
-
|
|
145
|
+
// Route each file through the shared reader so a returned `.docx` is
|
|
146
|
+
// converted to CriticMarkup first, rather than regexing the binary ZIP.
|
|
147
|
+
const text = await readAnnotatedInput(file);
|
|
145
148
|
const comments = parseCommentsWithReplies(text, path.basename(file));
|
|
146
149
|
allComments.push(...comments);
|
|
147
150
|
}
|
package/dist/lib/response.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response.js","sourceRoot":"","sources":["../../lib/response.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"response.js","sourceRoot":"","sources":["../../lib/response.ts"],"names":[],"mappings":"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"}
|
package/lib/annotations.ts
CHANGED
|
@@ -124,6 +124,34 @@ function isCommentFalsePositive(commentContent: string, fullText: string, positi
|
|
|
124
124
|
// Combined pattern for any track change (not comments)
|
|
125
125
|
const TRACK_CHANGE_PATTERN = /(\{\+\+.+?\+\+\}|\{--.+?--\}|\{~~.+?~>.+?~~\})/gs;
|
|
126
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Reject binary content before it reaches the CriticMarkup regexes. A `.docx`
|
|
129
|
+
* is a ZIP; read as UTF-8 it starts with `PK\x03\x04` and is riddled with NUL
|
|
130
|
+
* bytes from the DEFLATE stream. Running the annotation patterns over that
|
|
131
|
+
* yields accidental byte-matches that look like a small, plausible count —
|
|
132
|
+
* silent garbage. This guard turns that whole failure class into a loud error.
|
|
133
|
+
* Callers that legitimately handle Word documents route them through
|
|
134
|
+
* `readAnnotatedInput` (lib/input.ts), which converts the docx to CriticMarkup
|
|
135
|
+
* first, so this only fires on a raw binary that reached a text-only path.
|
|
136
|
+
*
|
|
137
|
+
* UTF-8 Markdown never contains a NUL byte, so the check cannot false-positive
|
|
138
|
+
* on real input; it scans only the head to stay O(1) on large documents.
|
|
139
|
+
* @throws Error if the text is binary rather than text/Markdown.
|
|
140
|
+
*/
|
|
141
|
+
function assertNotBinary(text: string): void {
|
|
142
|
+
// ZIP local-file-header magic "PK\x03\x04" (docx/xlsx/pptx read as text),
|
|
143
|
+
// or any NUL byte in the head: a reliable binary marker absent from UTF-8
|
|
144
|
+
// Markdown, so this never false-positives on real input. Scans only the
|
|
145
|
+
// head to stay cheap on large documents.
|
|
146
|
+
if (text.startsWith('PK\u0003\u0004') || text.slice(0, 8192).includes('\u0000')) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
'Expected text/Markdown but received binary content. If this is a Word ' +
|
|
149
|
+
'document, convert it first with "rev import <docx>" (rev status/comments ' +
|
|
150
|
+
'read .docx directly; other commands need the imported Markdown).',
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
127
155
|
// =============================================================================
|
|
128
156
|
// Public API
|
|
129
157
|
// =============================================================================
|
|
@@ -138,6 +166,7 @@ export function parseAnnotations(text: string): Annotation[] {
|
|
|
138
166
|
if (typeof text !== 'string') {
|
|
139
167
|
throw new TypeError(`text must be a string, got ${typeof text}`);
|
|
140
168
|
}
|
|
169
|
+
assertNotBinary(text);
|
|
141
170
|
|
|
142
171
|
const annotations: Annotation[] = [];
|
|
143
172
|
|
|
@@ -257,6 +286,7 @@ export function stripAnnotations(text: string, options: StripOptions = {}): stri
|
|
|
257
286
|
if (typeof text !== 'string') {
|
|
258
287
|
throw new TypeError(`text must be a string, got ${typeof text}`);
|
|
259
288
|
}
|
|
289
|
+
assertNotBinary(text);
|
|
260
290
|
|
|
261
291
|
const { keepComments = false } = options;
|
|
262
292
|
|
|
@@ -427,6 +457,7 @@ export function hasAnnotations(text: string): boolean {
|
|
|
427
457
|
if (typeof text !== 'string') {
|
|
428
458
|
throw new TypeError(`text must be a string, got ${typeof text}`);
|
|
429
459
|
}
|
|
460
|
+
assertNotBinary(text);
|
|
430
461
|
|
|
431
462
|
return ANNOTATION_TESTERS.some((re) => re.test(text));
|
|
432
463
|
}
|
package/lib/commands/comments.ts
CHANGED
|
@@ -23,10 +23,31 @@ import {
|
|
|
23
23
|
getUserName,
|
|
24
24
|
exitWithError,
|
|
25
25
|
getAnnotationSuggestions,
|
|
26
|
-
|
|
26
|
+
loadAnnotated,
|
|
27
|
+
readAnnotatedInput,
|
|
28
|
+
requireEditableMarkdown,
|
|
29
|
+
InputError,
|
|
27
30
|
} from './context.js';
|
|
28
31
|
import type { Comment, Annotation } from '../types.js';
|
|
29
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Read one file for the multi-file navigation commands (next/prev/first/last/
|
|
35
|
+
* todo). Converts a `.docx` to CriticMarkup like every other reader; on an
|
|
36
|
+
* unreadable/binary file it warns and returns null so a scan over many files
|
|
37
|
+
* is not aborted by one bad entry (and never silently under-counts).
|
|
38
|
+
*/
|
|
39
|
+
async function readCommentSource(f: string): Promise<string | null> {
|
|
40
|
+
try {
|
|
41
|
+
return await readAnnotatedInput(f);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
if (err instanceof InputError) {
|
|
44
|
+
console.error(chalk.yellow(` Skipping ${f}: ${err.message}`));
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
30
51
|
/**
|
|
31
52
|
* Add a reply after a comment
|
|
32
53
|
* @param text - Full document text
|
|
@@ -170,9 +191,15 @@ export function register(program: Command): void {
|
|
|
170
191
|
.option('-i, --interactive', 'Interactive review mode (reply, resolve, skip)')
|
|
171
192
|
.option('-t, --tui', 'Visual TUI mode for comment review')
|
|
172
193
|
.action(async (file: string, options: CommentsOptions) => {
|
|
173
|
-
|
|
194
|
+
// Interactive and TUI modes write replies/resolutions back to the file,
|
|
195
|
+
// so they need editable Markdown; a .docx is refused with a pointer to
|
|
196
|
+
// `rev import`. Plain listing/export is read-only and reads a .docx
|
|
197
|
+
// directly (converted to CriticMarkup first).
|
|
198
|
+
if (options.interactive || options.tui) {
|
|
199
|
+
requireEditableMarkdown(file, 'Markdown file');
|
|
200
|
+
}
|
|
174
201
|
|
|
175
|
-
const text =
|
|
202
|
+
const text = await loadAnnotated(file, 'Markdown file');
|
|
176
203
|
|
|
177
204
|
// TUI review mode
|
|
178
205
|
if (options.tui) {
|
|
@@ -314,7 +341,8 @@ export function register(program: Command): void {
|
|
|
314
341
|
.option('-u, --unresolve', 'Mark as pending (unresolve)')
|
|
315
342
|
.option('--dry-run', 'Preview without saving')
|
|
316
343
|
.action((file: string, options: ResolveOptions) => {
|
|
317
|
-
|
|
344
|
+
// Writes resolved markers back to the file; refuse a .docx (see review).
|
|
345
|
+
requireEditableMarkdown(file, 'Markdown file');
|
|
318
346
|
|
|
319
347
|
let text = fs.readFileSync(file, 'utf-8');
|
|
320
348
|
const comments = getComments(text);
|
|
@@ -395,7 +423,7 @@ export function register(program: Command): void {
|
|
|
395
423
|
.description('Show next pending comment')
|
|
396
424
|
.argument('[file]', 'Specific file (default: all markdown files)')
|
|
397
425
|
.option('-n, --number <n>', 'Skip to nth pending comment', parseInt)
|
|
398
|
-
.action((file: string | undefined, options: NextOptions) => {
|
|
426
|
+
.action(async (file: string | undefined, options: NextOptions) => {
|
|
399
427
|
const files = file ? [file] : findFiles('.md');
|
|
400
428
|
|
|
401
429
|
if (files.length === 0) {
|
|
@@ -407,7 +435,8 @@ export function register(program: Command): void {
|
|
|
407
435
|
const allPending: Array<Annotation & { file: string; number: number }> = [];
|
|
408
436
|
for (const f of files) {
|
|
409
437
|
if (!fs.existsSync(f)) continue;
|
|
410
|
-
const text =
|
|
438
|
+
const text = await readCommentSource(f);
|
|
439
|
+
if (text === null) continue;
|
|
411
440
|
const allComments = getComments(text);
|
|
412
441
|
const pending = getComments(text, { pendingOnly: true });
|
|
413
442
|
|
|
@@ -460,7 +489,7 @@ export function register(program: Command): void {
|
|
|
460
489
|
.description('Show previous pending comment')
|
|
461
490
|
.argument('[file]', 'Specific file (default: all markdown files)')
|
|
462
491
|
.option('-n, --number <n>', 'Skip to nth pending comment from end', parseInt)
|
|
463
|
-
.action((file: string | undefined, options: PrevOptions) => {
|
|
492
|
+
.action(async (file: string | undefined, options: PrevOptions) => {
|
|
464
493
|
const files = file ? [file] : findFiles('.md');
|
|
465
494
|
|
|
466
495
|
if (files.length === 0) {
|
|
@@ -472,7 +501,8 @@ export function register(program: Command): void {
|
|
|
472
501
|
const allPending: Array<Annotation & { file: string; number: number }> = [];
|
|
473
502
|
for (const f of files) {
|
|
474
503
|
if (!fs.existsSync(f)) continue;
|
|
475
|
-
const text =
|
|
504
|
+
const text = await readCommentSource(f);
|
|
505
|
+
if (text === null) continue;
|
|
476
506
|
const allComments = getComments(text);
|
|
477
507
|
const pending = getComments(text, { pendingOnly: true });
|
|
478
508
|
|
|
@@ -527,7 +557,7 @@ export function register(program: Command): void {
|
|
|
527
557
|
.command('first')
|
|
528
558
|
.description('Show first comment')
|
|
529
559
|
.argument('[section]', 'Specific file or section name (default: all markdown files)')
|
|
530
|
-
.action((section: string | undefined) => {
|
|
560
|
+
.action(async (section: string | undefined) => {
|
|
531
561
|
const files = section ? findSectionFile(section) : findFiles('.md');
|
|
532
562
|
|
|
533
563
|
if (files.length === 0) {
|
|
@@ -538,7 +568,8 @@ export function register(program: Command): void {
|
|
|
538
568
|
// Find first comment across files
|
|
539
569
|
for (const f of files) {
|
|
540
570
|
if (!fs.existsSync(f)) continue;
|
|
541
|
-
const text =
|
|
571
|
+
const text = await readCommentSource(f);
|
|
572
|
+
if (text === null) continue;
|
|
542
573
|
const comments = getComments(text);
|
|
543
574
|
|
|
544
575
|
if (comments.length > 0) {
|
|
@@ -573,7 +604,7 @@ export function register(program: Command): void {
|
|
|
573
604
|
.command('last')
|
|
574
605
|
.description('Show last comment')
|
|
575
606
|
.argument('[section]', 'Specific file or section name (default: all markdown files)')
|
|
576
|
-
.action((section: string | undefined) => {
|
|
607
|
+
.action(async (section: string | undefined) => {
|
|
577
608
|
const files = section ? findSectionFile(section) : findFiles('.md').reverse();
|
|
578
609
|
|
|
579
610
|
if (files.length === 0) {
|
|
@@ -584,7 +615,8 @@ export function register(program: Command): void {
|
|
|
584
615
|
// Find last comment across files (reverse order)
|
|
585
616
|
for (const f of files) {
|
|
586
617
|
if (!fs.existsSync(f)) continue;
|
|
587
|
-
const text =
|
|
618
|
+
const text = await readCommentSource(f);
|
|
619
|
+
if (text === null) continue;
|
|
588
620
|
const comments = getComments(text);
|
|
589
621
|
|
|
590
622
|
if (comments.length > 0) {
|
|
@@ -622,7 +654,7 @@ export function register(program: Command): void {
|
|
|
622
654
|
.description('List all pending comments as a checklist')
|
|
623
655
|
.argument('[file]', 'Specific file (default: all markdown files)')
|
|
624
656
|
.option('--by-author', 'Group by author')
|
|
625
|
-
.action((file: string | undefined, options: { byAuthor?: boolean }) => {
|
|
657
|
+
.action(async (file: string | undefined, options: { byAuthor?: boolean }) => {
|
|
626
658
|
const files = file ? [file] : findFiles('.md');
|
|
627
659
|
|
|
628
660
|
if (files.length === 0) {
|
|
@@ -640,7 +672,8 @@ export function register(program: Command): void {
|
|
|
640
672
|
}> = [];
|
|
641
673
|
for (const f of files) {
|
|
642
674
|
if (!fs.existsSync(f)) continue;
|
|
643
|
-
const text =
|
|
675
|
+
const text = await readCommentSource(f);
|
|
676
|
+
if (text === null) continue;
|
|
644
677
|
const allComments = getComments(text);
|
|
645
678
|
const pending = allComments.filter(c => !c.resolved);
|
|
646
679
|
|
|
@@ -711,10 +744,8 @@ export function register(program: Command): void {
|
|
|
711
744
|
.option('-a, --all', 'Accept all changes')
|
|
712
745
|
.option('--dry-run', 'Preview without saving')
|
|
713
746
|
.action((file: string, options: AcceptOptions) => {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
process.exit(1);
|
|
717
|
-
}
|
|
747
|
+
// Writes accepted text back to the file; refuse a .docx (see review).
|
|
748
|
+
requireEditableMarkdown(file, 'Markdown file');
|
|
718
749
|
|
|
719
750
|
let text = fs.readFileSync(file, 'utf-8');
|
|
720
751
|
const changes = getTrackChanges(text);
|
|
@@ -795,10 +826,8 @@ export function register(program: Command): void {
|
|
|
795
826
|
.option('-a, --all', 'Reject all changes')
|
|
796
827
|
.option('--dry-run', 'Preview without saving')
|
|
797
828
|
.action((file: string, options: RejectOptions) => {
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
process.exit(1);
|
|
801
|
-
}
|
|
829
|
+
// Writes reverted text back to the file; refuse a .docx (see review).
|
|
830
|
+
requireEditableMarkdown(file, 'Markdown file');
|
|
802
831
|
|
|
803
832
|
let text = fs.readFileSync(file, 'utf-8');
|
|
804
833
|
const changes = getTrackChanges(text);
|
|
@@ -878,10 +907,8 @@ export function register(program: Command): void {
|
|
|
878
907
|
.option('--all', 'Reply to all pending comments with the same message (requires -m)')
|
|
879
908
|
.option('--dry-run', 'Preview without saving')
|
|
880
909
|
.action(async (file: string, options: ReplyOptions) => {
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
process.exit(1);
|
|
884
|
-
}
|
|
910
|
+
// Writes replies back to the file; refuse a .docx (see review).
|
|
911
|
+
requireEditableMarkdown(file, 'Markdown file');
|
|
885
912
|
|
|
886
913
|
// Get author name
|
|
887
914
|
let author = options.author || getUserName();
|
|
@@ -1001,9 +1028,9 @@ export function register(program: Command): void {
|
|
|
1001
1028
|
.option('--no-context', 'Exclude commented text context')
|
|
1002
1029
|
.option('--force', 'Overwrite existing output file')
|
|
1003
1030
|
.action(async (file: string, options: ReplyDocOptions) => {
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
const text =
|
|
1031
|
+
// Read-only over the input; the reply template is written to a new file,
|
|
1032
|
+
// so a .docx is read directly (converted to CriticMarkup first).
|
|
1033
|
+
const text = await loadAnnotated(file, 'Markdown file');
|
|
1007
1034
|
const comments = getComments(text);
|
|
1008
1035
|
|
|
1009
1036
|
if (comments.length === 0) {
|
package/lib/commands/context.ts
CHANGED
|
@@ -160,6 +160,15 @@ export {
|
|
|
160
160
|
requireFile,
|
|
161
161
|
} from '../errors.js';
|
|
162
162
|
|
|
163
|
+
export {
|
|
164
|
+
isWordDocument,
|
|
165
|
+
readAnnotatedInput,
|
|
166
|
+
assertEditableMarkdown,
|
|
167
|
+
requireEditableMarkdown,
|
|
168
|
+
loadAnnotated,
|
|
169
|
+
InputError,
|
|
170
|
+
} from '../input.js';
|
|
171
|
+
|
|
163
172
|
export {
|
|
164
173
|
listJournals,
|
|
165
174
|
getJournalProfile,
|
package/lib/commands/core.ts
CHANGED
|
@@ -22,7 +22,10 @@ import {
|
|
|
22
22
|
interactiveReview,
|
|
23
23
|
exitWithError,
|
|
24
24
|
getFileNotFoundSuggestions,
|
|
25
|
-
|
|
25
|
+
loadAnnotated,
|
|
26
|
+
readAnnotatedInput,
|
|
27
|
+
requireEditableMarkdown,
|
|
28
|
+
InputError,
|
|
26
29
|
} from './context.js';
|
|
27
30
|
|
|
28
31
|
interface StripOptions {
|
|
@@ -43,7 +46,9 @@ export function register(program: Command): void {
|
|
|
43
46
|
.description('Interactively review and accept/reject track changes')
|
|
44
47
|
.argument('<file>', 'Markdown file to review')
|
|
45
48
|
.action(async (file: string) => {
|
|
46
|
-
|
|
49
|
+
// Review writes accepted/rejected text back to the same path; a .docx
|
|
50
|
+
// cannot hold CriticMarkup, so refuse it with a pointer to `rev import`.
|
|
51
|
+
requireEditableMarkdown(file, 'Markdown file');
|
|
47
52
|
|
|
48
53
|
const text = fs.readFileSync(file, 'utf-8');
|
|
49
54
|
const result = await interactiveReview(text);
|
|
@@ -78,10 +83,10 @@ export function register(program: Command): void {
|
|
|
78
83
|
.argument('<file>', 'Markdown file to strip')
|
|
79
84
|
.option('-o, --output <file>', 'Output file (default: stdout)')
|
|
80
85
|
.option('-c, --keep-comments', 'Keep comment annotations')
|
|
81
|
-
.action((file: string, options: StripOptions) => {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const text =
|
|
86
|
+
.action(async (file: string, options: StripOptions) => {
|
|
87
|
+
// Reads the input (a .docx is converted to CriticMarkup first) and writes
|
|
88
|
+
// clean text to stdout or --output, never back to the input file.
|
|
89
|
+
const text = await loadAnnotated(file, 'Markdown file');
|
|
85
90
|
const clean = stripAnnotations(text, { keepComments: options.keepComments });
|
|
86
91
|
|
|
87
92
|
if (options.output) {
|
|
@@ -107,12 +112,26 @@ export function register(program: Command): void {
|
|
|
107
112
|
if (!fs.existsSync(file)) {
|
|
108
113
|
if (jsonMode) {
|
|
109
114
|
jsonOutput({ error: `File not found: ${file}` });
|
|
110
|
-
|
|
111
|
-
exitWithError(`File not found: ${file}`, getFileNotFoundSuggestions(file));
|
|
115
|
+
return;
|
|
112
116
|
}
|
|
117
|
+
exitWithError(`File not found: ${file}`, getFileNotFoundSuggestions(file));
|
|
113
118
|
}
|
|
114
119
|
|
|
115
|
-
|
|
120
|
+
// Route .docx through the OOXML reader so real track changes and
|
|
121
|
+
// comments are counted, instead of regexing the binary ZIP (#8).
|
|
122
|
+
let text: string;
|
|
123
|
+
try {
|
|
124
|
+
text = await readAnnotatedInput(file);
|
|
125
|
+
} catch (err) {
|
|
126
|
+
if (err instanceof InputError) {
|
|
127
|
+
if (jsonMode) {
|
|
128
|
+
jsonOutput({ error: err.message });
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
exitWithError(err.message, err.suggestions);
|
|
132
|
+
}
|
|
133
|
+
throw err;
|
|
134
|
+
}
|
|
116
135
|
const counts = countAnnotations(text);
|
|
117
136
|
const comments = getComments(text);
|
|
118
137
|
|
package/lib/commands/response.ts
CHANGED
|
@@ -75,7 +75,7 @@ export function register(program: Command): void {
|
|
|
75
75
|
|
|
76
76
|
const spin = fmt.spinner('Collecting comments...').start();
|
|
77
77
|
|
|
78
|
-
const comments = collectComments(mdFiles);
|
|
78
|
+
const comments = await collectComments(mdFiles);
|
|
79
79
|
spin.stop();
|
|
80
80
|
|
|
81
81
|
if (comments.length === 0) {
|
package/lib/import.ts
CHANGED
|
@@ -722,6 +722,32 @@ export async function importWordWithTrackChanges(
|
|
|
722
722
|
};
|
|
723
723
|
}
|
|
724
724
|
|
|
725
|
+
/**
|
|
726
|
+
* Read a `.docx` into the same CriticMarkup-annotated Markdown that
|
|
727
|
+
* `rev import` produces, for read-only inspection (`rev status`, `rev
|
|
728
|
+
* comments`, ...). Track changes become `{++..++}` / `{--..--}` / `{~~..~~}`
|
|
729
|
+
* and comments are placed at their anchors, so `countAnnotations` /
|
|
730
|
+
* `getComments` and the rest operate on it exactly as they do on imported
|
|
731
|
+
* Markdown — the numbers match a subsequent `rev import`.
|
|
732
|
+
*
|
|
733
|
+
* Side-effect free: no media is extracted, nothing is written to disk, and
|
|
734
|
+
* extraction/placement messages are discarded (the caller only wants the
|
|
735
|
+
* annotated text). Works with or without pandoc — the XML fallback emits the
|
|
736
|
+
* same CriticMarkup — so it never reads the binary ZIP as text.
|
|
737
|
+
*/
|
|
738
|
+
export async function readDocxAsAnnotatedMarkdown(docxPath: string): Promise<string> {
|
|
739
|
+
const extracted = await extractFromWord(docxPath, { skipMediaExtraction: true });
|
|
740
|
+
let text = extracted.text;
|
|
741
|
+
const comments = extracted.comments || [];
|
|
742
|
+
const anchors = extracted.anchors || new Map();
|
|
743
|
+
|
|
744
|
+
if (comments.length > 0) {
|
|
745
|
+
text = insertCommentsIntoMarkdown(text, comments, anchors, { quiet: true });
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
return cleanupAnnotations(text);
|
|
749
|
+
}
|
|
750
|
+
|
|
725
751
|
/**
|
|
726
752
|
* Legacy import function: Word doc → annotated MD via diff
|
|
727
753
|
*/
|