@tduyng/pi-timbre 0.0.0-stage → 0.0.2
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 +178 -0
- package/README.md +156 -2
- package/package.json +45 -4
- package/src/README.md +385 -0
- package/src/code.ts +154 -0
- package/src/config.ts +21 -0
- package/src/guidance/catalog.md +17 -0
- package/src/guidance/review.md +438 -0
- package/src/guide.ts +26 -0
- package/src/index.ts +695 -0
- package/src/memory.ts +238 -0
- package/src/patterns.ts +255 -0
- package/src/register.ts +35 -0
- package/src/report.ts +205 -0
- package/src/rules.ts +37 -0
- package/src/scan.ts +328 -0
- package/src/segments.ts +81 -0
- package/src/stats.ts +112 -0
package/src/stats.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export function sentences(text: string): string[] {
|
|
2
|
+
return text
|
|
3
|
+
.split(/(?<=[.!?])\s+/)
|
|
4
|
+
.map((s) => s.trim())
|
|
5
|
+
.filter((s) => s.length > 0)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type Burstiness = {
|
|
9
|
+
cov: number
|
|
10
|
+
mean: number
|
|
11
|
+
count: number
|
|
12
|
+
monotoneRun: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function burstiness(text: string): Burstiness {
|
|
16
|
+
const lens = sentences(text).map((s) => s.split(/\s+/).length)
|
|
17
|
+
const count = lens.length
|
|
18
|
+
if (count === 0) return { cov: 0, mean: 0, count: 0, monotoneRun: 0 }
|
|
19
|
+
|
|
20
|
+
const mean = lens.reduce((a, b) => a + b, 0) / count
|
|
21
|
+
const variance = lens.reduce((a, b) => a + (b - mean) ** 2, 0) / count
|
|
22
|
+
const cov = mean === 0 ? 0 : Math.sqrt(variance) / mean
|
|
23
|
+
|
|
24
|
+
let run = 1
|
|
25
|
+
let best = 1
|
|
26
|
+
for (let i = 1; i < count; i++) {
|
|
27
|
+
run = Math.abs(lens[i]! - lens[i - 1]!) <= 3 ? run + 1 : 1
|
|
28
|
+
best = Math.max(best, run)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return { cov, mean, count, monotoneRun: count === 1 ? 1 : best }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function wordCount(text: string): number {
|
|
35
|
+
return text.split(/\s+/).filter((w) => w !== '').length
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function isMonotone(b: Burstiness): boolean {
|
|
39
|
+
return b.count >= 6 && b.cov < 0.35
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function hasStaccatoRun(text: string): boolean {
|
|
43
|
+
const lens = sentences(text).map((s) => s.split(/\s+/).length)
|
|
44
|
+
let run = 0
|
|
45
|
+
for (const n of lens) {
|
|
46
|
+
run = n < 9 ? run + 1 : 0
|
|
47
|
+
if (run >= 4) return true
|
|
48
|
+
}
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function openingOf(sentence: string): string {
|
|
53
|
+
const words = sentence.toLowerCase().match(/[a-z0-9']+/g) ?? []
|
|
54
|
+
if (words.length < 2) return ''
|
|
55
|
+
return `${words[0]} ${words[1]}`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function repeatedOpening(text: string, run = 3): string | null {
|
|
59
|
+
const openings = sentences(text).map(openingOf)
|
|
60
|
+
let count = 0
|
|
61
|
+
let prev = ''
|
|
62
|
+
for (const o of openings) {
|
|
63
|
+
count = o !== '' && o === prev ? count + 1 : 1
|
|
64
|
+
prev = o
|
|
65
|
+
if (o !== '' && count >= run) return o
|
|
66
|
+
}
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const SUBORDINATORS = [
|
|
71
|
+
'who',
|
|
72
|
+
'whom',
|
|
73
|
+
'whose',
|
|
74
|
+
'which',
|
|
75
|
+
'although',
|
|
76
|
+
'because',
|
|
77
|
+
'since',
|
|
78
|
+
'whereas',
|
|
79
|
+
'despite',
|
|
80
|
+
'unless',
|
|
81
|
+
'until'
|
|
82
|
+
] as const
|
|
83
|
+
|
|
84
|
+
const SUBORDINATOR_RE = new RegExp(
|
|
85
|
+
`\\b(?:${SUBORDINATORS.join('|')})\\b|\\bin order to\\b|\\bresulting in\\b|\\bleading to\\b`,
|
|
86
|
+
'gi'
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
function subordinatorCount(sentence: string): number {
|
|
90
|
+
SUBORDINATOR_RE.lastIndex = 0
|
|
91
|
+
return (sentence.match(SUBORDINATOR_RE) ?? []).length
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function hasClauseStack(text: string, perSentence = 3, minSentences = 2): boolean {
|
|
95
|
+
let stacked = 0
|
|
96
|
+
for (const s of sentences(text)) {
|
|
97
|
+
if (subordinatorCount(s) >= perSentence) {
|
|
98
|
+
stacked++
|
|
99
|
+
if (stacked >= minSentences) return true
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return false
|
|
103
|
+
}
|
|
104
|
+
export function hasLongSentenceRun(text: string, limit = 28, run = 3): boolean {
|
|
105
|
+
const lens = sentences(text).map((s) => s.split(/\s+/).length)
|
|
106
|
+
let count = 0
|
|
107
|
+
for (const n of lens) {
|
|
108
|
+
count = n > limit ? count + 1 : 0
|
|
109
|
+
if (count >= run) return true
|
|
110
|
+
}
|
|
111
|
+
return false
|
|
112
|
+
}
|