docrev 0.8.1 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +9 -0
- package/PLAN-tables-and-postprocess.md +850 -0
- package/README.md +33 -0
- package/bin/rev.js +12 -131
- package/bin/rev.ts +145 -0
- package/dist/bin/rev.d.ts +9 -0
- package/dist/bin/rev.d.ts.map +1 -0
- package/dist/bin/rev.js +118 -0
- package/dist/bin/rev.js.map +1 -0
- package/dist/lib/annotations.d.ts +91 -0
- package/dist/lib/annotations.d.ts.map +1 -0
- package/dist/lib/annotations.js +554 -0
- package/dist/lib/annotations.js.map +1 -0
- package/dist/lib/build.d.ts +171 -0
- package/dist/lib/build.d.ts.map +1 -0
- package/dist/lib/build.js +755 -0
- package/dist/lib/build.js.map +1 -0
- package/dist/lib/citations.d.ts +34 -0
- package/dist/lib/citations.d.ts.map +1 -0
- package/dist/lib/citations.js +140 -0
- package/dist/lib/citations.js.map +1 -0
- package/dist/lib/commands/build.d.ts +13 -0
- package/dist/lib/commands/build.d.ts.map +1 -0
- package/dist/lib/commands/build.js +678 -0
- package/dist/lib/commands/build.js.map +1 -0
- package/dist/lib/commands/citations.d.ts +11 -0
- package/dist/lib/commands/citations.d.ts.map +1 -0
- package/dist/lib/commands/citations.js +428 -0
- package/dist/lib/commands/citations.js.map +1 -0
- package/dist/lib/commands/comments.d.ts +11 -0
- package/dist/lib/commands/comments.d.ts.map +1 -0
- package/dist/lib/commands/comments.js +883 -0
- package/dist/lib/commands/comments.js.map +1 -0
- package/dist/lib/commands/context.d.ts +35 -0
- package/dist/lib/commands/context.d.ts.map +1 -0
- package/dist/lib/commands/context.js +59 -0
- package/dist/lib/commands/context.js.map +1 -0
- package/dist/lib/commands/core.d.ts +11 -0
- package/dist/lib/commands/core.d.ts.map +1 -0
- package/dist/lib/commands/core.js +246 -0
- package/dist/lib/commands/core.js.map +1 -0
- package/dist/lib/commands/doi.d.ts +11 -0
- package/dist/lib/commands/doi.d.ts.map +1 -0
- package/dist/lib/commands/doi.js +373 -0
- package/dist/lib/commands/doi.js.map +1 -0
- package/dist/lib/commands/history.d.ts +11 -0
- package/dist/lib/commands/history.d.ts.map +1 -0
- package/dist/lib/commands/history.js +245 -0
- package/dist/lib/commands/history.js.map +1 -0
- package/dist/lib/commands/index.d.ts +28 -0
- package/dist/lib/commands/index.d.ts.map +1 -0
- package/dist/lib/commands/index.js +35 -0
- package/dist/lib/commands/index.js.map +1 -0
- package/dist/lib/commands/init.d.ts +11 -0
- package/dist/lib/commands/init.d.ts.map +1 -0
- package/dist/lib/commands/init.js +209 -0
- package/dist/lib/commands/init.js.map +1 -0
- package/dist/lib/commands/response.d.ts +11 -0
- package/dist/lib/commands/response.d.ts.map +1 -0
- package/dist/lib/commands/response.js +317 -0
- package/dist/lib/commands/response.js.map +1 -0
- package/dist/lib/commands/sections.d.ts +11 -0
- package/dist/lib/commands/sections.d.ts.map +1 -0
- package/dist/lib/commands/sections.js +1071 -0
- package/dist/lib/commands/sections.js.map +1 -0
- package/dist/lib/commands/utilities.d.ts +19 -0
- package/dist/lib/commands/utilities.d.ts.map +1 -0
- package/dist/lib/commands/utilities.js +2009 -0
- package/dist/lib/commands/utilities.js.map +1 -0
- package/dist/lib/comment-realign.d.ts +50 -0
- package/dist/lib/comment-realign.d.ts.map +1 -0
- package/dist/lib/comment-realign.js +372 -0
- package/dist/lib/comment-realign.js.map +1 -0
- package/dist/lib/config.d.ts +41 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +76 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/crossref.d.ts +108 -0
- package/dist/lib/crossref.d.ts.map +1 -0
- package/dist/lib/crossref.js +597 -0
- package/dist/lib/crossref.js.map +1 -0
- package/dist/lib/dependencies.d.ts +30 -0
- package/dist/lib/dependencies.d.ts.map +1 -0
- package/dist/lib/dependencies.js +95 -0
- package/dist/lib/dependencies.js.map +1 -0
- package/dist/lib/doi-cache.d.ts +29 -0
- package/dist/lib/doi-cache.d.ts.map +1 -0
- package/dist/lib/doi-cache.js +104 -0
- package/dist/lib/doi-cache.js.map +1 -0
- package/dist/lib/doi.d.ts +65 -0
- package/dist/lib/doi.d.ts.map +1 -0
- package/dist/lib/doi.js +710 -0
- package/dist/lib/doi.js.map +1 -0
- package/dist/lib/equations.d.ts +61 -0
- package/dist/lib/equations.d.ts.map +1 -0
- package/dist/lib/equations.js +445 -0
- package/dist/lib/equations.js.map +1 -0
- package/dist/lib/errors.d.ts +60 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +303 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/format.d.ts +104 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/format.js +416 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/lib/git.d.ts +88 -0
- package/dist/lib/git.d.ts.map +1 -0
- package/dist/lib/git.js +304 -0
- package/dist/lib/git.js.map +1 -0
- package/dist/lib/grammar.d.ts +62 -0
- package/dist/lib/grammar.d.ts.map +1 -0
- package/dist/lib/grammar.js +244 -0
- package/dist/lib/grammar.js.map +1 -0
- package/dist/lib/image-registry.d.ts +68 -0
- package/dist/lib/image-registry.d.ts.map +1 -0
- package/dist/lib/image-registry.js +112 -0
- package/dist/lib/image-registry.js.map +1 -0
- package/dist/lib/import.d.ts +184 -0
- package/dist/lib/import.d.ts.map +1 -0
- package/dist/lib/import.js +1581 -0
- package/dist/lib/import.js.map +1 -0
- package/dist/lib/journals.d.ts +55 -0
- package/dist/lib/journals.d.ts.map +1 -0
- package/dist/lib/journals.js +417 -0
- package/dist/lib/journals.js.map +1 -0
- package/dist/lib/merge.d.ts +138 -0
- package/dist/lib/merge.d.ts.map +1 -0
- package/dist/lib/merge.js +603 -0
- package/dist/lib/merge.js.map +1 -0
- package/dist/lib/orcid.d.ts +36 -0
- package/dist/lib/orcid.d.ts.map +1 -0
- package/dist/lib/orcid.js +117 -0
- package/dist/lib/orcid.js.map +1 -0
- package/dist/lib/pdf-comments.d.ts +95 -0
- package/dist/lib/pdf-comments.d.ts.map +1 -0
- package/dist/lib/pdf-comments.js +192 -0
- package/dist/lib/pdf-comments.js.map +1 -0
- package/dist/lib/pdf-import.d.ts +118 -0
- package/dist/lib/pdf-import.d.ts.map +1 -0
- package/dist/lib/pdf-import.js +397 -0
- package/dist/lib/pdf-import.js.map +1 -0
- package/dist/lib/plugins.d.ts +76 -0
- package/dist/lib/plugins.d.ts.map +1 -0
- package/dist/lib/plugins.js +235 -0
- package/dist/lib/plugins.js.map +1 -0
- package/dist/lib/postprocess.d.ts +42 -0
- package/dist/lib/postprocess.d.ts.map +1 -0
- package/dist/lib/postprocess.js +138 -0
- package/dist/lib/postprocess.js.map +1 -0
- package/dist/lib/pptx-template.d.ts +59 -0
- package/dist/lib/pptx-template.d.ts.map +1 -0
- package/dist/lib/pptx-template.js +613 -0
- package/dist/lib/pptx-template.js.map +1 -0
- package/dist/lib/pptx-themes.d.ts +80 -0
- package/dist/lib/pptx-themes.d.ts.map +1 -0
- package/dist/lib/pptx-themes.js +818 -0
- package/dist/lib/pptx-themes.js.map +1 -0
- package/dist/lib/protect-restore.d.ts +137 -0
- package/dist/lib/protect-restore.d.ts.map +1 -0
- package/dist/lib/protect-restore.js +394 -0
- package/dist/lib/protect-restore.js.map +1 -0
- package/dist/lib/rate-limiter.d.ts +27 -0
- package/dist/lib/rate-limiter.d.ts.map +1 -0
- package/dist/lib/rate-limiter.js +79 -0
- package/dist/lib/rate-limiter.js.map +1 -0
- package/dist/lib/response.d.ts +41 -0
- package/dist/lib/response.d.ts.map +1 -0
- package/dist/lib/response.js +150 -0
- package/dist/lib/response.js.map +1 -0
- package/dist/lib/review.d.ts +35 -0
- package/dist/lib/review.d.ts.map +1 -0
- package/dist/lib/review.js +263 -0
- package/dist/lib/review.js.map +1 -0
- package/dist/lib/schema.d.ts +66 -0
- package/dist/lib/schema.d.ts.map +1 -0
- package/dist/lib/schema.js +339 -0
- package/dist/lib/schema.js.map +1 -0
- package/dist/lib/scientific-words.d.ts +6 -0
- package/dist/lib/scientific-words.d.ts.map +1 -0
- package/dist/lib/scientific-words.js +66 -0
- package/dist/lib/scientific-words.js.map +1 -0
- package/dist/lib/sections.d.ts +40 -0
- package/dist/lib/sections.d.ts.map +1 -0
- package/dist/lib/sections.js +288 -0
- package/dist/lib/sections.js.map +1 -0
- package/dist/lib/slides.d.ts +86 -0
- package/dist/lib/slides.d.ts.map +1 -0
- package/dist/lib/slides.js +676 -0
- package/dist/lib/slides.js.map +1 -0
- package/dist/lib/spelling.d.ts +76 -0
- package/dist/lib/spelling.d.ts.map +1 -0
- package/dist/lib/spelling.js +272 -0
- package/dist/lib/spelling.js.map +1 -0
- package/dist/lib/templates.d.ts +30 -0
- package/dist/lib/templates.d.ts.map +1 -0
- package/dist/lib/templates.js +504 -0
- package/dist/lib/templates.js.map +1 -0
- package/dist/lib/themes.d.ts +85 -0
- package/dist/lib/themes.d.ts.map +1 -0
- package/dist/lib/themes.js +652 -0
- package/dist/lib/themes.js.map +1 -0
- package/dist/lib/trackchanges.d.ts +51 -0
- package/dist/lib/trackchanges.d.ts.map +1 -0
- package/dist/lib/trackchanges.js +202 -0
- package/dist/lib/trackchanges.js.map +1 -0
- package/dist/lib/tui.d.ts +76 -0
- package/dist/lib/tui.d.ts.map +1 -0
- package/dist/lib/tui.js +377 -0
- package/dist/lib/tui.js.map +1 -0
- package/dist/lib/types.d.ts +447 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +6 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/undo.d.ts +57 -0
- package/dist/lib/undo.d.ts.map +1 -0
- package/dist/lib/undo.js +185 -0
- package/dist/lib/undo.js.map +1 -0
- package/dist/lib/utils.d.ts +16 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +40 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/lib/variables.d.ts +42 -0
- package/dist/lib/variables.d.ts.map +1 -0
- package/dist/lib/variables.js +141 -0
- package/dist/lib/variables.js.map +1 -0
- package/dist/lib/word.d.ts +80 -0
- package/dist/lib/word.d.ts.map +1 -0
- package/dist/lib/word.js +360 -0
- package/dist/lib/word.js.map +1 -0
- package/dist/lib/wordcomments.d.ts +51 -0
- package/dist/lib/wordcomments.d.ts.map +1 -0
- package/dist/lib/wordcomments.js +587 -0
- package/dist/lib/wordcomments.js.map +1 -0
- package/eslint.config.js +27 -0
- package/lib/annotations.ts +622 -0
- package/lib/apply-buildup-colors.py +88 -0
- package/lib/build.ts +1013 -0
- package/lib/{citations.js → citations.ts} +38 -27
- package/lib/commands/{build.js → build.ts} +80 -27
- package/lib/commands/{citations.js → citations.ts} +36 -18
- package/lib/commands/{comments.js → comments.ts} +187 -54
- package/lib/commands/{context.js → context.ts} +18 -8
- package/lib/commands/{core.js → core.ts} +34 -20
- package/lib/commands/{doi.js → doi.ts} +32 -16
- package/lib/commands/{history.js → history.ts} +25 -12
- package/lib/commands/{index.js → index.ts} +9 -5
- package/lib/commands/{init.js → init.ts} +20 -8
- package/lib/commands/{response.js → response.ts} +47 -20
- package/lib/commands/{sections.js → sections.ts} +273 -68
- package/lib/commands/{utilities.js → utilities.ts} +338 -158
- package/lib/{comment-realign.js → comment-realign.ts} +117 -45
- package/lib/config.ts +84 -0
- package/lib/{crossref.js → crossref.ts} +213 -138
- package/lib/dependencies.ts +106 -0
- package/lib/doi-cache.ts +115 -0
- package/lib/{doi.js → doi.ts} +115 -281
- package/lib/{equations.js → equations.ts} +60 -64
- package/lib/{errors.js → errors.ts} +56 -48
- package/lib/{format.js → format.ts} +137 -63
- package/lib/{git.js → git.ts} +66 -63
- package/lib/{grammar.js → grammar.ts} +45 -32
- package/lib/image-registry.ts +180 -0
- package/lib/import.ts +2060 -0
- package/lib/journals.ts +505 -0
- package/lib/{merge.js → merge.ts} +185 -135
- package/lib/{orcid.js → orcid.ts} +17 -22
- package/lib/{pdf-comments.js → pdf-comments.ts} +76 -18
- package/lib/{pdf-import.js → pdf-import.ts} +148 -70
- package/lib/{plugins.js → plugins.ts} +82 -39
- package/lib/postprocess.ts +188 -0
- package/lib/pptx-color-filter.lua +37 -0
- package/lib/pptx-template.ts +625 -0
- package/lib/pptx-themes/academic.pptx +0 -0
- package/lib/pptx-themes/corporate.pptx +0 -0
- package/lib/pptx-themes/dark.pptx +0 -0
- package/lib/pptx-themes/default.pptx +0 -0
- package/lib/pptx-themes/minimal.pptx +0 -0
- package/lib/pptx-themes/plant.pptx +0 -0
- package/lib/pptx-themes.ts +896 -0
- package/lib/protect-restore.ts +516 -0
- package/lib/rate-limiter.ts +94 -0
- package/lib/{response.js → response.ts} +36 -21
- package/lib/{review.js → review.ts} +53 -43
- package/lib/{schema.js → schema.ts} +70 -25
- package/lib/{sections.js → sections.ts} +71 -76
- package/lib/slides.ts +793 -0
- package/lib/{spelling.js → spelling.ts} +43 -59
- package/lib/{templates.js → templates.ts} +20 -17
- package/lib/themes.ts +742 -0
- package/lib/{trackchanges.js → trackchanges.ts} +52 -23
- package/lib/types.ts +509 -0
- package/lib/{undo.js → undo.ts} +75 -52
- package/lib/utils.ts +41 -0
- package/lib/{variables.js → variables.ts} +60 -54
- package/lib/word.ts +428 -0
- package/lib/{wordcomments.js → wordcomments.ts} +94 -40
- package/package.json +15 -5
- package/skill/REFERENCE.md +67 -0
- package/tsconfig.json +26 -0
- package/lib/annotations.js +0 -414
- package/lib/build.js +0 -639
- package/lib/config.js +0 -79
- package/lib/import.js +0 -1145
- package/lib/journals.js +0 -629
- package/lib/word.js +0 -225
- /package/lib/{scientific-words.js → scientific-words.ts} +0 -0
package/lib/{git.js → git.ts}
RENAMED
|
@@ -7,12 +7,12 @@ import { execSync } from 'child_process';
|
|
|
7
7
|
import * as fs from 'fs';
|
|
8
8
|
import * as path from 'path';
|
|
9
9
|
import { diffWords } from 'diff';
|
|
10
|
+
import type { FileChange, CommitInfo, ChangedFile, BlameEntry, AuthorStats, ContributorStats } from './types.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Check if current directory is a git repository
|
|
13
|
-
* @returns {boolean}
|
|
14
14
|
*/
|
|
15
|
-
export function isGitRepo() {
|
|
15
|
+
export function isGitRepo(): boolean {
|
|
16
16
|
try {
|
|
17
17
|
execSync('git rev-parse --is-inside-work-tree', { stdio: 'pipe' });
|
|
18
18
|
return true;
|
|
@@ -23,9 +23,8 @@ export function isGitRepo() {
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Get the current git branch
|
|
26
|
-
* @returns {string|null}
|
|
27
26
|
*/
|
|
28
|
-
export function getCurrentBranch() {
|
|
27
|
+
export function getCurrentBranch(): string | null {
|
|
29
28
|
try {
|
|
30
29
|
return execSync('git rev-parse --abbrev-ref HEAD', { stdio: 'pipe' })
|
|
31
30
|
.toString()
|
|
@@ -37,15 +36,14 @@ export function getCurrentBranch() {
|
|
|
37
36
|
|
|
38
37
|
/**
|
|
39
38
|
* Get the default branch (main or master)
|
|
40
|
-
* @returns {string}
|
|
41
39
|
*/
|
|
42
|
-
export function getDefaultBranch() {
|
|
40
|
+
export function getDefaultBranch(): string {
|
|
43
41
|
try {
|
|
44
42
|
// Try to get the remote default branch
|
|
45
43
|
const remote = execSync('git remote show origin', { stdio: 'pipe' })
|
|
46
44
|
.toString();
|
|
47
45
|
const match = remote.match(/HEAD branch:\s*(\S+)/);
|
|
48
|
-
if (match) return match[1];
|
|
46
|
+
if (match?.[1]) return match[1];
|
|
49
47
|
} catch {
|
|
50
48
|
// Fall through
|
|
51
49
|
}
|
|
@@ -66,11 +64,10 @@ export function getDefaultBranch() {
|
|
|
66
64
|
|
|
67
65
|
/**
|
|
68
66
|
* Get file content from a specific git ref
|
|
69
|
-
* @param
|
|
70
|
-
* @param
|
|
71
|
-
* @returns {string|null}
|
|
67
|
+
* @param filePath - Path to file
|
|
68
|
+
* @param ref - Git reference (branch, tag, commit)
|
|
72
69
|
*/
|
|
73
|
-
export function getFileAtRef(filePath, ref) {
|
|
70
|
+
export function getFileAtRef(filePath: string, ref: string): string | null {
|
|
74
71
|
try {
|
|
75
72
|
return execSync(`git show ${ref}:${filePath}`, { stdio: 'pipe' }).toString();
|
|
76
73
|
} catch {
|
|
@@ -80,11 +77,10 @@ export function getFileAtRef(filePath, ref) {
|
|
|
80
77
|
|
|
81
78
|
/**
|
|
82
79
|
* Get list of changed files between refs
|
|
83
|
-
* @param
|
|
84
|
-
* @param
|
|
85
|
-
* @returns {Array<{file: string, status: string}>}
|
|
80
|
+
* @param fromRef - Starting reference
|
|
81
|
+
* @param toRef - Ending reference (default: HEAD)
|
|
86
82
|
*/
|
|
87
|
-
export function getChangedFiles(fromRef, toRef = 'HEAD') {
|
|
83
|
+
export function getChangedFiles(fromRef: string, toRef: string = 'HEAD'): ChangedFile[] {
|
|
88
84
|
try {
|
|
89
85
|
const output = execSync(`git diff --name-status ${fromRef}..${toRef}`, { stdio: 'pipe' })
|
|
90
86
|
.toString()
|
|
@@ -93,10 +89,12 @@ export function getChangedFiles(fromRef, toRef = 'HEAD') {
|
|
|
93
89
|
if (!output) return [];
|
|
94
90
|
|
|
95
91
|
return output.split('\n').map(line => {
|
|
96
|
-
const
|
|
92
|
+
const parts = line.split('\t');
|
|
93
|
+
const status = parts[0];
|
|
94
|
+
const file = parts[1] ?? '';
|
|
97
95
|
return {
|
|
98
96
|
file,
|
|
99
|
-
status: status === 'A' ? 'added' : status === 'D' ? 'deleted' : 'modified',
|
|
97
|
+
status: (status === 'A' ? 'added' : status === 'D' ? 'deleted' : 'modified') as 'added' | 'deleted' | 'modified',
|
|
100
98
|
};
|
|
101
99
|
});
|
|
102
100
|
} catch {
|
|
@@ -106,11 +104,10 @@ export function getChangedFiles(fromRef, toRef = 'HEAD') {
|
|
|
106
104
|
|
|
107
105
|
/**
|
|
108
106
|
* Get commit history for a file
|
|
109
|
-
* @param
|
|
110
|
-
* @param
|
|
111
|
-
* @returns {Array<{hash: string, date: string, message: string}>}
|
|
107
|
+
* @param filePath - Path to file
|
|
108
|
+
* @param limit - Maximum number of commits to return
|
|
112
109
|
*/
|
|
113
|
-
export function getFileHistory(filePath, limit = 10) {
|
|
110
|
+
export function getFileHistory(filePath: string, limit: number = 10): CommitInfo[] {
|
|
114
111
|
try {
|
|
115
112
|
const output = execSync(
|
|
116
113
|
`git log --format="%h|%ci|%s" -n ${limit} -- "${filePath}"`,
|
|
@@ -120,8 +117,13 @@ export function getFileHistory(filePath, limit = 10) {
|
|
|
120
117
|
if (!output) return [];
|
|
121
118
|
|
|
122
119
|
return output.split('\n').map(line => {
|
|
123
|
-
const
|
|
124
|
-
return {
|
|
120
|
+
const parts = line.split('|');
|
|
121
|
+
return {
|
|
122
|
+
hash: parts[0] ?? '',
|
|
123
|
+
date: parts[1] ?? '',
|
|
124
|
+
message: parts[2] ?? '',
|
|
125
|
+
author: ''
|
|
126
|
+
};
|
|
125
127
|
});
|
|
126
128
|
} catch {
|
|
127
129
|
return [];
|
|
@@ -130,12 +132,11 @@ export function getFileHistory(filePath, limit = 10) {
|
|
|
130
132
|
|
|
131
133
|
/**
|
|
132
134
|
* Compare file content between two refs
|
|
133
|
-
* @param
|
|
134
|
-
* @param
|
|
135
|
-
* @param
|
|
136
|
-
* @returns {{added: number, removed: number, changes: Array}}
|
|
135
|
+
* @param filePath - Path to file
|
|
136
|
+
* @param fromRef - Starting reference
|
|
137
|
+
* @param toRef - Ending reference (default: HEAD)
|
|
137
138
|
*/
|
|
138
|
-
export function compareFileVersions(filePath, fromRef, toRef = 'HEAD') {
|
|
139
|
+
export function compareFileVersions(filePath: string, fromRef: string, toRef: string = 'HEAD'): FileChange {
|
|
139
140
|
const oldContent = getFileAtRef(filePath, fromRef) || '';
|
|
140
141
|
const newContent = toRef === 'HEAD'
|
|
141
142
|
? fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : ''
|
|
@@ -145,15 +146,15 @@ export function compareFileVersions(filePath, fromRef, toRef = 'HEAD') {
|
|
|
145
146
|
|
|
146
147
|
let added = 0;
|
|
147
148
|
let removed = 0;
|
|
148
|
-
const changes = [];
|
|
149
|
+
const changes: Array<{ added?: boolean; removed?: boolean; value: string }> = [];
|
|
149
150
|
|
|
150
151
|
for (const part of diffs) {
|
|
151
152
|
if (part.added) {
|
|
152
153
|
added += part.value.split(/\s+/).filter(w => w).length;
|
|
153
|
-
changes.push({
|
|
154
|
+
changes.push({ added: true, value: part.value });
|
|
154
155
|
} else if (part.removed) {
|
|
155
156
|
removed += part.value.split(/\s+/).filter(w => w).length;
|
|
156
|
-
changes.push({
|
|
157
|
+
changes.push({ removed: true, value: part.value });
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
160
|
|
|
@@ -162,15 +163,18 @@ export function compareFileVersions(filePath, fromRef, toRef = 'HEAD') {
|
|
|
162
163
|
|
|
163
164
|
/**
|
|
164
165
|
* Get word count difference between refs
|
|
165
|
-
* @param
|
|
166
|
-
* @param
|
|
167
|
-
* @param
|
|
168
|
-
* @returns {{total: {added: number, removed: number}, byFile: Object}}
|
|
166
|
+
* @param files - Array of file paths
|
|
167
|
+
* @param fromRef - Starting reference
|
|
168
|
+
* @param toRef - Ending reference (default: HEAD)
|
|
169
169
|
*/
|
|
170
|
-
export function getWordCountDiff(
|
|
170
|
+
export function getWordCountDiff(
|
|
171
|
+
files: string[],
|
|
172
|
+
fromRef: string,
|
|
173
|
+
toRef: string = 'HEAD'
|
|
174
|
+
): { total: { added: number; removed: number }; byFile: Record<string, { added: number; removed: number }> } {
|
|
171
175
|
let totalAdded = 0;
|
|
172
176
|
let totalRemoved = 0;
|
|
173
|
-
const byFile = {};
|
|
177
|
+
const byFile: Record<string, { added: number; removed: number }> = {};
|
|
174
178
|
|
|
175
179
|
for (const file of files) {
|
|
176
180
|
const { added, removed } = compareFileVersions(file, fromRef, toRef);
|
|
@@ -187,10 +191,9 @@ export function getWordCountDiff(files, fromRef, toRef = 'HEAD') {
|
|
|
187
191
|
|
|
188
192
|
/**
|
|
189
193
|
* Get recent commits
|
|
190
|
-
* @param
|
|
191
|
-
* @returns {Array<{hash: string, date: string, message: string, author: string}>}
|
|
194
|
+
* @param limit - Maximum number of commits to return
|
|
192
195
|
*/
|
|
193
|
-
export function getRecentCommits(limit = 10) {
|
|
196
|
+
export function getRecentCommits(limit: number = 10): CommitInfo[] {
|
|
194
197
|
try {
|
|
195
198
|
const output = execSync(
|
|
196
199
|
`git log --format="%h|%ci|%an|%s" -n ${limit}`,
|
|
@@ -200,8 +203,13 @@ export function getRecentCommits(limit = 10) {
|
|
|
200
203
|
if (!output) return [];
|
|
201
204
|
|
|
202
205
|
return output.split('\n').map(line => {
|
|
203
|
-
const
|
|
204
|
-
return {
|
|
206
|
+
const parts = line.split('|');
|
|
207
|
+
return {
|
|
208
|
+
hash: parts[0] ?? '',
|
|
209
|
+
date: parts[1] ?? '',
|
|
210
|
+
author: parts[2] ?? '',
|
|
211
|
+
message: parts[3] ?? ''
|
|
212
|
+
};
|
|
205
213
|
});
|
|
206
214
|
} catch {
|
|
207
215
|
return [];
|
|
@@ -210,9 +218,8 @@ export function getRecentCommits(limit = 10) {
|
|
|
210
218
|
|
|
211
219
|
/**
|
|
212
220
|
* Check if there are uncommitted changes
|
|
213
|
-
* @returns {boolean}
|
|
214
221
|
*/
|
|
215
|
-
export function hasUncommittedChanges() {
|
|
222
|
+
export function hasUncommittedChanges(): boolean {
|
|
216
223
|
try {
|
|
217
224
|
const output = execSync('git status --porcelain', { stdio: 'pipe' }).toString();
|
|
218
225
|
return output.trim().length > 0;
|
|
@@ -223,9 +230,8 @@ export function hasUncommittedChanges() {
|
|
|
223
230
|
|
|
224
231
|
/**
|
|
225
232
|
* Get tags
|
|
226
|
-
* @returns {string[]}
|
|
227
233
|
*/
|
|
228
|
-
export function getTags() {
|
|
234
|
+
export function getTags(): string[] {
|
|
229
235
|
try {
|
|
230
236
|
return execSync('git tag --sort=-creatordate', { stdio: 'pipe' })
|
|
231
237
|
.toString()
|
|
@@ -240,10 +246,9 @@ export function getTags() {
|
|
|
240
246
|
/**
|
|
241
247
|
* Get blame information for a file
|
|
242
248
|
* Returns author and commit info for each line
|
|
243
|
-
* @param
|
|
244
|
-
* @returns {Array<{line: number, author: string, date: string, hash: string, content: string}>}
|
|
249
|
+
* @param filePath - Path to file
|
|
245
250
|
*/
|
|
246
|
-
export function getFileBlame(filePath) {
|
|
251
|
+
export function getFileBlame(filePath: string): BlameEntry[] {
|
|
247
252
|
try {
|
|
248
253
|
const output = execSync(
|
|
249
254
|
`git blame --line-porcelain "${filePath}"`,
|
|
@@ -251,16 +256,16 @@ export function getFileBlame(filePath) {
|
|
|
251
256
|
).toString();
|
|
252
257
|
|
|
253
258
|
const lines = output.split('\n');
|
|
254
|
-
const result = [];
|
|
255
|
-
let current = {};
|
|
259
|
+
const result: BlameEntry[] = [];
|
|
260
|
+
let current: Partial<BlameEntry> = {};
|
|
256
261
|
let lineNumber = 0;
|
|
257
262
|
|
|
258
263
|
for (const line of lines) {
|
|
259
264
|
if (/^[0-9a-f]{40}/.test(line)) {
|
|
260
265
|
// New blame entry: hash original-line final-line [count]
|
|
261
266
|
const parts = line.split(' ');
|
|
262
|
-
current.hash = parts[0]
|
|
263
|
-
lineNumber = parseInt(parts[2], 10);
|
|
267
|
+
current.hash = parts[0]?.slice(0, 7) ?? '';
|
|
268
|
+
lineNumber = parseInt(parts[2] ?? '0', 10);
|
|
264
269
|
} else if (line.startsWith('author ')) {
|
|
265
270
|
current.author = line.slice(7);
|
|
266
271
|
} else if (line.startsWith('author-time ')) {
|
|
@@ -270,7 +275,7 @@ export function getFileBlame(filePath) {
|
|
|
270
275
|
// Actual content line (prefixed with tab)
|
|
271
276
|
current.content = line.slice(1);
|
|
272
277
|
current.line = lineNumber;
|
|
273
|
-
result.push(
|
|
278
|
+
result.push(current as BlameEntry);
|
|
274
279
|
current = {};
|
|
275
280
|
}
|
|
276
281
|
}
|
|
@@ -283,20 +288,19 @@ export function getFileBlame(filePath) {
|
|
|
283
288
|
|
|
284
289
|
/**
|
|
285
290
|
* Get author statistics for a file
|
|
286
|
-
* @param
|
|
287
|
-
* @returns {Object<string, {lines: number, percentage: number}>}
|
|
291
|
+
* @param filePath - Path to file
|
|
288
292
|
*/
|
|
289
|
-
export function getAuthorStats(filePath) {
|
|
293
|
+
export function getAuthorStats(filePath: string): Record<string, AuthorStats> {
|
|
290
294
|
const blame = getFileBlame(filePath);
|
|
291
295
|
if (blame.length === 0) return {};
|
|
292
296
|
|
|
293
|
-
const counts = {};
|
|
297
|
+
const counts: Record<string, number> = {};
|
|
294
298
|
for (const entry of blame) {
|
|
295
299
|
counts[entry.author] = (counts[entry.author] || 0) + 1;
|
|
296
300
|
}
|
|
297
301
|
|
|
298
302
|
const total = blame.length;
|
|
299
|
-
const stats = {};
|
|
303
|
+
const stats: Record<string, AuthorStats> = {};
|
|
300
304
|
for (const [author, lines] of Object.entries(counts)) {
|
|
301
305
|
stats[author] = {
|
|
302
306
|
lines,
|
|
@@ -309,11 +313,10 @@ export function getAuthorStats(filePath) {
|
|
|
309
313
|
|
|
310
314
|
/**
|
|
311
315
|
* Get contributors across multiple files
|
|
312
|
-
* @param
|
|
313
|
-
* @returns {Object<string, {lines: number, files: number}>}
|
|
316
|
+
* @param files - Array of file paths
|
|
314
317
|
*/
|
|
315
|
-
export function getContributors(files) {
|
|
316
|
-
const contributors = {};
|
|
318
|
+
export function getContributors(files: string[]): Record<string, ContributorStats> {
|
|
319
|
+
const contributors: Record<string, ContributorStats> = {};
|
|
317
320
|
|
|
318
321
|
for (const file of files) {
|
|
319
322
|
const stats = getAuthorStats(file);
|
|
@@ -13,10 +13,41 @@ import * as path from 'path';
|
|
|
13
13
|
// Default dictionary location
|
|
14
14
|
const DEFAULT_DICT_NAME = '.rev-dictionary';
|
|
15
15
|
|
|
16
|
+
interface GrammarRule {
|
|
17
|
+
id: string;
|
|
18
|
+
pattern: RegExp;
|
|
19
|
+
message: string;
|
|
20
|
+
severity: 'error' | 'warning' | 'info';
|
|
21
|
+
check?: (match: string) => boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface GrammarIssue {
|
|
25
|
+
rule: string;
|
|
26
|
+
message: string;
|
|
27
|
+
severity: 'error' | 'warning' | 'info';
|
|
28
|
+
line: number;
|
|
29
|
+
column: number;
|
|
30
|
+
match: string;
|
|
31
|
+
context: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface GrammarSummary {
|
|
35
|
+
total: number;
|
|
36
|
+
errors: number;
|
|
37
|
+
warnings: number;
|
|
38
|
+
info: number;
|
|
39
|
+
byRule: Record<string, number>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface CheckGrammarOptions {
|
|
43
|
+
scientific?: boolean;
|
|
44
|
+
directory?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
16
47
|
/**
|
|
17
48
|
* Common grammar/style rules
|
|
18
49
|
*/
|
|
19
|
-
const GRAMMAR_RULES = [
|
|
50
|
+
const GRAMMAR_RULES: GrammarRule[] = [
|
|
20
51
|
{
|
|
21
52
|
id: 'passive-voice',
|
|
22
53
|
pattern: /\b(is|are|was|were|be|been|being)\s+(being\s+)?\w+ed\b/gi,
|
|
@@ -50,7 +81,7 @@ const GRAMMAR_RULES = [
|
|
|
50
81
|
{
|
|
51
82
|
id: 'sentence-length',
|
|
52
83
|
pattern: /[^.!?]*[.!?]/g,
|
|
53
|
-
check: (match) => {
|
|
84
|
+
check: (match: string): boolean => {
|
|
54
85
|
const words = match.trim().split(/\s+/).length;
|
|
55
86
|
return words > 40;
|
|
56
87
|
},
|
|
@@ -80,7 +111,7 @@ const GRAMMAR_RULES = [
|
|
|
80
111
|
/**
|
|
81
112
|
* Scientific writing specific rules
|
|
82
113
|
*/
|
|
83
|
-
const SCIENTIFIC_RULES = [
|
|
114
|
+
const SCIENTIFIC_RULES: GrammarRule[] = [
|
|
84
115
|
{
|
|
85
116
|
id: 'first-person',
|
|
86
117
|
pattern: /\b(I|we|my|our)\b/gi,
|
|
@@ -109,12 +140,10 @@ const SCIENTIFIC_RULES = [
|
|
|
109
140
|
|
|
110
141
|
/**
|
|
111
142
|
* Load custom dictionary from file
|
|
112
|
-
* @param {string} directory - Directory to search for dictionary
|
|
113
|
-
* @returns {Set<string>} Set of custom words
|
|
114
143
|
*/
|
|
115
|
-
export function loadDictionary(directory = '.') {
|
|
144
|
+
export function loadDictionary(directory: string = '.'): Set<string> {
|
|
116
145
|
const dictPath = path.join(directory, DEFAULT_DICT_NAME);
|
|
117
|
-
const words = new Set();
|
|
146
|
+
const words = new Set<string>();
|
|
118
147
|
|
|
119
148
|
if (fs.existsSync(dictPath)) {
|
|
120
149
|
const content = fs.readFileSync(dictPath, 'utf-8');
|
|
@@ -133,10 +162,8 @@ export function loadDictionary(directory = '.') {
|
|
|
133
162
|
|
|
134
163
|
/**
|
|
135
164
|
* Save custom dictionary to file
|
|
136
|
-
* @param {Set<string>} words - Set of words
|
|
137
|
-
* @param {string} directory - Directory to save dictionary
|
|
138
165
|
*/
|
|
139
|
-
export function saveDictionary(words
|
|
166
|
+
export function saveDictionary(words: Set<string>, directory: string = '.'): void {
|
|
140
167
|
const dictPath = path.join(directory, DEFAULT_DICT_NAME);
|
|
141
168
|
|
|
142
169
|
const header = `# Custom dictionary for docrev
|
|
@@ -150,11 +177,8 @@ export function saveDictionary(words, directory = '.') {
|
|
|
150
177
|
|
|
151
178
|
/**
|
|
152
179
|
* Add word to custom dictionary
|
|
153
|
-
* @param {string} word - Word to add
|
|
154
|
-
* @param {string} directory - Directory containing dictionary
|
|
155
|
-
* @returns {boolean} True if word was added (not already present)
|
|
156
180
|
*/
|
|
157
|
-
export function addToDictionary(word, directory = '.') {
|
|
181
|
+
export function addToDictionary(word: string, directory: string = '.'): boolean {
|
|
158
182
|
const words = loadDictionary(directory);
|
|
159
183
|
const normalizedWord = word.trim().toLowerCase();
|
|
160
184
|
|
|
@@ -169,11 +193,8 @@ export function addToDictionary(word, directory = '.') {
|
|
|
169
193
|
|
|
170
194
|
/**
|
|
171
195
|
* Remove word from custom dictionary
|
|
172
|
-
* @param {string} word - Word to remove
|
|
173
|
-
* @param {string} directory - Directory containing dictionary
|
|
174
|
-
* @returns {boolean} True if word was removed
|
|
175
196
|
*/
|
|
176
|
-
export function removeFromDictionary(word, directory = '.') {
|
|
197
|
+
export function removeFromDictionary(word: string, directory: string = '.'): boolean {
|
|
177
198
|
const words = loadDictionary(directory);
|
|
178
199
|
const normalizedWord = word.trim().toLowerCase();
|
|
179
200
|
|
|
@@ -188,16 +209,11 @@ export function removeFromDictionary(word, directory = '.') {
|
|
|
188
209
|
|
|
189
210
|
/**
|
|
190
211
|
* Check text for grammar/style issues
|
|
191
|
-
* @param {string} text - Text to check
|
|
192
|
-
* @param {object} options - Options
|
|
193
|
-
* @param {boolean} options.scientific - Include scientific writing rules
|
|
194
|
-
* @param {string} options.directory - Directory for custom dictionary
|
|
195
|
-
* @returns {Array<{rule: string, message: string, severity: string, line: number, match: string}>}
|
|
196
212
|
*/
|
|
197
|
-
export function checkGrammar(text, options = {}) {
|
|
213
|
+
export function checkGrammar(text: string, options: CheckGrammarOptions = {}): GrammarIssue[] {
|
|
198
214
|
const { scientific = true, directory = '.' } = options;
|
|
199
215
|
const customDict = loadDictionary(directory);
|
|
200
|
-
const issues = [];
|
|
216
|
+
const issues: GrammarIssue[] = [];
|
|
201
217
|
|
|
202
218
|
// Get all rules
|
|
203
219
|
const rules = scientific ? [...GRAMMAR_RULES, ...SCIENTIFIC_RULES] : GRAMMAR_RULES;
|
|
@@ -207,6 +223,7 @@ export function checkGrammar(text, options = {}) {
|
|
|
207
223
|
|
|
208
224
|
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
209
225
|
const line = lines[lineNum];
|
|
226
|
+
if (!line) continue;
|
|
210
227
|
|
|
211
228
|
// Skip code blocks and YAML frontmatter
|
|
212
229
|
if (line.trim().startsWith('```') || line.trim().startsWith('---')) {
|
|
@@ -252,11 +269,9 @@ export function checkGrammar(text, options = {}) {
|
|
|
252
269
|
|
|
253
270
|
/**
|
|
254
271
|
* Get grammar check summary
|
|
255
|
-
* @param {Array} issues - List of issues from checkGrammar
|
|
256
|
-
* @returns {object} Summary stats
|
|
257
272
|
*/
|
|
258
|
-
export function getGrammarSummary(issues) {
|
|
259
|
-
const summary = {
|
|
273
|
+
export function getGrammarSummary(issues: GrammarIssue[]): GrammarSummary {
|
|
274
|
+
const summary: GrammarSummary = {
|
|
260
275
|
total: issues.length,
|
|
261
276
|
errors: 0,
|
|
262
277
|
warnings: 0,
|
|
@@ -277,10 +292,8 @@ export function getGrammarSummary(issues) {
|
|
|
277
292
|
|
|
278
293
|
/**
|
|
279
294
|
* List available grammar rules
|
|
280
|
-
* @param {boolean} scientific - Include scientific rules
|
|
281
|
-
* @returns {Array<{id: string, message: string, severity: string}>}
|
|
282
295
|
*/
|
|
283
|
-
export function listRules(scientific = true) {
|
|
296
|
+
export function listRules(scientific: boolean = true): Array<{ id: string; message: string; severity: string }> {
|
|
284
297
|
const rules = scientific ? [...GRAMMAR_RULES, ...SCIENTIFIC_RULES] : GRAMMAR_RULES;
|
|
285
298
|
return rules.map(r => ({
|
|
286
299
|
id: r.id,
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image registry utilities for tracking figures and tables in markdown documents
|
|
3
|
+
*
|
|
4
|
+
* The registry maps figure/table labels and display numbers to source paths,
|
|
5
|
+
* enabling Word import to match rendered figures back to original sources.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Image registry entry
|
|
13
|
+
*/
|
|
14
|
+
interface ImageEntry {
|
|
15
|
+
caption: string;
|
|
16
|
+
path: string;
|
|
17
|
+
label: string | null;
|
|
18
|
+
type: 'fig' | 'tbl';
|
|
19
|
+
number?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Image registry
|
|
24
|
+
*/
|
|
25
|
+
interface ImageRegistry {
|
|
26
|
+
figures: ImageEntry[];
|
|
27
|
+
byLabel: Map<string, ImageEntry>;
|
|
28
|
+
byNumber: Map<string, ImageEntry>;
|
|
29
|
+
byCaption: Map<string, ImageEntry>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Crossref registry info
|
|
34
|
+
*/
|
|
35
|
+
interface CrossrefInfo {
|
|
36
|
+
num: number;
|
|
37
|
+
isSupp: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Crossref registry
|
|
42
|
+
*/
|
|
43
|
+
interface CrossrefRegistry {
|
|
44
|
+
figures: Map<string, CrossrefInfo>;
|
|
45
|
+
tables: Map<string, CrossrefInfo>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Saved registry data
|
|
50
|
+
*/
|
|
51
|
+
interface RegistryData {
|
|
52
|
+
version: number;
|
|
53
|
+
created: string;
|
|
54
|
+
figures: ImageEntry[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Pattern to extract markdown images with optional pandoc-crossref anchors
|
|
59
|
+
* Captures: {#fig:label} or 
|
|
60
|
+
* Groups: [1] = caption, [2] = path, [3] = label type (fig/tbl), [4] = label
|
|
61
|
+
*/
|
|
62
|
+
export const IMAGE_PATTERN = /!\[([^\]]*)\]\(([^)]+)\)(?:\{#(fig|tbl):([^}]+)\})?/g;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Build image registry from markdown content
|
|
66
|
+
* Maps figure labels and display numbers to source paths
|
|
67
|
+
*/
|
|
68
|
+
export function buildImageRegistry(
|
|
69
|
+
content: string,
|
|
70
|
+
crossrefRegistry: CrossrefRegistry | null = null
|
|
71
|
+
): ImageRegistry {
|
|
72
|
+
const figures: ImageEntry[] = [];
|
|
73
|
+
const byLabel = new Map<string, ImageEntry>();
|
|
74
|
+
const byNumber = new Map<string, ImageEntry>();
|
|
75
|
+
const byCaption = new Map<string, ImageEntry>();
|
|
76
|
+
|
|
77
|
+
IMAGE_PATTERN.lastIndex = 0;
|
|
78
|
+
let match: RegExpExecArray | null;
|
|
79
|
+
|
|
80
|
+
while ((match = IMAGE_PATTERN.exec(content)) !== null) {
|
|
81
|
+
const caption = match[1];
|
|
82
|
+
const imagePath = match[2];
|
|
83
|
+
const labelType = match[3] as 'fig' | 'tbl' | undefined; // 'fig' or 'tbl' or undefined
|
|
84
|
+
const label = match[4]; // label without prefix
|
|
85
|
+
|
|
86
|
+
const entry: ImageEntry = {
|
|
87
|
+
caption,
|
|
88
|
+
path: imagePath,
|
|
89
|
+
label: label || null,
|
|
90
|
+
type: labelType || 'fig',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Add display number if we have a crossref registry
|
|
94
|
+
if (label && crossrefRegistry) {
|
|
95
|
+
const info = crossrefRegistry.figures.get(label) || crossrefRegistry.tables.get(label);
|
|
96
|
+
if (info) {
|
|
97
|
+
entry.number = info.isSupp ? `S${info.num}` : `${info.num}`;
|
|
98
|
+
byNumber.set(`${entry.type}:${entry.number}`, entry);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
figures.push(entry);
|
|
103
|
+
|
|
104
|
+
if (label) {
|
|
105
|
+
byLabel.set(`${labelType || 'fig'}:${label}`, entry);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Index by first 50 chars of caption for fuzzy matching
|
|
109
|
+
if (caption) {
|
|
110
|
+
const captionKey = caption.slice(0, 50).toLowerCase().trim();
|
|
111
|
+
byCaption.set(captionKey, entry);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { figures, byLabel, byNumber, byCaption };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Write image registry to .rev directory
|
|
120
|
+
*/
|
|
121
|
+
export function writeImageRegistry(directory: string, registry: ImageRegistry): string {
|
|
122
|
+
const revDir = path.join(directory, '.rev');
|
|
123
|
+
if (!fs.existsSync(revDir)) {
|
|
124
|
+
fs.mkdirSync(revDir, { recursive: true });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Convert Maps to objects for JSON serialization
|
|
128
|
+
const data: RegistryData = {
|
|
129
|
+
version: 1,
|
|
130
|
+
created: new Date().toISOString(),
|
|
131
|
+
figures: registry.figures,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const registryPath = path.join(revDir, 'image-registry.json');
|
|
135
|
+
fs.writeFileSync(registryPath, JSON.stringify(data, null, 2), 'utf-8');
|
|
136
|
+
|
|
137
|
+
return registryPath;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Read image registry from .rev directory
|
|
142
|
+
*/
|
|
143
|
+
export function readImageRegistry(directory: string): (ImageRegistry & RegistryData) | null {
|
|
144
|
+
const registryPath = path.join(directory, '.rev', 'image-registry.json');
|
|
145
|
+
|
|
146
|
+
if (!fs.existsSync(registryPath)) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const data = JSON.parse(fs.readFileSync(registryPath, 'utf-8')) as RegistryData;
|
|
152
|
+
|
|
153
|
+
// Rebuild lookup maps from figures array
|
|
154
|
+
const byLabel = new Map<string, ImageEntry>();
|
|
155
|
+
const byNumber = new Map<string, ImageEntry>();
|
|
156
|
+
const byCaption = new Map<string, ImageEntry>();
|
|
157
|
+
|
|
158
|
+
for (const entry of data.figures || []) {
|
|
159
|
+
if (entry.label) {
|
|
160
|
+
byLabel.set(`${entry.type || 'fig'}:${entry.label}`, entry);
|
|
161
|
+
}
|
|
162
|
+
if (entry.number) {
|
|
163
|
+
byNumber.set(`${entry.type || 'fig'}:${entry.number}`, entry);
|
|
164
|
+
}
|
|
165
|
+
if (entry.caption) {
|
|
166
|
+
const captionKey = entry.caption.slice(0, 50).toLowerCase().trim();
|
|
167
|
+
byCaption.set(captionKey, entry);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
...data,
|
|
173
|
+
byLabel,
|
|
174
|
+
byNumber,
|
|
175
|
+
byCaption,
|
|
176
|
+
};
|
|
177
|
+
} catch (err) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
}
|