dravoice 0.1.1 → 0.1.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/LICENSE +21 -21
- package/README.md +102 -36
- package/bin/dravoice.js +11 -10
- package/package.json +47 -45
- package/src/index.js +874 -197
- package/src/v2/analyzers/discourse.js +63 -52
- package/src/v2/analyzers/evidence.js +73 -38
- package/src/v2/analyzers/lexical.js +114 -58
- package/src/v2/analyzers/register.js +46 -34
- package/src/v2/analyzers/rhetorical-shape.js +59 -48
- package/src/v2/analyzers/rhythm.js +39 -47
- package/src/v2/analyzers/structure.js +24 -24
- package/src/v2/benchmark.js +574 -568
- package/src/v2/brief.js +154 -146
- package/src/v2/config.js +78 -0
- package/src/v2/document-model.js +351 -260
- package/src/v2/inspect.js +67 -67
- package/src/v2/io-utils.js +51 -0
- package/src/v2/profile.js +155 -129
- package/src/v2/prompt.js +65 -64
- package/src/v2/review.js +177 -219
- package/src/v2/revise-plan.js +130 -33
- package/src/v2/stylometry.js +123 -17
- package/src/v2/text-utils.js +123 -123
package/src/v2/document-model.js
CHANGED
|
@@ -1,260 +1,351 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { normalizeText, splitSentences, tokenizeWords } from "./text-utils.js";
|
|
4
|
-
|
|
5
|
-
export const VOICE_EXTENSIONS = new Set([".md", ".mdx", ".txt"]);
|
|
6
|
-
const DEFAULT_MAX_FILES = 500;
|
|
7
|
-
const DEFAULT_MAX_FILE_BYTES = 1024 * 1024;
|
|
8
|
-
const DEFAULT_MAX_TOTAL_BYTES = 20 * 1024 * 1024;
|
|
9
|
-
|
|
10
|
-
export function loadDocuments({
|
|
11
|
-
examplesDir,
|
|
12
|
-
maxFiles = DEFAULT_MAX_FILES,
|
|
13
|
-
maxFileBytes = DEFAULT_MAX_FILE_BYTES,
|
|
14
|
-
maxTotalBytes = DEFAULT_MAX_TOTAL_BYTES,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
let
|
|
54
|
-
let
|
|
55
|
-
let
|
|
56
|
-
let
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
.
|
|
216
|
-
.
|
|
217
|
-
.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { normalizeText, splitSentences, tokenizeWords } from "./text-utils.js";
|
|
4
|
+
|
|
5
|
+
export const VOICE_EXTENSIONS = new Set([".md", ".mdx", ".txt"]);
|
|
6
|
+
const DEFAULT_MAX_FILES = 500;
|
|
7
|
+
const DEFAULT_MAX_FILE_BYTES = 1024 * 1024;
|
|
8
|
+
const DEFAULT_MAX_TOTAL_BYTES = 20 * 1024 * 1024;
|
|
9
|
+
|
|
10
|
+
export function loadDocuments({
|
|
11
|
+
examplesDir,
|
|
12
|
+
maxFiles = DEFAULT_MAX_FILES,
|
|
13
|
+
maxFileBytes = DEFAULT_MAX_FILE_BYTES,
|
|
14
|
+
maxTotalBytes = DEFAULT_MAX_TOTAL_BYTES,
|
|
15
|
+
excludePaths = [],
|
|
16
|
+
}) {
|
|
17
|
+
const root = path.resolve(examplesDir);
|
|
18
|
+
const files = walkVoiceFiles(root, {
|
|
19
|
+
maxFiles,
|
|
20
|
+
excludePaths: excludePaths.map((item) => path.resolve(item)),
|
|
21
|
+
});
|
|
22
|
+
if (files.length === 0) {
|
|
23
|
+
throw new Error(`No Markdown, MDX, or text examples found at ${examplesDir}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let totalBytes = 0;
|
|
27
|
+
return files.map((filePath) => {
|
|
28
|
+
const stats = fs.statSync(filePath);
|
|
29
|
+
if (stats.size > maxFileBytes) {
|
|
30
|
+
throw new Error(`Voice file ${displayPath(filePath, root)} exceeds the ${maxFileBytes} byte limit.`);
|
|
31
|
+
}
|
|
32
|
+
totalBytes += stats.size;
|
|
33
|
+
if (totalBytes > maxTotalBytes) {
|
|
34
|
+
throw new Error(`Voice corpus exceeds the ${maxTotalBytes} byte total limit.`);
|
|
35
|
+
}
|
|
36
|
+
const contents = fs.readFileSync(filePath, "utf8");
|
|
37
|
+
if (looksBinary(contents)) {
|
|
38
|
+
throw new Error(`Voice file ${displayPath(filePath, root)} looks like binary-looking text and cannot be analyzed.`);
|
|
39
|
+
}
|
|
40
|
+
return parseDocument({
|
|
41
|
+
filePath,
|
|
42
|
+
rootDir: root,
|
|
43
|
+
contents,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function parseDocument({ filePath, rootDir = process.cwd(), contents }) {
|
|
49
|
+
const relative = displayPath(filePath, rootDir);
|
|
50
|
+
const lines = String(contents ?? "").split(/\r?\n/);
|
|
51
|
+
const headings = [];
|
|
52
|
+
const blocks = [];
|
|
53
|
+
let currentHeading = null;
|
|
54
|
+
let currentParagraph = null;
|
|
55
|
+
let inFence = false;
|
|
56
|
+
let inFrontmatter = lines[0]?.trim() === "---";
|
|
57
|
+
let inHtmlComment = false;
|
|
58
|
+
let jsxBlockTag = null;
|
|
59
|
+
|
|
60
|
+
const flushParagraph = () => {
|
|
61
|
+
if (currentParagraph?.lines.length) {
|
|
62
|
+
blocks.push(currentParagraph);
|
|
63
|
+
}
|
|
64
|
+
currentParagraph = null;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
lines.forEach((line, index) => {
|
|
68
|
+
const lineNumber = index + 1;
|
|
69
|
+
const trimmed = line.trim();
|
|
70
|
+
|
|
71
|
+
if (inFrontmatter) {
|
|
72
|
+
if (index > 0 && trimmed === "---") {
|
|
73
|
+
inFrontmatter = false;
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (/^(```|~~~)/.test(trimmed)) {
|
|
79
|
+
flushParagraph();
|
|
80
|
+
inFence = !inFence;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (inFence) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (inHtmlComment) {
|
|
89
|
+
if (trimmed.includes("-->")) {
|
|
90
|
+
inHtmlComment = false;
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (jsxBlockTag) {
|
|
96
|
+
if (new RegExp(`</${escapeRegExp(jsxBlockTag)}\\s*>`).test(trimmed)) {
|
|
97
|
+
jsxBlockTag = null;
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (trimmed.startsWith("<!--")) {
|
|
103
|
+
flushParagraph();
|
|
104
|
+
if (!trimmed.includes("-->")) {
|
|
105
|
+
inHtmlComment = true;
|
|
106
|
+
}
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const jsxBlock = jsxBlockStart(trimmed);
|
|
111
|
+
if (jsxBlock) {
|
|
112
|
+
flushParagraph();
|
|
113
|
+
jsxBlockTag = jsxBlock;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!trimmed || isMdxScaffold(trimmed) || isTableLine(trimmed) || isJsxLike(trimmed)) {
|
|
118
|
+
if (!trimmed) {
|
|
119
|
+
flushParagraph();
|
|
120
|
+
}
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const headingMatch = /^(#{1,6})\s+(.+)$/.exec(trimmed);
|
|
125
|
+
if (headingMatch) {
|
|
126
|
+
flushParagraph();
|
|
127
|
+
currentHeading = {
|
|
128
|
+
id: `h${headings.length + 1}`,
|
|
129
|
+
depth: headingMatch[1].length,
|
|
130
|
+
text: stripMarkdown(headingMatch[2]).trim(),
|
|
131
|
+
line: lineNumber,
|
|
132
|
+
};
|
|
133
|
+
headings.push(currentHeading);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const blockType = blockTypeFor(trimmed);
|
|
138
|
+
const text = stripMarkdown(trimmed.replace(/^[-*]\s+/, "").replace(/^\d+[.)]\s+/, "").replace(/^>\s+/, ""));
|
|
139
|
+
if (blockType === "paragraph") {
|
|
140
|
+
if (!currentParagraph) {
|
|
141
|
+
currentParagraph = makeBlock({ type: "paragraph", line: lineNumber, heading: currentHeading });
|
|
142
|
+
}
|
|
143
|
+
currentParagraph.lines.push(text);
|
|
144
|
+
currentParagraph.lineNumbers.push(lineNumber);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
flushParagraph();
|
|
149
|
+
blocks.push({
|
|
150
|
+
...makeBlock({ type: blockType, line: lineNumber, heading: currentHeading }),
|
|
151
|
+
lines: [text],
|
|
152
|
+
lineNumbers: [lineNumber],
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
flushParagraph();
|
|
156
|
+
|
|
157
|
+
const paragraphs = blocks.map(blockToParagraph).filter((paragraph) => paragraph.text);
|
|
158
|
+
const sentences = paragraphs.flatMap(paragraphToSentences);
|
|
159
|
+
return {
|
|
160
|
+
file: relative,
|
|
161
|
+
path: filePath,
|
|
162
|
+
headings,
|
|
163
|
+
sections: buildSections(headings, blocks),
|
|
164
|
+
blocks,
|
|
165
|
+
paragraphs,
|
|
166
|
+
sentences,
|
|
167
|
+
wordCount: sentences.reduce((sum, sentence) => sum + sentence.tokens.length, 0),
|
|
168
|
+
text: paragraphs.map((paragraph) => paragraph.text).join("\n\n"),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function makeBlock({ type, line, heading }) {
|
|
173
|
+
return {
|
|
174
|
+
type,
|
|
175
|
+
line,
|
|
176
|
+
heading: heading?.text ?? null,
|
|
177
|
+
headingId: heading?.id ?? null,
|
|
178
|
+
headingDepth: heading?.depth ?? 0,
|
|
179
|
+
lines: [],
|
|
180
|
+
lineNumbers: [],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function blockTypeFor(trimmed) {
|
|
185
|
+
if (/^>\s+/.test(trimmed)) {
|
|
186
|
+
return "quote";
|
|
187
|
+
}
|
|
188
|
+
if (/^[-*]\s+/.test(trimmed)) {
|
|
189
|
+
return "list";
|
|
190
|
+
}
|
|
191
|
+
if (/^\d+[.)]\s+/.test(trimmed)) {
|
|
192
|
+
return "list";
|
|
193
|
+
}
|
|
194
|
+
return "paragraph";
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function buildSections(headings, blocks) {
|
|
198
|
+
if (headings.length === 0) {
|
|
199
|
+
return [{ heading: null, blocks }];
|
|
200
|
+
}
|
|
201
|
+
return headings.map((heading, index) => {
|
|
202
|
+
const nextBoundary = headings.slice(index + 1).find((candidate) => candidate.depth <= heading.depth);
|
|
203
|
+
return {
|
|
204
|
+
heading,
|
|
205
|
+
blocks: blocks.filter((block) =>
|
|
206
|
+
block.line > heading.line &&
|
|
207
|
+
(!nextBoundary || block.line < nextBoundary.line)
|
|
208
|
+
),
|
|
209
|
+
};
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function blockToParagraph(block) {
|
|
214
|
+
return {
|
|
215
|
+
type: block.type,
|
|
216
|
+
line: block.line,
|
|
217
|
+
heading: block.heading,
|
|
218
|
+
headingId: block.headingId,
|
|
219
|
+
lines: block.lines,
|
|
220
|
+
lineNumbers: block.lineNumbers?.length ? block.lineNumbers : [block.line],
|
|
221
|
+
text: block.lines.join(" ").replace(/\s+/g, " ").trim(),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function paragraphToSentences(paragraph) {
|
|
226
|
+
const lineStarts = [];
|
|
227
|
+
let offset = 0;
|
|
228
|
+
for (let index = 0; index < paragraph.lines.length; index += 1) {
|
|
229
|
+
const text = String(paragraph.lines[index] ?? "").replace(/\s+/g, " ").trim();
|
|
230
|
+
if (!text) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
lineStarts.push({ offset, line: paragraph.lineNumbers[index] ?? paragraph.line });
|
|
234
|
+
offset += text.length + 1;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let searchFrom = 0;
|
|
238
|
+
return splitSentences(paragraph.text).map((text) => {
|
|
239
|
+
const sentenceOffset = paragraph.text.indexOf(text, searchFrom);
|
|
240
|
+
if (sentenceOffset >= 0) {
|
|
241
|
+
searchFrom = sentenceOffset + text.length;
|
|
242
|
+
}
|
|
243
|
+
const line = lineForOffset(lineStarts, sentenceOffset >= 0 ? sentenceOffset : searchFrom);
|
|
244
|
+
return {
|
|
245
|
+
text,
|
|
246
|
+
normalized: normalizeText(text),
|
|
247
|
+
line,
|
|
248
|
+
blockType: paragraph.type,
|
|
249
|
+
heading: paragraph.heading,
|
|
250
|
+
headingId: paragraph.headingId,
|
|
251
|
+
tokens: tokenizeWords(text),
|
|
252
|
+
};
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function stripMarkdown(text) {
|
|
257
|
+
return text
|
|
258
|
+
.replace(/\*\*/g, "")
|
|
259
|
+
.replace(/__+/g, "")
|
|
260
|
+
.replace(/`([^`]+)`/g, "$1")
|
|
261
|
+
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 $2");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function isJsxLike(trimmed) {
|
|
265
|
+
return /^<\/?[A-Za-z][^>]*>/.test(trimmed) || /^<[A-Za-z][^>]*\/>$/.test(trimmed);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function jsxBlockStart(trimmed) {
|
|
269
|
+
const match = /^<([A-Za-z][\w.-]*)(?:\s[^>]*)?>\s*$/.exec(trimmed);
|
|
270
|
+
if (!match || /\/>\s*$/.test(trimmed) || new RegExp(`</${escapeRegExp(match[1])}\\s*>`).test(trimmed)) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
return match[1];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function isMdxScaffold(trimmed) {
|
|
277
|
+
return /^(?:import|export)\s/.test(trimmed);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function isTableLine(trimmed) {
|
|
281
|
+
return /^\|.*\|$/.test(trimmed);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function looksBinary(contents) {
|
|
285
|
+
return String(contents ?? "").includes("\0");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function walkVoiceFiles(rootDir, { maxFiles, excludePaths }) {
|
|
289
|
+
const result = [];
|
|
290
|
+
if (!fs.existsSync(rootDir)) {
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
const stack = [rootDir];
|
|
294
|
+
const skipDirs = new Set([".git", "node_modules", "dist", "build", "__pycache__", "prompts", "voice-pack", "dravoice-voice"]);
|
|
295
|
+
while (stack.length) {
|
|
296
|
+
const current = stack.pop();
|
|
297
|
+
if (isExcludedPath(current, excludePaths)) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const entries = fs.readdirSync(current, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
301
|
+
const directories = [];
|
|
302
|
+
for (const entry of entries) {
|
|
303
|
+
const fullPath = path.join(current, entry.name);
|
|
304
|
+
if (isExcludedPath(fullPath, excludePaths)) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (entry.isDirectory()) {
|
|
308
|
+
if (!skipDirs.has(entry.name)) {
|
|
309
|
+
directories.push(fullPath);
|
|
310
|
+
}
|
|
311
|
+
} else if (entry.isFile() && VOICE_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) {
|
|
312
|
+
result.push(fullPath);
|
|
313
|
+
if (result.length > maxFiles) {
|
|
314
|
+
throw new Error(`Corpus contains more than ${maxFiles} voice file(s) allowed.`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
stack.push(...directories.reverse());
|
|
319
|
+
}
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function lineForOffset(lineStarts, offset) {
|
|
324
|
+
let line = lineStarts[0]?.line ?? 1;
|
|
325
|
+
for (const item of lineStarts) {
|
|
326
|
+
if (item.offset > offset) {
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
line = item.line;
|
|
330
|
+
}
|
|
331
|
+
return line;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function isExcludedPath(filePath, excludePaths) {
|
|
335
|
+
return excludePaths.some((excluded) => {
|
|
336
|
+
const relative = path.relative(excluded, filePath);
|
|
337
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function escapeRegExp(value) {
|
|
342
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function displayPath(filePath, rootDir) {
|
|
346
|
+
const relative = path.relative(rootDir, filePath);
|
|
347
|
+
if (relative && !relative.startsWith("..") && !path.isAbsolute(relative)) {
|
|
348
|
+
return relative.split(path.sep).join("/");
|
|
349
|
+
}
|
|
350
|
+
return filePath.split(path.sep).join("/");
|
|
351
|
+
}
|