dravoice 0.1.2 → 0.1.4

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/src/v2/inspect.js CHANGED
@@ -1,67 +1,67 @@
1
- export function renderInspectV2(profile) {
2
- const lines = [
3
- "Dravoice V2 Profile",
4
- "",
5
- `Source: ${profile.source.documentCount} document(s), ${profile.source.wordCount} words, ${profile.source.sentenceCount} sentences`,
6
- `Corpus confidence: ${capitalize(profile.source.confidence.band)} - ${profile.source.confidence.message}`,
7
- "",
8
- "Feature families:",
9
- ];
10
-
11
- for (const [name, family] of Object.entries(profile.families)) {
12
- lines.push(`- ${name}: ${family.confidence}`);
13
- if (family.warnings.length) {
14
- for (const warning of family.warnings) {
15
- lines.push(` warning: ${warning}`);
16
- }
17
- }
18
- }
19
-
20
- lines.push("", "Feature details:");
21
- for (const [name, family] of Object.entries(profile.families)) {
22
- lines.push(`- ${name}: ${featureSummary(name, family.features)}`);
23
- if (family.revisionHandles?.length) {
24
- lines.push(" Revision handles:");
25
- for (const handle of family.revisionHandles) {
26
- lines.push(` - ${handle}`);
27
- }
28
- }
29
- }
30
-
31
- lines.push("", "Guidance:");
32
- for (const rule of profile.guidance.draftingRules) {
33
- lines.push(`- ${rule}`);
34
- }
35
-
36
- lines.push("");
37
- return `${lines.join("\n")}`;
38
- }
39
-
40
- function capitalize(value) {
41
- return value.charAt(0).toUpperCase() + value.slice(1);
42
- }
43
-
44
- function featureSummary(name, features) {
45
- if (name === "rhythm") {
46
- return `sentenceWords.median=${features.sentenceWords.median}; paragraphWords.median=${features.paragraphWords.median}; listDensity=${features.listDensity}; quoteDensity=${features.quoteDensity}`;
47
- }
48
- if (name === "lexical") {
49
- return `wordCount=${features.wordCount}; contentTypeTokenRatio=${features.vocabularyRichness.contentTypeTokenRatio}; wordLength.median=${features.wordLength.median}; maskedCharacterFourgrams=${features.maskedCharacterFourgrams?.length ?? 0}; functionWordBigrams=${features.functionWordBigrams?.length ?? 0}`;
50
- }
51
- if (name === "register") {
52
- return `primary=${features.primary.value} (${features.primary.score}); alternates=${features.scores.slice(1, 4).map((score) => `${score.value}:${score.score}`).join(", ")}`;
53
- }
54
- if (name === "discourse") {
55
- return `transitionRates=${Object.entries(features.transitionRates).map(([key, value]) => `${key}:${value}`).join(", ")}; sentenceCallbacks=${features.sentenceCallbacks}`;
56
- }
57
- if (name === "rhetoricalShape") {
58
- return `openingMoves=${features.openingMoves.slice(0, 5).join(" -> ") || "none"}; moveTrigrams=${features.moveTrigrams?.slice(0, 2).map((item) => `${item.value}:${item.count}`).join(", ") || "none"}; commonSequences=${features.commonSequences.slice(0, 3).map((item) => `${item.value}:${item.count}`).join(", ") || "none"}`;
59
- }
60
- if (name === "evidence") {
61
- return `evidenceSentenceRate=${features.evidenceSentenceRate}; claimSentenceRate=${features.claimSentenceRate}; supportedClaimRate=${features.supportedClaimRate}; unsupportedClaimRate=${features.unsupportedClaimRate}; evidenceTypes=${features.evidenceTypes.map((item) => `${item.value}:${item.count}`).join(", ") || "none"}`;
62
- }
63
- if (name === "structure") {
64
- return `sectionWords.median=${features.sectionWords.median}; headingCount.median=${features.headingCount.median}; listDocumentRate=${features.listDocumentRate}; quoteDocumentRate=${features.quoteDocumentRate}`;
65
- }
66
- return JSON.stringify(features);
67
- }
1
+ export function renderInspectV2(profile) {
2
+ const lines = [
3
+ "Dravoice V2 Profile",
4
+ "",
5
+ `Source: ${profile.source.documentCount} document(s), ${profile.source.wordCount} words, ${profile.source.sentenceCount} sentences`,
6
+ `Corpus confidence: ${capitalize(profile.source.confidence.band)} - ${profile.source.confidence.message}`,
7
+ "",
8
+ "Feature families:",
9
+ ];
10
+
11
+ for (const [name, family] of Object.entries(profile.families)) {
12
+ lines.push(`- ${name}: ${family.confidence}`);
13
+ if (family.warnings.length) {
14
+ for (const warning of family.warnings) {
15
+ lines.push(` warning: ${warning}`);
16
+ }
17
+ }
18
+ }
19
+
20
+ lines.push("", "Feature details:");
21
+ for (const [name, family] of Object.entries(profile.families)) {
22
+ lines.push(`- ${name}: ${featureSummary(name, family.features)}`);
23
+ if (family.revisionHandles?.length) {
24
+ lines.push(" Revision handles:");
25
+ for (const handle of family.revisionHandles) {
26
+ lines.push(` - ${handle}`);
27
+ }
28
+ }
29
+ }
30
+
31
+ lines.push("", "Guidance:");
32
+ for (const rule of profile.guidance.draftingRules) {
33
+ lines.push(`- ${rule}`);
34
+ }
35
+
36
+ lines.push("");
37
+ return `${lines.join("\n")}`;
38
+ }
39
+
40
+ function capitalize(value) {
41
+ return value.charAt(0).toUpperCase() + value.slice(1);
42
+ }
43
+
44
+ function featureSummary(name, features) {
45
+ if (name === "rhythm") {
46
+ return `sentenceWords.median=${features.sentenceWords.median}; paragraphWords.median=${features.paragraphWords.median}; listDensity=${features.listDensity}; quoteDensity=${features.quoteDensity}`;
47
+ }
48
+ if (name === "lexical") {
49
+ return `wordCount=${features.wordCount}; contentTypeTokenRatio=${features.vocabularyRichness.contentTypeTokenRatio}; wordLength.median=${features.wordLength.median}; maskedCharacterFourgrams=${features.maskedCharacterFourgrams?.length ?? 0}; functionWordBigrams=${features.functionWordBigrams?.length ?? 0}`;
50
+ }
51
+ if (name === "register") {
52
+ return `primary=${features.primary.value} (${features.primary.score}); mixedRegister=${features.mixedRegister ? "yes" : "no"}; markerSets=${features.markerSets?.length ?? 0}; alternates=${features.scores.slice(1, 4).map((score) => `${score.value}:${score.score}`).join(", ")}`;
53
+ }
54
+ if (name === "discourse") {
55
+ return `transitionRates=${Object.entries(features.transitionRates).map(([key, value]) => `${key}:${value}`).join(", ")}; sentenceCallbacks=${features.sentenceCallbacks}`;
56
+ }
57
+ if (name === "rhetoricalShape") {
58
+ return `openingMoves=${features.openingMoves.slice(0, 5).join(" -> ") || "none"}; moveTrigrams=${features.moveTrigrams?.slice(0, 2).map((item) => `${item.value}:${item.count}`).join(", ") || "none"}; commonSequences=${features.commonSequences.slice(0, 3).map((item) => `${item.value}:${item.count}`).join(", ") || "none"}`;
59
+ }
60
+ if (name === "evidence") {
61
+ return `evidenceSentenceRate=${features.evidenceSentenceRate}; claimSentenceRate=${features.claimSentenceRate}; supportedClaimRate=${features.supportedClaimRate}; unsupportedClaimRate=${features.unsupportedClaimRate}; evidenceTypes=${features.evidenceTypes.map((item) => `${item.value}:${item.count}`).join(", ") || "none"}`;
62
+ }
63
+ if (name === "structure") {
64
+ return `sectionWords.median=${features.sectionWords.median}; headingCount.median=${features.headingCount.median}; maxHeadingDepth.median=${features.maxHeadingDepth?.median ?? 0}; sectionOrderPatterns=${features.sectionOrderPatterns?.length ?? 0}; listDocumentRate=${features.listDocumentRate}; quoteDocumentRate=${features.quoteDocumentRate}`;
65
+ }
66
+ return JSON.stringify(features);
67
+ }
@@ -0,0 +1,51 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ const DEFAULT_MAX_READ_BYTES = 5 * 1024 * 1024;
5
+
6
+ export function readUtf8FileBounded(filePath, {
7
+ label = "File",
8
+ maxBytes = DEFAULT_MAX_READ_BYTES,
9
+ rejectBinary = true,
10
+ } = {}) {
11
+ const resolved = path.resolve(filePath);
12
+ const stats = fs.statSync(resolved);
13
+ if (!stats.isFile()) {
14
+ throw new Error(`${label} is not a regular file: ${filePath}`);
15
+ }
16
+ if (stats.size > maxBytes) {
17
+ throw new Error(`${label} ${displayPath(filePath)} exceeds the ${maxBytes} byte limit.`);
18
+ }
19
+ const contents = fs.readFileSync(resolved, "utf8");
20
+ if (rejectBinary && contents.includes("\0")) {
21
+ throw new Error(`${label} ${displayPath(filePath)} looks like binary-looking text and cannot be analyzed.`);
22
+ }
23
+ return contents;
24
+ }
25
+
26
+ export function readJsonFileBounded(filePath, options = {}) {
27
+ const label = options.label ?? "JSON file";
28
+ const contents = readUtf8FileBounded(filePath, { ...options, label });
29
+ try {
30
+ return JSON.parse(contents);
31
+ } catch (error) {
32
+ throw new Error(`${label} ${displayPath(filePath)} is not valid JSON: ${error.message}`);
33
+ }
34
+ }
35
+
36
+ export function writeUtf8FileSafely(filePath, contents) {
37
+ rejectSymlink(filePath);
38
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
39
+ rejectSymlink(filePath);
40
+ fs.writeFileSync(filePath, contents, "utf8");
41
+ }
42
+
43
+ function rejectSymlink(filePath) {
44
+ if (fs.existsSync(filePath) && fs.lstatSync(filePath).isSymbolicLink()) {
45
+ throw new Error(`Refusing to write through symlink: ${filePath}`);
46
+ }
47
+ }
48
+
49
+ function displayPath(filePath) {
50
+ return String(filePath).split(path.sep).join("/");
51
+ }