@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.
- package/LICENSE +21 -0
- package/README.md +17 -0
- package/dist/baselines.d.ts +55 -0
- package/dist/baselines.js +42 -0
- package/dist/baselines.js.map +1 -0
- package/dist/brief.d.ts +45 -0
- package/dist/brief.js +110 -0
- package/dist/brief.js.map +1 -0
- package/dist/document.d.ts +5 -0
- package/dist/document.js +531 -0
- package/dist/document.js.map +1 -0
- package/dist/editorial.d.ts +53 -0
- package/dist/editorial.js +252 -0
- package/dist/editorial.js.map +1 -0
- package/dist/fixes.d.ts +8 -0
- package/dist/fixes.js +53 -0
- package/dist/fixes.js.map +1 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.js +232 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown-links.d.ts +12 -0
- package/dist/markdown-links.js +130 -0
- package/dist/markdown-links.js.map +1 -0
- package/dist/preserve.d.ts +36 -0
- package/dist/preserve.js +83 -0
- package/dist/preserve.js.map +1 -0
- package/dist/protected-tokens.d.ts +7 -0
- package/dist/protected-tokens.js +103 -0
- package/dist/protected-tokens.js.map +1 -0
- package/dist/ranges.d.ts +13 -0
- package/dist/ranges.js +35 -0
- package/dist/ranges.js.map +1 -0
- package/dist/rules.d.ts +12 -0
- package/dist/rules.js +336 -0
- package/dist/rules.js.map +1 -0
- package/dist/sentences.d.ts +3 -0
- package/dist/sentences.js +95 -0
- package/dist/sentences.js.map +1 -0
- package/dist/types.d.ts +140 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist-cjs/baselines.d.ts +55 -0
- package/dist-cjs/baselines.js +45 -0
- package/dist-cjs/baselines.js.map +1 -0
- package/dist-cjs/brief.d.ts +45 -0
- package/dist-cjs/brief.js +114 -0
- package/dist-cjs/brief.js.map +1 -0
- package/dist-cjs/document.d.ts +5 -0
- package/dist-cjs/document.js +535 -0
- package/dist-cjs/document.js.map +1 -0
- package/dist-cjs/editorial.d.ts +53 -0
- package/dist-cjs/editorial.js +263 -0
- package/dist-cjs/editorial.js.map +1 -0
- package/dist-cjs/fixes.d.ts +8 -0
- package/dist-cjs/fixes.js +57 -0
- package/dist-cjs/fixes.js.map +1 -0
- package/dist-cjs/index.d.ts +73 -0
- package/dist-cjs/index.js +258 -0
- package/dist-cjs/index.js.map +1 -0
- package/dist-cjs/markdown-links.d.ts +12 -0
- package/dist-cjs/markdown-links.js +134 -0
- package/dist-cjs/markdown-links.js.map +1 -0
- package/dist-cjs/package.json +1 -0
- package/dist-cjs/preserve.d.ts +36 -0
- package/dist-cjs/preserve.js +87 -0
- package/dist-cjs/preserve.js.map +1 -0
- package/dist-cjs/protected-tokens.d.ts +7 -0
- package/dist-cjs/protected-tokens.js +106 -0
- package/dist-cjs/protected-tokens.js.map +1 -0
- package/dist-cjs/ranges.d.ts +13 -0
- package/dist-cjs/ranges.js +41 -0
- package/dist-cjs/ranges.js.map +1 -0
- package/dist-cjs/rules.d.ts +12 -0
- package/dist-cjs/rules.js +340 -0
- package/dist-cjs/rules.js.map +1 -0
- package/dist-cjs/sentences.d.ts +3 -0
- package/dist-cjs/sentences.js +98 -0
- package/dist-cjs/sentences.js.map +1 -0
- package/dist-cjs/types.d.ts +140 -0
- package/dist-cjs/types.js +3 -0
- package/dist-cjs/types.js.map +1 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Upforge
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @roughen/core
|
|
2
|
+
|
|
3
|
+
Deterministic local prose linting. Zero runtime dependencies, model calls, telemetry, or network access. ESM and CommonJS exports include TypeScript declarations.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { lint } from '@roughen/core';
|
|
7
|
+
const result = lint('The crew arrived.', { format: 'md', fix: true });
|
|
8
|
+
// result.fixed === 'The crew arrived.'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Findings explain the mechanical issue and expose UTF-16 source ranges, line/column locations, confidence in the match, and fix safety. With fixes enabled, findings refer to the fixed text. Quotes, code, Markdown tables, links, JSX expressions, and language/emoji joiners are protected. Careful fixes require explicit opt-in; manual edits never auto-apply.
|
|
12
|
+
|
|
13
|
+
`collectMarkdownLinkTargets(text, 'md')` exposes raw inline and reference-definition targets with source ranges, using the same grammar as the prose parser. Reference definitions contribute their target once; this helper doesn't resolve reference usage or verify that destinations exist. Code and verbatim quotations follow the parser's exclusions. The research runner uses it to catch references lost during reconstruction, including relative paths and fragments.
|
|
14
|
+
|
|
15
|
+
`register: 'marketing'` measures list saturation against human marketing pages (a provisional threshold fit on the unrated marketing-001 fit split; see [site-pass-001](../../research/corpus/reports/site-pass-001.md)) and keeps every other baseline. `voice.bannedCharacters` and `voice.bannedPatterns` are absolute house rules: every occurrence in editable prose is an error, and `judgeRevision` rejects a revision that adds one.
|
|
16
|
+
|
|
17
|
+
Production thresholds remain unvalidated and score is null. Sentence metrics are descriptive and don't identify authorship; the pilot and expanded discovery corpus do not establish production calibration. Supports text, Markdown, HTML fragments, and conservative MDX; source-code literal extraction isn't supported yet. MIT.
|
|
@@ -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,42 @@
|
|
|
1
|
+
/** Human-writing baselines behind Roughen's density and rhythm rules. */
|
|
2
|
+
export const baselines = {
|
|
3
|
+
corpus: {
|
|
4
|
+
id: 'human-110',
|
|
5
|
+
documents: 110,
|
|
6
|
+
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
|
+
density: {
|
|
10
|
+
series: { p95: 8.43 },
|
|
11
|
+
contrast: { p95: 1.94 },
|
|
12
|
+
generalizer: { p95: 4.36 },
|
|
13
|
+
reveal: { p95: 0 },
|
|
14
|
+
emDash: { p95: 5.97 },
|
|
15
|
+
semicolon: { p95: 2.58 },
|
|
16
|
+
},
|
|
17
|
+
sentences: {
|
|
18
|
+
/** 95th percentile of per-document mean sentence length, in words. */
|
|
19
|
+
meanWordsP95: 22.67,
|
|
20
|
+
/** 90th percentile of individual human sentence lengths, in words. */
|
|
21
|
+
longWords: 28,
|
|
22
|
+
},
|
|
23
|
+
rhythm: {
|
|
24
|
+
/** 5th percentile of the coefficient of variation of sentence length. */
|
|
25
|
+
cvP5: 0.341,
|
|
26
|
+
},
|
|
27
|
+
/** Thresholds fit on other registers. Anything a register doesn't set falls back to the values above. */
|
|
28
|
+
registers: {
|
|
29
|
+
marketing: {
|
|
30
|
+
corpus: {
|
|
31
|
+
id: 'marketing-001-fit',
|
|
32
|
+
documents: 164,
|
|
33
|
+
measured: 112,
|
|
34
|
+
description: 'Pre-2021 human marketing pages (marketing-001), the 70% fit split by host. Unrated.',
|
|
35
|
+
},
|
|
36
|
+
density: {
|
|
37
|
+
series: { p95: 19.87 },
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=baselines.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baselines.js","sourceRoot":"","sources":["../src/baselines.ts"],"names":[],"mappings":"AAGA,yEAAyE;AACzE,MAAM,CAAC,MAAM,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"}
|
package/dist/brief.d.ts
ADDED
|
@@ -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[];
|
package/dist/brief.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { rangesIntersect } from './ranges.js';
|
|
2
|
+
import { revisionLengthTolerance } from './preserve.js';
|
|
3
|
+
const constraints = [
|
|
4
|
+
'Keep every number, price, date, name, link, code sample and quotation exactly as written.',
|
|
5
|
+
"Don't add facts, examples, statistics or claims that aren't already in the text.",
|
|
6
|
+
'Keep the headings and the order of sections.',
|
|
7
|
+
`Keep the length within ${Math.round(revisionLengthTolerance * 100)}% of the original.`,
|
|
8
|
+
"Change only the flagged passages. Leave sentences that aren't flagged alone.",
|
|
9
|
+
];
|
|
10
|
+
// Bans are reported per occurrence and every one must go, whichever character or pattern it is.
|
|
11
|
+
const banRules = new Set(['voice/banned-character', 'voice/banned-pattern']);
|
|
12
|
+
function instructionFor(finding, group) {
|
|
13
|
+
const count = Number(finding.evidence?.count ?? 1);
|
|
14
|
+
const at = Number(finding.evidence?.rewriteAtLeast ?? 1);
|
|
15
|
+
switch (finding.ruleId) {
|
|
16
|
+
case 'voice/banned-character': {
|
|
17
|
+
const characters = [...new Set(group.map((item) => `“${item.evidence?.character}”`))].join(', ');
|
|
18
|
+
const advice = [...new Set(group.map((item) => item.evidence?.advice).filter(Boolean))].join(' ');
|
|
19
|
+
return `Remove banned characters: rewrite all ${group.length} of these passages without ${characters}, which the project bans outright.${advice ? ` ${advice}` : ''}`;
|
|
20
|
+
}
|
|
21
|
+
case 'voice/banned-pattern': return `Remove banned phrasing: rewrite all ${group.length} of these passages. The project bans each outright.`;
|
|
22
|
+
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.`;
|
|
23
|
+
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.`;
|
|
24
|
+
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.`;
|
|
25
|
+
case 'structure/reveal-frame': return `Lead with the point: remove at least ${at} of these ${count} announcements ("The real question is…", ", which is why").`;
|
|
26
|
+
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.`;
|
|
27
|
+
case 'punctuation/semicolon': return `Split semicolons: turn at least ${at} of these ${count} into two sentences, or a comma and "and", "but" or "so".`;
|
|
28
|
+
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.`;
|
|
29
|
+
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.';
|
|
30
|
+
default: return finding.message;
|
|
31
|
+
}
|
|
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 function revisionBrief(source, result, options = {}) {
|
|
40
|
+
const max = options.maxExcerpts ?? 8;
|
|
41
|
+
const groups = new Map();
|
|
42
|
+
for (const finding of result.findings) {
|
|
43
|
+
if (finding.fixSafety !== 'manual' && finding.fix)
|
|
44
|
+
continue; // automatic fixes need no model
|
|
45
|
+
if (finding.severity === 'info' && !options.includeInfo)
|
|
46
|
+
continue;
|
|
47
|
+
if (finding.range[0] === finding.range[1] && !finding.ruleId.startsWith('voice/'))
|
|
48
|
+
continue;
|
|
49
|
+
groups.set(finding.ruleId, [...(groups.get(finding.ruleId) ?? []), finding]);
|
|
50
|
+
}
|
|
51
|
+
const items = [...groups].map(([ruleId, findings]) => ({
|
|
52
|
+
ruleId,
|
|
53
|
+
instruction: instructionFor(findings[0], findings),
|
|
54
|
+
why: findings[0].why,
|
|
55
|
+
rewriteAtLeast: banRules.has(ruleId) ? findings.length : Number(findings[0].evidence?.rewriteAtLeast ?? findings.length),
|
|
56
|
+
excerpts: findings.slice(0, max).map((finding) => ({
|
|
57
|
+
text: excerpt(source, finding.range),
|
|
58
|
+
// Lines come from `source`, so a caller can brief one section of a larger document.
|
|
59
|
+
line: source.slice(0, finding.range[0]).split('\n').length,
|
|
60
|
+
range: finding.range,
|
|
61
|
+
})),
|
|
62
|
+
}));
|
|
63
|
+
const allConstraints = [...constraints, ...(options.constraints ?? [])];
|
|
64
|
+
const text = items.length ? [
|
|
65
|
+
'Revise the flagged passages below. Each item says what to change and why.',
|
|
66
|
+
'',
|
|
67
|
+
...items.flatMap((item, index) => [
|
|
68
|
+
`${index + 1}. ${item.instruction}`,
|
|
69
|
+
` Why: ${item.why.split('. ')[0]}.`,
|
|
70
|
+
...item.excerpts.map((entry) => ` - line ${entry.line}: “${entry.text}”`),
|
|
71
|
+
...(item.excerpts.length < (groups.get(item.ruleId)?.length ?? 0) ? [` - …and ${(groups.get(item.ruleId).length) - item.excerpts.length} more`] : []),
|
|
72
|
+
]),
|
|
73
|
+
'',
|
|
74
|
+
'Constraints:',
|
|
75
|
+
...allConstraints.map((constraint) => `- ${constraint}`),
|
|
76
|
+
].join('\n') : '';
|
|
77
|
+
return { needed: items.length > 0, items, constraints: allConstraints, text };
|
|
78
|
+
}
|
|
79
|
+
/** The flagged span with a little context, on one line. */
|
|
80
|
+
function excerpt(source, [start, end]) {
|
|
81
|
+
const from = Math.max(0, source.lastIndexOf('\n', start - 1) + 1, start - 60);
|
|
82
|
+
const newline = source.indexOf('\n', end);
|
|
83
|
+
const to = Math.min(newline < 0 ? source.length : newline, end + 60);
|
|
84
|
+
const text = source.slice(from, to).replace(/\s+/g, ' ').trim();
|
|
85
|
+
return `${from > 0 && source[from - 1] !== '\n' ? '…' : ''}${text}${to < source.length && source[to] !== '\n' ? '…' : ''}`;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Findings inside `regions`, with each density rule's rewrite target scaled to
|
|
89
|
+
* the share of its instances found there. Single source of truth for
|
|
90
|
+
* region-scoped review: the plugin's edit hook and Signal's per-section revision.
|
|
91
|
+
*/
|
|
92
|
+
export function scopeFindings(findings, regions) {
|
|
93
|
+
const totals = new Map();
|
|
94
|
+
for (const finding of findings)
|
|
95
|
+
totals.set(finding.ruleId, (totals.get(finding.ruleId) ?? 0) + 1);
|
|
96
|
+
const inside = findings.filter((finding) => regions.some((region) => rangesIntersect(region, finding.range)
|
|
97
|
+
|| (finding.range[0] === finding.range[1] && finding.range[0] >= region[0] && finding.range[0] <= region[1])));
|
|
98
|
+
const shares = new Map();
|
|
99
|
+
for (const finding of inside)
|
|
100
|
+
shares.set(finding.ruleId, (shares.get(finding.ruleId) ?? 0) + 1);
|
|
101
|
+
return inside.map((finding) => {
|
|
102
|
+
const target = finding.evidence?.rewriteAtLeast;
|
|
103
|
+
if (target === undefined)
|
|
104
|
+
return finding;
|
|
105
|
+
const share = shares.get(finding.ruleId);
|
|
106
|
+
const scaled = Math.min(share, Math.ceil((Number(target) * share) / totals.get(finding.ruleId)));
|
|
107
|
+
return { ...finding, evidence: { ...finding.evidence, count: share, rewriteAtLeast: scaled } };
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
//# 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[];
|