@roughen/core 0.3.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.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -0
  3. package/dist/baselines.d.ts +55 -0
  4. package/dist/baselines.js +42 -0
  5. package/dist/baselines.js.map +1 -0
  6. package/dist/brief.d.ts +45 -0
  7. package/dist/brief.js +110 -0
  8. package/dist/brief.js.map +1 -0
  9. package/dist/document.d.ts +5 -0
  10. package/dist/document.js +531 -0
  11. package/dist/document.js.map +1 -0
  12. package/dist/editorial.d.ts +53 -0
  13. package/dist/editorial.js +252 -0
  14. package/dist/editorial.js.map +1 -0
  15. package/dist/fixes.d.ts +8 -0
  16. package/dist/fixes.js +53 -0
  17. package/dist/fixes.js.map +1 -0
  18. package/dist/index.d.ts +73 -0
  19. package/dist/index.js +232 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/markdown-links.d.ts +12 -0
  22. package/dist/markdown-links.js +130 -0
  23. package/dist/markdown-links.js.map +1 -0
  24. package/dist/preserve.d.ts +36 -0
  25. package/dist/preserve.js +83 -0
  26. package/dist/preserve.js.map +1 -0
  27. package/dist/protected-tokens.d.ts +7 -0
  28. package/dist/protected-tokens.js +103 -0
  29. package/dist/protected-tokens.js.map +1 -0
  30. package/dist/ranges.d.ts +13 -0
  31. package/dist/ranges.js +35 -0
  32. package/dist/ranges.js.map +1 -0
  33. package/dist/rules.d.ts +12 -0
  34. package/dist/rules.js +336 -0
  35. package/dist/rules.js.map +1 -0
  36. package/dist/sentences.d.ts +3 -0
  37. package/dist/sentences.js +95 -0
  38. package/dist/sentences.js.map +1 -0
  39. package/dist/types.d.ts +140 -0
  40. package/dist/types.js +2 -0
  41. package/dist/types.js.map +1 -0
  42. package/dist-cjs/baselines.d.ts +55 -0
  43. package/dist-cjs/baselines.js +45 -0
  44. package/dist-cjs/baselines.js.map +1 -0
  45. package/dist-cjs/brief.d.ts +45 -0
  46. package/dist-cjs/brief.js +114 -0
  47. package/dist-cjs/brief.js.map +1 -0
  48. package/dist-cjs/document.d.ts +5 -0
  49. package/dist-cjs/document.js +535 -0
  50. package/dist-cjs/document.js.map +1 -0
  51. package/dist-cjs/editorial.d.ts +53 -0
  52. package/dist-cjs/editorial.js +263 -0
  53. package/dist-cjs/editorial.js.map +1 -0
  54. package/dist-cjs/fixes.d.ts +8 -0
  55. package/dist-cjs/fixes.js +57 -0
  56. package/dist-cjs/fixes.js.map +1 -0
  57. package/dist-cjs/index.d.ts +73 -0
  58. package/dist-cjs/index.js +258 -0
  59. package/dist-cjs/index.js.map +1 -0
  60. package/dist-cjs/markdown-links.d.ts +12 -0
  61. package/dist-cjs/markdown-links.js +134 -0
  62. package/dist-cjs/markdown-links.js.map +1 -0
  63. package/dist-cjs/package.json +1 -0
  64. package/dist-cjs/preserve.d.ts +36 -0
  65. package/dist-cjs/preserve.js +87 -0
  66. package/dist-cjs/preserve.js.map +1 -0
  67. package/dist-cjs/protected-tokens.d.ts +7 -0
  68. package/dist-cjs/protected-tokens.js +106 -0
  69. package/dist-cjs/protected-tokens.js.map +1 -0
  70. package/dist-cjs/ranges.d.ts +13 -0
  71. package/dist-cjs/ranges.js +41 -0
  72. package/dist-cjs/ranges.js.map +1 -0
  73. package/dist-cjs/rules.d.ts +12 -0
  74. package/dist-cjs/rules.js +340 -0
  75. package/dist-cjs/rules.js.map +1 -0
  76. package/dist-cjs/sentences.d.ts +3 -0
  77. package/dist-cjs/sentences.js +98 -0
  78. package/dist-cjs/sentences.js.map +1 -0
  79. package/dist-cjs/types.d.ts +140 -0
  80. package/dist-cjs/types.js +3 -0
  81. package/dist-cjs/types.js.map +1 -0
  82. package/package.json +45 -0
@@ -0,0 +1,95 @@
1
+ import { protectedTokenRanges } from './protected-tokens.js';
2
+ const abbreviations = new Set([
3
+ 'dr', 'mr', 'mrs', 'ms', 'prof', 'sr', 'jr', 'st',
4
+ 'e.g', 'i.e', 'etc', 'cf', 'vs', 'al',
5
+ 'inc', 'ltd', 'co', 'corp', 'dept', 'est',
6
+ ]);
7
+ /** Width of the letter code point ending at `end`, or 0. Surrogate pairs decode as `/u` does. */
8
+ function letterBefore(text, end) {
9
+ const low = text.charCodeAt(end - 1);
10
+ const high = text.charCodeAt(end - 2);
11
+ const width = low >= 0xDC00 && low <= 0xDFFF && high >= 0xD800 && high <= 0xDBFF ? 2 : end > 0 ? 1 : 0;
12
+ return width && /^\p{L}$/u.test(text.slice(end - width, end)) ? width : 0;
13
+ }
14
+ /**
15
+ * The chain `/([\p{L}]+(?:\.[\p{L}]+)*)$/u` matches on `text.slice(0, end)`,
16
+ * scanned backward in time proportional to the chain instead of the text.
17
+ */
18
+ function precedingChain(text, end) {
19
+ let start = end;
20
+ for (let width = 0;;) {
21
+ if ((width = letterBefore(text, start)))
22
+ start -= width;
23
+ else if (start < end && text[start - 1] === '.' && letterBefore(text, start - 1))
24
+ start--;
25
+ else
26
+ return text.slice(start, end);
27
+ }
28
+ }
29
+ /** Deterministic segmentation with original UTF-16 ranges; it does not infer authorship. */
30
+ export function segmentSentences(text, offset = 0) {
31
+ if (!Number.isSafeInteger(offset) || offset < 0)
32
+ throw new RangeError('Sentence offset must be a non-negative safe integer.');
33
+ const result = [];
34
+ const protectedRanges = protectedTokenRanges(text);
35
+ const push = (start, end) => {
36
+ while (start < end && /\s/.test(text[start]))
37
+ start++;
38
+ while (end > start && /\s/.test(text[end - 1]))
39
+ end--;
40
+ if (end <= start)
41
+ return;
42
+ const sentence = text.slice(start, end);
43
+ const wordCount = (sentence.match(/[\p{L}\p{N}]+(?:['’.-][\p{L}\p{N}]+)*/gu) ?? []).length;
44
+ if (wordCount)
45
+ result.push({ text: sentence, range: [offset + start, offset + end], wordCount });
46
+ };
47
+ let start = 0;
48
+ let protectedIndex = 0;
49
+ // Every mark before `settled` shares a terminal group already found not to end a sentence.
50
+ let settled = 0;
51
+ const opener = /[\p{Lu}\p{Lt}\p{N}"'“‘([{]/uy;
52
+ for (let i = 0; i < text.length; i++) {
53
+ const char = text[i];
54
+ if (!/[.!?]/.test(char) || i < settled)
55
+ continue;
56
+ while (protectedIndex < protectedRanges.length && protectedRanges[protectedIndex][1] <= i)
57
+ protectedIndex++;
58
+ const protectedRange = protectedRanges[protectedIndex];
59
+ if (protectedRange && i >= protectedRange[0] && i < protectedRange[1])
60
+ continue;
61
+ if (char === '.') {
62
+ if (text[i - 1] === '.' || text[i + 1] === '.')
63
+ continue;
64
+ if (/\d/.test(text[i - 1] ?? '') && /\d/.test(text[i + 1] ?? ''))
65
+ continue;
66
+ }
67
+ let end = i + 1;
68
+ while (/[.!?]/.test(text[end] ?? '') && end < text.length)
69
+ end++;
70
+ while (/["'”’\])}]/.test(text[end] ?? '') && end < text.length)
71
+ end++;
72
+ let next = end;
73
+ while (next < text.length && /\s/.test(text[next]))
74
+ next++;
75
+ opener.lastIndex = next;
76
+ if (!(next === text.length || (next > end && opener.test(text)))) {
77
+ settled = end;
78
+ continue;
79
+ }
80
+ // The abbreviation lookback runs last, so only a real boundary candidate pays for it.
81
+ if (char === '.') {
82
+ const preceding = precedingChain(text, i);
83
+ if (abbreviations.has(preceding.toLowerCase()))
84
+ continue;
85
+ if (/^(?:\p{Lu}\.)*\p{Lu}$/u.test(preceding))
86
+ continue;
87
+ }
88
+ push(start, end);
89
+ start = next;
90
+ i = next - 1;
91
+ }
92
+ push(start, text.length);
93
+ return result;
94
+ }
95
+ //# sourceMappingURL=sentences.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentences.js","sourceRoot":"","sources":["../src/sentences.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,140 @@
1
+ /** All ranges are half-open UTF-16 offsets into the untouched source string. */
2
+ export type Range = [number, number];
3
+ export type Format = 'text' | 'md' | 'mdx' | 'html' | 'source';
4
+ export type Severity = 'error' | 'warn' | 'info';
5
+ export type FixSafety = 'safe' | 'careful' | 'manual';
6
+ export interface Block {
7
+ kind: 'paragraph' | 'heading' | 'listItem' | 'blockquote' | 'code' | 'html';
8
+ /** A length-preserving prose view. Protected syntax is replaced with spaces. */
9
+ text: string;
10
+ sourceRange: Range;
11
+ depth?: number;
12
+ skip: boolean;
13
+ }
14
+ export interface Document {
15
+ source: string;
16
+ format: Format;
17
+ blocks: Block[];
18
+ protectedRanges: Range[];
19
+ /** Code: fences, spans, frontmatter, raw HTML code and expressions. A subset of protectedRanges. */
20
+ codeRanges: Range[];
21
+ /** Verbatim quotations and blockquotes. A subset of protectedRanges. */
22
+ quoteRanges: Range[];
23
+ }
24
+ export interface MarkdownLinkTarget {
25
+ /** Exact source spelling, including any Markdown backslash escapes. */
26
+ target: string;
27
+ sourceRange: Range;
28
+ kind: 'inline' | 'reference-definition';
29
+ }
30
+ export interface Sentence {
31
+ text: string;
32
+ range: Range;
33
+ wordCount: number;
34
+ }
35
+ export interface Finding {
36
+ ruleId: string;
37
+ severity: Severity;
38
+ message: string;
39
+ why: string;
40
+ range: Range;
41
+ loc: {
42
+ line: number;
43
+ column: number;
44
+ endLine: number;
45
+ endColumn: number;
46
+ };
47
+ fix?: {
48
+ range: Range;
49
+ text: string;
50
+ };
51
+ fixSafety: FixSafety;
52
+ /** Confidence in the described mechanical match, never authorship probability. */
53
+ confidence: number;
54
+ evidence?: Record<string, number | string>;
55
+ }
56
+ export type RuleFinding = Omit<Finding, 'ruleId' | 'severity' | 'loc'>;
57
+ export type RuleSetting = Severity | 'off' | [Severity | 'off', Record<string, unknown>];
58
+ /**
59
+ * Which human baseline the density rules measure against. 'article' is the
60
+ * 110 human articles every threshold was fit on; 'marketing' swaps in the
61
+ * thresholds fit on human marketing pages where they differ (list saturation).
62
+ */
63
+ export type Register = 'article' | 'marketing';
64
+ /** A character the project bans outright in copy, reported at every occurrence. */
65
+ export interface BannedCharacter {
66
+ character: string;
67
+ use?: string;
68
+ why?: string;
69
+ }
70
+ /** A pattern (a regular expression source) the project bans outright in copy. */
71
+ export interface BannedPattern {
72
+ pattern: string;
73
+ flags?: string;
74
+ message?: string;
75
+ use?: string;
76
+ }
77
+ export interface Config {
78
+ format?: Format;
79
+ fix?: boolean;
80
+ careful?: boolean;
81
+ register?: Register;
82
+ rules?: Record<string, RuleSetting>;
83
+ voice?: {
84
+ banned?: {
85
+ term: string;
86
+ use?: string;
87
+ }[];
88
+ required?: string[];
89
+ /** Absolute bans, whatever the density: em dashes on a site that forbids them, say. */
90
+ bannedCharacters?: (string | BannedCharacter)[];
91
+ bannedPatterns?: BannedPattern[];
92
+ };
93
+ }
94
+ export type DensityFamily = 'series' | 'contrast' | 'generalizer' | 'reveal' | 'emDash' | 'semicolon';
95
+ /** Instances of each structural habit in editable body prose, with the words they're measured against. */
96
+ export interface DensityProfile {
97
+ words: number;
98
+ instances: Record<DensityFamily, Range[]>;
99
+ }
100
+ export interface RuleContext {
101
+ document: Document;
102
+ config: Config;
103
+ register: Register;
104
+ options: Record<string, unknown>;
105
+ /** Sentences of editable body prose (paragraphs and list items), in source order. */
106
+ sentences: Sentence[];
107
+ profile: DensityProfile;
108
+ }
109
+ export interface Rule {
110
+ id: string;
111
+ severity: Severity;
112
+ description: string;
113
+ /** Registry order is the stable tie-breaker for overlapping fixes. */
114
+ check(context: RuleContext): RuleFinding[];
115
+ }
116
+ export interface Metrics {
117
+ sentenceWords: number[];
118
+ sentenceCount: number;
119
+ wordCount: number;
120
+ meanSentenceLength: number | null;
121
+ sentenceLengthCV: number | null;
122
+ meanAbsoluteSuccessiveDifference: number | null;
123
+ }
124
+ export interface Result {
125
+ /** With fix:true these findings refer to fixed, otherwise to the input. */
126
+ findings: Finding[];
127
+ /** No calibrated corpus exists yet. Never invent a texture or AI score. */
128
+ score: null;
129
+ calibration: {
130
+ status: 'unavailable';
131
+ reason: string;
132
+ };
133
+ metrics: Metrics;
134
+ fixed?: string;
135
+ fixes?: {
136
+ applied: number;
137
+ passes: number;
138
+ converged: boolean;
139
+ };
140
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,55 @@
1
+ /** Human-writing baselines behind Roughen's density and rhythm rules. */
2
+ export declare const baselines: {
3
+ readonly corpus: {
4
+ readonly id: "human-110";
5
+ readonly documents: 110;
6
+ readonly description: "Pre-2021 human-written technical, service, content and UX articles (discovery-001 and pilot-001 originals).";
7
+ };
8
+ /** 95th percentile of per-document instances per 1,000 body words (documents of 300+ words). */
9
+ readonly density: {
10
+ series: {
11
+ p95: number;
12
+ };
13
+ contrast: {
14
+ p95: number;
15
+ };
16
+ generalizer: {
17
+ p95: number;
18
+ };
19
+ reveal: {
20
+ p95: number;
21
+ };
22
+ emDash: {
23
+ p95: number;
24
+ };
25
+ semicolon: {
26
+ p95: number;
27
+ };
28
+ };
29
+ readonly sentences: {
30
+ /** 95th percentile of per-document mean sentence length, in words. */
31
+ readonly meanWordsP95: 22.67;
32
+ /** 90th percentile of individual human sentence lengths, in words. */
33
+ readonly longWords: 28;
34
+ };
35
+ readonly rhythm: {
36
+ /** 5th percentile of the coefficient of variation of sentence length. */
37
+ readonly cvP5: 0.341;
38
+ };
39
+ /** Thresholds fit on other registers. Anything a register doesn't set falls back to the values above. */
40
+ readonly registers: {
41
+ readonly marketing: {
42
+ readonly corpus: {
43
+ readonly id: "marketing-001-fit";
44
+ readonly documents: 164;
45
+ readonly measured: 112;
46
+ readonly description: "Pre-2021 human marketing pages (marketing-001), the 70% fit split by host. Unrated.";
47
+ };
48
+ readonly density: {
49
+ series: {
50
+ p95: number;
51
+ };
52
+ };
53
+ };
54
+ };
55
+ };
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.baselines = void 0;
4
+ /** Human-writing baselines behind Roughen's density and rhythm rules. */
5
+ exports.baselines = {
6
+ corpus: {
7
+ id: 'human-110',
8
+ documents: 110,
9
+ description: 'Pre-2021 human-written technical, service, content and UX articles (discovery-001 and pilot-001 originals).',
10
+ },
11
+ /** 95th percentile of per-document instances per 1,000 body words (documents of 300+ words). */
12
+ density: {
13
+ series: { p95: 8.43 },
14
+ contrast: { p95: 1.94 },
15
+ generalizer: { p95: 4.36 },
16
+ reveal: { p95: 0 },
17
+ emDash: { p95: 5.97 },
18
+ semicolon: { p95: 2.58 },
19
+ },
20
+ sentences: {
21
+ /** 95th percentile of per-document mean sentence length, in words. */
22
+ meanWordsP95: 22.67,
23
+ /** 90th percentile of individual human sentence lengths, in words. */
24
+ longWords: 28,
25
+ },
26
+ rhythm: {
27
+ /** 5th percentile of the coefficient of variation of sentence length. */
28
+ cvP5: 0.341,
29
+ },
30
+ /** Thresholds fit on other registers. Anything a register doesn't set falls back to the values above. */
31
+ registers: {
32
+ marketing: {
33
+ corpus: {
34
+ id: 'marketing-001-fit',
35
+ documents: 164,
36
+ measured: 112,
37
+ description: 'Pre-2021 human marketing pages (marketing-001), the 70% fit split by host. Unrated.',
38
+ },
39
+ density: {
40
+ series: { p95: 19.87 },
41
+ },
42
+ },
43
+ },
44
+ };
45
+ //# sourceMappingURL=baselines.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baselines.js","sourceRoot":"","sources":["../src/baselines.ts"],"names":[],"mappings":";;;AAGA,yEAAyE;AAC5D,QAAA,SAAS,GAAG;IACvB,MAAM,EAAE;QACN,EAAE,EAAE,WAAW;QACf,SAAS,EAAE,GAAG;QACd,WAAW,EAAE,6GAA6G;KAC3H;IACD,gGAAgG;IAChG,OAAO,EAAE;QACP,MAAM,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE;QACrB,QAAQ,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE;QACvB,WAAW,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE;QAC1B,MAAM,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE;QAClB,MAAM,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE;QACrB,SAAS,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE;KACwB;IAClD,SAAS,EAAE;QACT,sEAAsE;QACtE,YAAY,EAAE,KAAK;QACnB,sEAAsE;QACtE,SAAS,EAAE,EAAE;KACd;IACD,MAAM,EAAE;QACN,yEAAyE;QACzE,IAAI,EAAE,KAAK;KACZ;IACD,yGAAyG;IACzG,SAAS,EAAE;QACT,SAAS,EAAE;YACT,MAAM,EAAE;gBACN,EAAE,EAAE,mBAAmB;gBACvB,SAAS,EAAE,GAAG;gBACd,QAAQ,EAAE,GAAG;gBACb,WAAW,EAAE,qFAAqF;aACnG;YACD,OAAO,EAAE;gBACP,MAAM,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE;aACmC;SAC5D;KACF;CACO,CAAC"}
@@ -0,0 +1,45 @@
1
+ import type { Finding, Range, Result } from './types.js';
2
+ export interface BriefItem {
3
+ ruleId: string;
4
+ /** What to do, in one instruction. */
5
+ instruction: string;
6
+ /** Why the change helps a reader. */
7
+ why: string;
8
+ /** How many of the excerpts to rewrite. Density rules only need to drop below the baseline. */
9
+ rewriteAtLeast: number;
10
+ excerpts: {
11
+ text: string;
12
+ line: number;
13
+ range: [number, number];
14
+ }[];
15
+ }
16
+ export interface RevisionBrief {
17
+ /** False when nothing needs a model-assisted revision. */
18
+ needed: boolean;
19
+ items: BriefItem[];
20
+ /** Always-on rules for any rewrite: what must not change. */
21
+ constraints: string[];
22
+ /** The brief rendered as plain text for a prompt or hook message. */
23
+ text: string;
24
+ }
25
+ export interface BriefOptions {
26
+ /** Excerpts shown per rule. Default 8. */
27
+ maxExcerpts?: number;
28
+ /** Include info-severity findings. Default false. */
29
+ includeInfo?: boolean;
30
+ /** Extra constraints from the caller, such as project voice rules. */
31
+ constraints?: string[];
32
+ }
33
+ /**
34
+ * Deterministic revision instructions from lint findings. Roughen never calls
35
+ * a model; the host (Claude, Signal) does the rewrite and then checks it with
36
+ * comparePreservation. Single source of truth for how findings become
37
+ * rewrite instructions on every surface.
38
+ */
39
+ export declare function revisionBrief(source: string, result: Pick<Result, 'findings'>, options?: BriefOptions): RevisionBrief;
40
+ /**
41
+ * Findings inside `regions`, with each density rule's rewrite target scaled to
42
+ * the share of its instances found there. Single source of truth for
43
+ * region-scoped review: the plugin's edit hook and Signal's per-section revision.
44
+ */
45
+ export declare function scopeFindings(findings: readonly Finding[], regions: readonly Range[]): Finding[];
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.revisionBrief = revisionBrief;
4
+ exports.scopeFindings = scopeFindings;
5
+ const ranges_js_1 = require("./ranges.js");
6
+ const preserve_js_1 = require("./preserve.js");
7
+ const constraints = [
8
+ 'Keep every number, price, date, name, link, code sample and quotation exactly as written.',
9
+ "Don't add facts, examples, statistics or claims that aren't already in the text.",
10
+ 'Keep the headings and the order of sections.',
11
+ `Keep the length within ${Math.round(preserve_js_1.revisionLengthTolerance * 100)}% of the original.`,
12
+ "Change only the flagged passages. Leave sentences that aren't flagged alone.",
13
+ ];
14
+ // Bans are reported per occurrence and every one must go, whichever character or pattern it is.
15
+ const banRules = new Set(['voice/banned-character', 'voice/banned-pattern']);
16
+ function instructionFor(finding, group) {
17
+ const count = Number(finding.evidence?.count ?? 1);
18
+ const at = Number(finding.evidence?.rewriteAtLeast ?? 1);
19
+ switch (finding.ruleId) {
20
+ case 'voice/banned-character': {
21
+ const characters = [...new Set(group.map((item) => `“${item.evidence?.character}”`))].join(', ');
22
+ const advice = [...new Set(group.map((item) => item.evidence?.advice).filter(Boolean))].join(' ');
23
+ return `Remove banned characters: rewrite all ${group.length} of these passages without ${characters}, which the project bans outright.${advice ? ` ${advice}` : ''}`;
24
+ }
25
+ case 'voice/banned-pattern': return `Remove banned phrasing: rewrite all ${group.length} of these passages. The project bans each outright.`;
26
+ case 'structure/list-saturation': return `Cut comma series: rewrite at least ${at} of these ${count} "A, B, and C" lists so the sentence names the one or two items that matter and says something specific about them.`;
27
+ case 'structure/contrast-frame': return `Drop contrast formulas: rewrite at least ${at} of these ${count} "X, not Y" / "isn't X. It's Y" constructions as direct statements.`;
28
+ case 'hedge/generalizer': return `Replace hedges with specifics: in at least ${at} of these ${count} places, swap "usually/often/typically" for the actual condition, number or example, or cut it.`;
29
+ case 'structure/reveal-frame': return `Lead with the point: remove at least ${at} of these ${count} announcements ("The real question is…", ", which is why").`;
30
+ case 'punctuation/em-dash': return `Thin out em dashes: rewrite at least ${at} of these ${count} as commas, parentheses, a colon or two sentences. Keep the ones that mark a real interruption.`;
31
+ case 'punctuation/semicolon': return `Split semicolons: turn at least ${at} of these ${count} into two sentences, or a comma and "and", "but" or "so".`;
32
+ case 'rhythm/long-sentences': return `Shorten long sentences: split at least ${at} of these ${count} where the thought turns. Don't cut facts to do it.`;
33
+ case 'rhythm/low-variance': return 'Vary the rhythm in the highlighted run: join two short related sentences or break a long one where the thought turns.';
34
+ default: return finding.message;
35
+ }
36
+ }
37
+ /**
38
+ * Deterministic revision instructions from lint findings. Roughen never calls
39
+ * a model; the host (Claude, Signal) does the rewrite and then checks it with
40
+ * comparePreservation. Single source of truth for how findings become
41
+ * rewrite instructions on every surface.
42
+ */
43
+ function revisionBrief(source, result, options = {}) {
44
+ const max = options.maxExcerpts ?? 8;
45
+ const groups = new Map();
46
+ for (const finding of result.findings) {
47
+ if (finding.fixSafety !== 'manual' && finding.fix)
48
+ continue; // automatic fixes need no model
49
+ if (finding.severity === 'info' && !options.includeInfo)
50
+ continue;
51
+ if (finding.range[0] === finding.range[1] && !finding.ruleId.startsWith('voice/'))
52
+ continue;
53
+ groups.set(finding.ruleId, [...(groups.get(finding.ruleId) ?? []), finding]);
54
+ }
55
+ const items = [...groups].map(([ruleId, findings]) => ({
56
+ ruleId,
57
+ instruction: instructionFor(findings[0], findings),
58
+ why: findings[0].why,
59
+ rewriteAtLeast: banRules.has(ruleId) ? findings.length : Number(findings[0].evidence?.rewriteAtLeast ?? findings.length),
60
+ excerpts: findings.slice(0, max).map((finding) => ({
61
+ text: excerpt(source, finding.range),
62
+ // Lines come from `source`, so a caller can brief one section of a larger document.
63
+ line: source.slice(0, finding.range[0]).split('\n').length,
64
+ range: finding.range,
65
+ })),
66
+ }));
67
+ const allConstraints = [...constraints, ...(options.constraints ?? [])];
68
+ const text = items.length ? [
69
+ 'Revise the flagged passages below. Each item says what to change and why.',
70
+ '',
71
+ ...items.flatMap((item, index) => [
72
+ `${index + 1}. ${item.instruction}`,
73
+ ` Why: ${item.why.split('. ')[0]}.`,
74
+ ...item.excerpts.map((entry) => ` - line ${entry.line}: “${entry.text}”`),
75
+ ...(item.excerpts.length < (groups.get(item.ruleId)?.length ?? 0) ? [` - …and ${(groups.get(item.ruleId).length) - item.excerpts.length} more`] : []),
76
+ ]),
77
+ '',
78
+ 'Constraints:',
79
+ ...allConstraints.map((constraint) => `- ${constraint}`),
80
+ ].join('\n') : '';
81
+ return { needed: items.length > 0, items, constraints: allConstraints, text };
82
+ }
83
+ /** The flagged span with a little context, on one line. */
84
+ function excerpt(source, [start, end]) {
85
+ const from = Math.max(0, source.lastIndexOf('\n', start - 1) + 1, start - 60);
86
+ const newline = source.indexOf('\n', end);
87
+ const to = Math.min(newline < 0 ? source.length : newline, end + 60);
88
+ const text = source.slice(from, to).replace(/\s+/g, ' ').trim();
89
+ return `${from > 0 && source[from - 1] !== '\n' ? '…' : ''}${text}${to < source.length && source[to] !== '\n' ? '…' : ''}`;
90
+ }
91
+ /**
92
+ * Findings inside `regions`, with each density rule's rewrite target scaled to
93
+ * the share of its instances found there. Single source of truth for
94
+ * region-scoped review: the plugin's edit hook and Signal's per-section revision.
95
+ */
96
+ function scopeFindings(findings, regions) {
97
+ const totals = new Map();
98
+ for (const finding of findings)
99
+ totals.set(finding.ruleId, (totals.get(finding.ruleId) ?? 0) + 1);
100
+ const inside = findings.filter((finding) => regions.some((region) => (0, ranges_js_1.rangesIntersect)(region, finding.range)
101
+ || (finding.range[0] === finding.range[1] && finding.range[0] >= region[0] && finding.range[0] <= region[1])));
102
+ const shares = new Map();
103
+ for (const finding of inside)
104
+ shares.set(finding.ruleId, (shares.get(finding.ruleId) ?? 0) + 1);
105
+ return inside.map((finding) => {
106
+ const target = finding.evidence?.rewriteAtLeast;
107
+ if (target === undefined)
108
+ return finding;
109
+ const share = shares.get(finding.ruleId);
110
+ const scaled = Math.min(share, Math.ceil((Number(target) * share) / totals.get(finding.ruleId)));
111
+ return { ...finding, evidence: { ...finding.evidence, count: share, rewriteAtLeast: scaled } };
112
+ });
113
+ }
114
+ //# sourceMappingURL=brief.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brief.js","sourceRoot":"","sources":["../src/brief.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { Document, Format, MarkdownLinkTarget } from './types.js';
2
+ /** Single source of truth for the lossless prose view used by every rule. */
3
+ export declare function parseDocument(source: string, format?: Format): Document;
4
+ /** Literal Markdown targets outside canonical code/verbatim-quotation ranges. */
5
+ export declare function collectMarkdownLinkTargets(source: string, format?: Format): MarkdownLinkTarget[];