dravoice 0.1.3 → 0.2.0
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/README.md +39 -9
- package/package.json +1 -1
- package/src/index.js +113 -13
- package/src/v2/analyzers/discourse.js +7 -1
- package/src/v2/analyzers/evidence.js +3 -3
- package/src/v2/analyzers/register.js +28 -4
- package/src/v2/analyzers/rhetorical-shape.js +7 -1
- package/src/v2/analyzers/structure.js +109 -1
- package/src/v2/benchmark.js +83 -0
- package/src/v2/brief.js +41 -7
- package/src/v2/doctor.js +308 -0
- package/src/v2/document-model.js +78 -6
- package/src/v2/inspect.js +2 -2
- package/src/v2/profile.js +238 -19
- package/src/v2/prompt.js +10 -3
- package/src/v2/review.js +142 -16
- package/src/v2/revise-plan.js +111 -8
- package/src/v2/stylometry.js +11 -7
- package/src/v2/text-utils.js +5 -2
package/src/v2/revise-plan.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
styleDistanceFromDiagnostics,
|
|
14
14
|
} from "./stylometry.js";
|
|
15
15
|
|
|
16
|
-
const MAX_ACTIONS =
|
|
16
|
+
const MAX_ACTIONS = 10;
|
|
17
17
|
|
|
18
18
|
const EDITABILITY = {
|
|
19
19
|
evidence: 1.00,
|
|
@@ -36,11 +36,13 @@ export function revisePlanDraftV2({ file, voice, cwd = process.cwd(), maxActions
|
|
|
36
36
|
const draftProfile = buildVoiceProfileV2({ documents: [draftDocument] });
|
|
37
37
|
const familyDiagnostics = familyDiagnosticsFor(sourceProfile, draftProfile);
|
|
38
38
|
const rollingWindows = rollingWindowsFor({ sourceProfile, draftDocument });
|
|
39
|
+
const paragraphWindows = paragraphWindowsFor({ sourceProfile, draftDocument });
|
|
39
40
|
const actions = rankedActions({
|
|
40
41
|
sourceProfile,
|
|
41
42
|
draftDocument,
|
|
42
43
|
familyDiagnostics,
|
|
43
44
|
rollingWindows,
|
|
45
|
+
paragraphWindows,
|
|
44
46
|
maxActions,
|
|
45
47
|
});
|
|
46
48
|
|
|
@@ -61,6 +63,7 @@ export function revisePlanDraftV2({ file, voice, cwd = process.cwd(), maxActions
|
|
|
61
63
|
familyDrift: Object.fromEntries(Object.entries(familyDiagnostics).map(([family, item]) => [family, item.drift])),
|
|
62
64
|
thresholds: Object.fromEntries(Object.entries(familyDiagnostics).map(([family, item]) => [family, item.threshold])),
|
|
63
65
|
rollingWindows,
|
|
66
|
+
paragraphWindows,
|
|
64
67
|
},
|
|
65
68
|
actions,
|
|
66
69
|
};
|
|
@@ -81,7 +84,7 @@ export function renderRevisePlanV2(plan) {
|
|
|
81
84
|
|
|
82
85
|
for (const [family, score] of Object.entries(plan.summary.familyScores)) {
|
|
83
86
|
const drift = plan.summary.familyDrift[family];
|
|
84
|
-
lines.push(`- ${family}: ${score} (drift ${drift})`);
|
|
87
|
+
lines.push(`- ${familyLabel(family)}: ${score} (drift ${drift})`);
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
lines.push("");
|
|
@@ -93,17 +96,17 @@ export function renderRevisePlanV2(plan) {
|
|
|
93
96
|
|
|
94
97
|
lines.push("Start here:");
|
|
95
98
|
plan.actions.forEach((action, index) => {
|
|
96
|
-
lines.push(`${index + 1}. ${action.priority} ${action.family}
|
|
97
|
-
lines.push(`
|
|
98
|
-
lines.push(`
|
|
99
|
-
lines.push(` Why
|
|
100
|
-
lines.push(`
|
|
99
|
+
lines.push(`${index + 1}. ${priorityLabel(action.priority)} ${familyLabel(action.family)}`);
|
|
100
|
+
lines.push(` Where: ${unitLabel(action.unit)}`);
|
|
101
|
+
lines.push(` Priority score: ${action.actionScore}`);
|
|
102
|
+
lines.push(` Why: ${action.why}`);
|
|
103
|
+
lines.push(` Do this: ${action.reviseBy}`);
|
|
101
104
|
});
|
|
102
105
|
lines.push("");
|
|
103
106
|
return lines.join("\n");
|
|
104
107
|
}
|
|
105
108
|
|
|
106
|
-
function rankedActions({ sourceProfile, draftDocument, familyDiagnostics, rollingWindows, maxActions }) {
|
|
109
|
+
function rankedActions({ sourceProfile, draftDocument, familyDiagnostics, rollingWindows, paragraphWindows, maxActions }) {
|
|
107
110
|
const confidence = confidenceWeight(sourceProfile.source.confidence.band);
|
|
108
111
|
const actions = [
|
|
109
112
|
...evidenceActions({ sourceProfile, draftDocument, familyDiagnostics, confidence }),
|
|
@@ -111,6 +114,7 @@ function rankedActions({ sourceProfile, draftDocument, familyDiagnostics, rollin
|
|
|
111
114
|
...shapeActions({ sourceProfile, draftDocument, familyDiagnostics, confidence }),
|
|
112
115
|
...discourseActions({ sourceProfile, draftDocument, familyDiagnostics, confidence }),
|
|
113
116
|
...rollingWindowActions({ rollingWindows, confidence }),
|
|
117
|
+
...paragraphWindowActions({ paragraphWindows, confidence }),
|
|
114
118
|
...documentLevelActions({ sourceProfile, draftDocument, familyDiagnostics, confidence }),
|
|
115
119
|
].filter((action) => action.actionScore > 0);
|
|
116
120
|
|
|
@@ -322,6 +326,41 @@ function rollingWindowsFor({ sourceProfile, draftDocument }) {
|
|
|
322
326
|
.slice(0, 4);
|
|
323
327
|
}
|
|
324
328
|
|
|
329
|
+
function paragraphWindowsFor({ sourceProfile, draftDocument }) {
|
|
330
|
+
const result = [];
|
|
331
|
+
for (const [index, paragraph] of draftDocument.paragraphs.entries()) {
|
|
332
|
+
const sentences = draftDocument.sentences.filter((sentence) =>
|
|
333
|
+
sentence.line >= paragraph.line &&
|
|
334
|
+
sentence.line <= (paragraph.lineNumbers?.at(-1) ?? paragraph.line)
|
|
335
|
+
);
|
|
336
|
+
if (sentences.length < 2) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const paragraphProfile = buildVoiceProfileV2({ documents: [documentForParagraph(draftDocument, paragraph, sentences, index)] });
|
|
340
|
+
const diagnostics = familyDiagnosticsFor(sourceProfile, paragraphProfile);
|
|
341
|
+
const ranked = ["evidence", "rhythm", "discourse", "rhetoricalShape", "lexical"]
|
|
342
|
+
.map((family) => ({ family, ...diagnostics[family] }))
|
|
343
|
+
.sort((left, right) => right.drift - left.drift || (100 - right.score) - (100 - left.score));
|
|
344
|
+
const best = ranked[0];
|
|
345
|
+
if (best?.drift > 0) {
|
|
346
|
+
result.push({
|
|
347
|
+
family: best.family,
|
|
348
|
+
paragraph: index + 1,
|
|
349
|
+
startLine: paragraph.line,
|
|
350
|
+
endLine: paragraph.lineNumbers?.at(-1) ?? paragraph.line,
|
|
351
|
+
distance: best.distance,
|
|
352
|
+
drift: best.drift,
|
|
353
|
+
score: best.score,
|
|
354
|
+
threshold: best.threshold,
|
|
355
|
+
stability: best.stability,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return result
|
|
360
|
+
.sort((left, right) => right.drift - left.drift || left.startLine - right.startLine)
|
|
361
|
+
.slice(0, 4);
|
|
362
|
+
}
|
|
363
|
+
|
|
325
364
|
function rollingWindowStarts(sentenceCount, windowSize, stride) {
|
|
326
365
|
const starts = [];
|
|
327
366
|
for (let start = 0; start <= sentenceCount - windowSize; start += stride) {
|
|
@@ -363,6 +402,29 @@ function documentForSentences(draftDocument, sentences, windowIndex) {
|
|
|
363
402
|
};
|
|
364
403
|
}
|
|
365
404
|
|
|
405
|
+
function documentForParagraph(draftDocument, paragraph, sentences, paragraphIndex) {
|
|
406
|
+
const block = {
|
|
407
|
+
type: paragraph.type,
|
|
408
|
+
line: paragraph.line,
|
|
409
|
+
heading: paragraph.heading,
|
|
410
|
+
headingId: paragraph.headingId,
|
|
411
|
+
headingDepth: 0,
|
|
412
|
+
lines: [paragraph.text],
|
|
413
|
+
lineNumbers: paragraph.lineNumbers ?? [paragraph.line],
|
|
414
|
+
};
|
|
415
|
+
return {
|
|
416
|
+
file: `${draftDocument.file ?? "draft"}#paragraph-${paragraphIndex + 1}`,
|
|
417
|
+
path: draftDocument.path,
|
|
418
|
+
headings: [],
|
|
419
|
+
sections: [{ heading: null, blocks: [block] }],
|
|
420
|
+
blocks: [block],
|
|
421
|
+
paragraphs: [paragraph],
|
|
422
|
+
sentences,
|
|
423
|
+
wordCount: sentences.reduce((sum, sentence) => sum + sentence.tokens.length, 0),
|
|
424
|
+
text: paragraph.text,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
366
428
|
function rollingWindowActions({ rollingWindows, confidence }) {
|
|
367
429
|
return rollingWindows.map((window, index) => makeAction({
|
|
368
430
|
family: window.family,
|
|
@@ -378,6 +440,21 @@ function rollingWindowActions({ rollingWindows, confidence }) {
|
|
|
378
440
|
}));
|
|
379
441
|
}
|
|
380
442
|
|
|
443
|
+
function paragraphWindowActions({ paragraphWindows, confidence }) {
|
|
444
|
+
return paragraphWindows.map((window, index) => makeAction({
|
|
445
|
+
family: window.family,
|
|
446
|
+
ordinal: `paragraph-${index + 1}`,
|
|
447
|
+
priority: window.family === "evidence" ? "review" : "consider",
|
|
448
|
+
unit: { type: "paragraph", line: window.startLine, endLine: window.endLine },
|
|
449
|
+
confidence,
|
|
450
|
+
drift: window.drift,
|
|
451
|
+
stability: window.stability,
|
|
452
|
+
localMismatch: Math.min(0.45, window.drift / Math.max(1, window.drift + 0.5)),
|
|
453
|
+
why: `Paragraph ${window.paragraph} shows localized ${window.family} drift beyond the writer's calibrated range.`,
|
|
454
|
+
reviseBy: rollingWindowReviseBy(window.family),
|
|
455
|
+
}));
|
|
456
|
+
}
|
|
457
|
+
|
|
381
458
|
function rollingWindowReviseBy(family) {
|
|
382
459
|
if (family === "evidence") {
|
|
383
460
|
return "Add or move concrete support into this local passage, or narrow the unsupported claims in the same window.";
|
|
@@ -435,3 +512,29 @@ function resolvePath(cwd, value) {
|
|
|
435
512
|
function capitalize(value) {
|
|
436
513
|
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
437
514
|
}
|
|
515
|
+
|
|
516
|
+
function familyLabel(family) {
|
|
517
|
+
return {
|
|
518
|
+
evidence: "Evidence",
|
|
519
|
+
rhythm: "Rhythm",
|
|
520
|
+
rhetoricalShape: "Rhetorical shape",
|
|
521
|
+
discourse: "Discourse",
|
|
522
|
+
lexical: "Lexical style",
|
|
523
|
+
register: "Register",
|
|
524
|
+
structure: "Structure",
|
|
525
|
+
}[family] ?? capitalize(family);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function priorityLabel(priority) {
|
|
529
|
+
return priority === "review" ? "Review" : "Consider";
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function unitLabel(unit) {
|
|
533
|
+
if (unit.type === "window") {
|
|
534
|
+
return `sentence window at lines ${unit.line}-${unit.endLine}`;
|
|
535
|
+
}
|
|
536
|
+
if (unit.type === "paragraph") {
|
|
537
|
+
return `paragraph at lines ${unit.line}-${unit.endLine}`;
|
|
538
|
+
}
|
|
539
|
+
return `${unit.type} at line ${unit.line}`;
|
|
540
|
+
}
|
package/src/v2/stylometry.js
CHANGED
|
@@ -159,9 +159,9 @@ function discourseDistance(source, draft) {
|
|
|
159
159
|
const transitionDelta = rateMapDistance(source.transitionRates, draft.transitionRates);
|
|
160
160
|
const callbackDelta = Math.abs((source.sentenceCallbacks ?? 0) - (draft.sentenceCallbacks ?? 0));
|
|
161
161
|
return weightedMean([
|
|
162
|
-
[transitionDelta, 0.
|
|
163
|
-
[topItemDistance(source.transitionBigrams, draft.transitionBigrams), 0.
|
|
164
|
-
[topItemDistance(source.transitionTrigrams, draft.transitionTrigrams), 0.
|
|
162
|
+
[transitionDelta, 0.65],
|
|
163
|
+
[topItemDistance(source.transitionBigrams, draft.transitionBigrams), 0.10],
|
|
164
|
+
[topItemDistance(source.transitionTrigrams, draft.transitionTrigrams), 0.025],
|
|
165
165
|
[callbackDelta, 0.20],
|
|
166
166
|
]);
|
|
167
167
|
}
|
|
@@ -188,10 +188,14 @@ function shapeDistance(source, draft) {
|
|
|
188
188
|
|
|
189
189
|
function structureDistance(source, draft) {
|
|
190
190
|
return weightedMean([
|
|
191
|
-
[distributionDelta(source.sectionWords, draft.sectionWords), 0.
|
|
192
|
-
[distributionDelta(source.headingCount, draft.headingCount), 0.
|
|
193
|
-
[
|
|
194
|
-
[
|
|
191
|
+
[distributionDelta(source.sectionWords, draft.sectionWords), 0.30],
|
|
192
|
+
[distributionDelta(source.headingCount, draft.headingCount), 0.16],
|
|
193
|
+
[distributionDelta(source.maxHeadingDepth, draft.maxHeadingDepth), 0.12],
|
|
194
|
+
[topItemDistance(source.sectionOrderPatterns, draft.sectionOrderPatterns), 0.12],
|
|
195
|
+
[topItemDistance(source.listPlacementPatterns, draft.listPlacementPatterns), 0.08],
|
|
196
|
+
[topItemDistance(source.quotePlacementPatterns, draft.quotePlacementPatterns), 0.08],
|
|
197
|
+
[Math.abs((source.listDocumentRate ?? 0) - (draft.listDocumentRate ?? 0)), 0.12],
|
|
198
|
+
[Math.abs((source.quoteDocumentRate ?? 0) - (draft.quoteDocumentRate ?? 0)), 0.08],
|
|
195
199
|
[sequenceDistance(source.openingMoves, draft.openingMoves), 0.15],
|
|
196
200
|
]);
|
|
197
201
|
}
|
package/src/v2/text-utils.js
CHANGED
|
@@ -75,17 +75,20 @@ export function characterNgrams(text, size = 3) {
|
|
|
75
75
|
|
|
76
76
|
export function distribution(values) {
|
|
77
77
|
if (!values.length) {
|
|
78
|
-
return { count: 0, min: 0, max: 0, mean: 0, median: 0, p25: 0, p75: 0 };
|
|
78
|
+
return { count: 0, min: 0, max: 0, mean: 0, median: 0, p25: 0, p75: 0, stdev: 0 };
|
|
79
79
|
}
|
|
80
80
|
const sorted = [...values].sort((a, b) => a - b);
|
|
81
|
+
const mean = sorted.reduce((sum, value) => sum + value, 0) / sorted.length;
|
|
82
|
+
const variance = sorted.reduce((sum, value) => sum + (value - mean) ** 2, 0) / sorted.length;
|
|
81
83
|
return {
|
|
82
84
|
count: sorted.length,
|
|
83
85
|
min: sorted[0],
|
|
84
86
|
max: sorted[sorted.length - 1],
|
|
85
|
-
mean: round(
|
|
87
|
+
mean: round(mean, 2),
|
|
86
88
|
median: percentile(sorted, 0.5),
|
|
87
89
|
p25: percentile(sorted, 0.25),
|
|
88
90
|
p75: percentile(sorted, 0.75),
|
|
91
|
+
stdev: round(Math.sqrt(variance), 2),
|
|
89
92
|
};
|
|
90
93
|
}
|
|
91
94
|
|