agent-simple-english 0.1.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/.claude-plugin/marketplace.json +23 -0
- package/.claude-plugin/plugin.json +12 -0
- package/LICENSE +21 -0
- package/README.md +435 -0
- package/THIRD_PARTY_NOTICES.md +13 -0
- package/commands/ste.md +13 -0
- package/hooks/hooks.json +58 -0
- package/package.json +64 -0
- package/src/adapter/commit-message.ts +472 -0
- package/src/adapter/feedback.ts +40 -0
- package/src/adapter/rule-summary.ts +79 -0
- package/src/cli/hook.ts +681 -0
- package/src/cli/main.ts +201 -0
- package/src/cli/session-command.ts +78 -0
- package/src/cli/session-state.ts +214 -0
- package/src/config/load.ts +85 -0
- package/src/config/merge.ts +20 -0
- package/src/config/schema.ts +69 -0
- package/src/dictionary/README.md +18 -0
- package/src/dictionary/data/pi-ste.json +200 -0
- package/src/dictionary/form.ts +6 -0
- package/src/dictionary/load.ts +54 -0
- package/src/dictionary/schema.ts +28 -0
- package/src/engine/comments.ts +386 -0
- package/src/engine/diff.ts +328 -0
- package/src/engine/identifiers.ts +20 -0
- package/src/engine/kinds.ts +43 -0
- package/src/engine/lint.ts +530 -0
- package/src/engine/markdown.ts +338 -0
- package/src/engine/paragraphs.ts +105 -0
- package/src/engine/rules/contraction.ts +19 -0
- package/src/engine/rules/dictionary.ts +281 -0
- package/src/engine/rules/hedging.ts +27 -0
- package/src/engine/rules/marketing.ts +71 -0
- package/src/engine/rules/paragraph-length.ts +23 -0
- package/src/engine/rules/phrasal-verb.ts +57 -0
- package/src/engine/rules/registry.ts +15 -0
- package/src/engine/rules/semicolon.ts +14 -0
- package/src/engine/rules/sentence-length.ts +24 -0
- package/src/engine/rules/verb-form.ts +76 -0
- package/src/engine/scan.ts +15 -0
- package/src/engine/sentences.ts +285 -0
- package/src/engine/tagger.ts +8 -0
- package/src/engine/tokens.ts +2 -0
- package/src/engine/types.ts +45 -0
- package/src/extension/index.ts +755 -0
- package/src/tagger/wink.ts +44 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
export interface ChangedRange {
|
|
2
|
+
readonly start: number
|
|
3
|
+
readonly end: number
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface Deletion {
|
|
7
|
+
readonly previousStart: number
|
|
8
|
+
readonly previousEnd: number
|
|
9
|
+
readonly currentOffset: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface RetainedRange {
|
|
13
|
+
readonly previousStart: number
|
|
14
|
+
readonly currentStart: number
|
|
15
|
+
readonly length: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TextChanges {
|
|
19
|
+
readonly ranges: readonly ChangedRange[]
|
|
20
|
+
readonly deletions: readonly Deletion[]
|
|
21
|
+
readonly retained: readonly RetainedRange[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const DIFF_CELL_LIMIT = 1_000_000
|
|
25
|
+
|
|
26
|
+
interface DiffBudget {
|
|
27
|
+
remaining: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function reserveCells(budget: DiffBudget, previousLength: number, currentLength: number): boolean {
|
|
31
|
+
const cells = previousLength * currentLength
|
|
32
|
+
if (cells > budget.remaining) return false
|
|
33
|
+
budget.remaining -= cells
|
|
34
|
+
return true
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function lineTokens(text: string): string[] {
|
|
38
|
+
const tokens: string[] = []
|
|
39
|
+
let start = 0
|
|
40
|
+
for (let index = 0; index < text.length; index++) {
|
|
41
|
+
if (text[index] === "\n") {
|
|
42
|
+
tokens.push(text.slice(start, index + 1))
|
|
43
|
+
start = index + 1
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (start < text.length) {
|
|
47
|
+
tokens.push(text.slice(start))
|
|
48
|
+
}
|
|
49
|
+
return tokens
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function addRange(ranges: ChangedRange[], start: number, end: number): void {
|
|
53
|
+
if (start === end) return
|
|
54
|
+
const last = ranges.at(-1)
|
|
55
|
+
if (last && start <= last.end) {
|
|
56
|
+
ranges[ranges.length - 1] = { start: last.start, end: Math.max(last.end, end) }
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
ranges.push({ start, end })
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function addRetained(
|
|
63
|
+
retained: RetainedRange[],
|
|
64
|
+
previousStart: number,
|
|
65
|
+
currentStart: number,
|
|
66
|
+
length: number,
|
|
67
|
+
): void {
|
|
68
|
+
if (length === 0) return
|
|
69
|
+
const last = retained.at(-1)
|
|
70
|
+
if (
|
|
71
|
+
last &&
|
|
72
|
+
last.previousStart + last.length === previousStart &&
|
|
73
|
+
last.currentStart + last.length === currentStart
|
|
74
|
+
) {
|
|
75
|
+
retained[retained.length - 1] = { ...last, length: last.length + length }
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
retained.push({ previousStart, currentStart, length })
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function diffCharacters(
|
|
82
|
+
previous: string,
|
|
83
|
+
current: string,
|
|
84
|
+
previousOffset: number,
|
|
85
|
+
currentOffset: number,
|
|
86
|
+
ranges: ChangedRange[],
|
|
87
|
+
deletions: Deletion[],
|
|
88
|
+
retained: RetainedRange[],
|
|
89
|
+
budget: DiffBudget,
|
|
90
|
+
): void {
|
|
91
|
+
let prefix = 0
|
|
92
|
+
while (
|
|
93
|
+
prefix < previous.length &&
|
|
94
|
+
prefix < current.length &&
|
|
95
|
+
previous[prefix] === current[prefix]
|
|
96
|
+
) {
|
|
97
|
+
prefix++
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let previousEnd = previous.length
|
|
101
|
+
let currentEnd = current.length
|
|
102
|
+
while (
|
|
103
|
+
previousEnd > prefix &&
|
|
104
|
+
currentEnd > prefix &&
|
|
105
|
+
previous[previousEnd - 1] === current[currentEnd - 1]
|
|
106
|
+
) {
|
|
107
|
+
previousEnd--
|
|
108
|
+
currentEnd--
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const oldText = previous.slice(prefix, previousEnd)
|
|
112
|
+
const newText = current.slice(prefix, currentEnd)
|
|
113
|
+
const oldBase = previousOffset + prefix
|
|
114
|
+
const newBase = currentOffset + prefix
|
|
115
|
+
const suffixLength = previous.length - previousEnd
|
|
116
|
+
const retainEdges = () => {
|
|
117
|
+
addRetained(retained, previousOffset, currentOffset, prefix)
|
|
118
|
+
addRetained(retained, previousOffset + previousEnd, currentOffset + currentEnd, suffixLength)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (oldText.length === 0) {
|
|
122
|
+
addRetained(retained, previousOffset, currentOffset, prefix)
|
|
123
|
+
addRange(ranges, newBase, newBase + newText.length)
|
|
124
|
+
addRetained(retained, previousOffset + previousEnd, currentOffset + currentEnd, suffixLength)
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
if (newText.length === 0) {
|
|
128
|
+
addRetained(retained, previousOffset, currentOffset, prefix)
|
|
129
|
+
deletions.push({
|
|
130
|
+
previousStart: oldBase,
|
|
131
|
+
previousEnd: oldBase + oldText.length,
|
|
132
|
+
currentOffset: newBase,
|
|
133
|
+
})
|
|
134
|
+
addRetained(retained, previousOffset + previousEnd, currentOffset + currentEnd, suffixLength)
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
if (!reserveCells(budget, oldText.length, newText.length)) {
|
|
138
|
+
retainEdges()
|
|
139
|
+
addRange(ranges, newBase, newBase + newText.length)
|
|
140
|
+
deletions.push({
|
|
141
|
+
previousStart: oldBase,
|
|
142
|
+
previousEnd: oldBase + oldText.length,
|
|
143
|
+
currentOffset: newBase,
|
|
144
|
+
})
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
addRetained(retained, previousOffset, currentOffset, prefix)
|
|
149
|
+
|
|
150
|
+
const width = newText.length + 1
|
|
151
|
+
const table = new Int32Array((oldText.length + 1) * width)
|
|
152
|
+
const lcs = (oldIndex: number, newIndex: number) => table[oldIndex * width + newIndex] ?? 0
|
|
153
|
+
for (let oldIndex = oldText.length - 1; oldIndex >= 0; oldIndex--) {
|
|
154
|
+
for (let newIndex = newText.length - 1; newIndex >= 0; newIndex--) {
|
|
155
|
+
table[oldIndex * width + newIndex] =
|
|
156
|
+
oldText[oldIndex] === newText[newIndex]
|
|
157
|
+
? lcs(oldIndex + 1, newIndex + 1) + 1
|
|
158
|
+
: Math.max(lcs(oldIndex + 1, newIndex), lcs(oldIndex, newIndex + 1))
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let oldIndex = 0
|
|
163
|
+
let newIndex = 0
|
|
164
|
+
let deletionStart: number | undefined
|
|
165
|
+
let deletionPoint = 0
|
|
166
|
+
const flushDeletion = () => {
|
|
167
|
+
if (deletionStart === undefined) return
|
|
168
|
+
deletions.push({
|
|
169
|
+
previousStart: oldBase + deletionStart,
|
|
170
|
+
previousEnd: oldBase + oldIndex,
|
|
171
|
+
currentOffset: newBase + deletionPoint,
|
|
172
|
+
})
|
|
173
|
+
deletionStart = undefined
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
while (oldIndex < oldText.length && newIndex < newText.length) {
|
|
177
|
+
if (oldText[oldIndex] === newText[newIndex]) {
|
|
178
|
+
flushDeletion()
|
|
179
|
+
addRetained(retained, oldBase + oldIndex, newBase + newIndex, 1)
|
|
180
|
+
oldIndex++
|
|
181
|
+
newIndex++
|
|
182
|
+
} else if (lcs(oldIndex + 1, newIndex) >= lcs(oldIndex, newIndex + 1)) {
|
|
183
|
+
if (deletionStart === undefined) {
|
|
184
|
+
deletionStart = oldIndex
|
|
185
|
+
deletionPoint = newIndex
|
|
186
|
+
}
|
|
187
|
+
oldIndex++
|
|
188
|
+
} else {
|
|
189
|
+
flushDeletion()
|
|
190
|
+
addRange(ranges, newBase + newIndex, newBase + newIndex + 1)
|
|
191
|
+
newIndex++
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
while (oldIndex < oldText.length) {
|
|
195
|
+
if (deletionStart === undefined) {
|
|
196
|
+
deletionStart = oldIndex
|
|
197
|
+
deletionPoint = newIndex
|
|
198
|
+
}
|
|
199
|
+
oldIndex++
|
|
200
|
+
}
|
|
201
|
+
flushDeletion()
|
|
202
|
+
addRange(ranges, newBase + newIndex, newBase + newText.length)
|
|
203
|
+
addRetained(retained, previousOffset + previousEnd, currentOffset + currentEnd, suffixLength)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function changedText(previousText: string, currentText: string): TextChanges {
|
|
207
|
+
const previous = lineTokens(previousText)
|
|
208
|
+
const current = lineTokens(currentText)
|
|
209
|
+
|
|
210
|
+
let start = 0
|
|
211
|
+
let previousOffset = 0
|
|
212
|
+
let currentOffset = 0
|
|
213
|
+
while (start < previous.length && start < current.length && previous[start] === current[start]) {
|
|
214
|
+
previousOffset += previous[start]?.length ?? 0
|
|
215
|
+
currentOffset += current[start]?.length ?? 0
|
|
216
|
+
start++
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let previousEnd = previous.length
|
|
220
|
+
let currentEnd = current.length
|
|
221
|
+
while (
|
|
222
|
+
previousEnd > start &&
|
|
223
|
+
currentEnd > start &&
|
|
224
|
+
previous[previousEnd - 1] === current[currentEnd - 1]
|
|
225
|
+
) {
|
|
226
|
+
previousEnd--
|
|
227
|
+
currentEnd--
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const oldLines = previous.slice(start, previousEnd)
|
|
231
|
+
const newLines = current.slice(start, currentEnd)
|
|
232
|
+
const ranges: ChangedRange[] = []
|
|
233
|
+
const deletions: Deletion[] = []
|
|
234
|
+
const retained: RetainedRange[] = []
|
|
235
|
+
const budget: DiffBudget = { remaining: DIFF_CELL_LIMIT }
|
|
236
|
+
addRetained(retained, 0, 0, currentOffset)
|
|
237
|
+
|
|
238
|
+
if (!reserveCells(budget, oldLines.length, newLines.length)) {
|
|
239
|
+
const oldText = oldLines.join("")
|
|
240
|
+
const newText = newLines.join("")
|
|
241
|
+
if (newText.length > 0) {
|
|
242
|
+
addRange(ranges, currentOffset, currentOffset + newText.length)
|
|
243
|
+
} else if (oldText.length > 0) {
|
|
244
|
+
deletions.push({
|
|
245
|
+
previousStart: previousOffset,
|
|
246
|
+
previousEnd: previousOffset + oldText.length,
|
|
247
|
+
currentOffset,
|
|
248
|
+
})
|
|
249
|
+
}
|
|
250
|
+
addRetained(
|
|
251
|
+
retained,
|
|
252
|
+
previousOffset + oldText.length,
|
|
253
|
+
currentOffset + newText.length,
|
|
254
|
+
previousText.length - previousOffset - oldText.length,
|
|
255
|
+
)
|
|
256
|
+
return { ranges, deletions, retained }
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const width = newLines.length + 1
|
|
260
|
+
const table = new Int32Array((oldLines.length + 1) * width)
|
|
261
|
+
const lcs = (oldIndex: number, newIndex: number) => table[oldIndex * width + newIndex] ?? 0
|
|
262
|
+
for (let oldIndex = oldLines.length - 1; oldIndex >= 0; oldIndex--) {
|
|
263
|
+
for (let newIndex = newLines.length - 1; newIndex >= 0; newIndex--) {
|
|
264
|
+
table[oldIndex * width + newIndex] =
|
|
265
|
+
oldLines[oldIndex] === newLines[newIndex]
|
|
266
|
+
? lcs(oldIndex + 1, newIndex + 1) + 1
|
|
267
|
+
: Math.max(lcs(oldIndex + 1, newIndex), lcs(oldIndex, newIndex + 1))
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
let oldIndex = 0
|
|
272
|
+
let newIndex = 0
|
|
273
|
+
let oldChunk = ""
|
|
274
|
+
let newChunk = ""
|
|
275
|
+
let oldChunkOffset = previousOffset
|
|
276
|
+
let newChunkOffset = currentOffset
|
|
277
|
+
const flushChunk = () => {
|
|
278
|
+
if (oldChunk === "" && newChunk === "") return
|
|
279
|
+
diffCharacters(
|
|
280
|
+
oldChunk,
|
|
281
|
+
newChunk,
|
|
282
|
+
oldChunkOffset,
|
|
283
|
+
newChunkOffset,
|
|
284
|
+
ranges,
|
|
285
|
+
deletions,
|
|
286
|
+
retained,
|
|
287
|
+
budget,
|
|
288
|
+
)
|
|
289
|
+
oldChunk = ""
|
|
290
|
+
newChunk = ""
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
while (oldIndex < oldLines.length && newIndex < newLines.length) {
|
|
294
|
+
if (oldLines[oldIndex] === newLines[newIndex]) {
|
|
295
|
+
flushChunk()
|
|
296
|
+
const lineLength = oldLines[oldIndex]?.length ?? 0
|
|
297
|
+
addRetained(retained, previousOffset, currentOffset, lineLength)
|
|
298
|
+
previousOffset += lineLength
|
|
299
|
+
currentOffset += lineLength
|
|
300
|
+
oldIndex++
|
|
301
|
+
newIndex++
|
|
302
|
+
oldChunkOffset = previousOffset
|
|
303
|
+
newChunkOffset = currentOffset
|
|
304
|
+
} else if (lcs(oldIndex + 1, newIndex) >= lcs(oldIndex, newIndex + 1)) {
|
|
305
|
+
oldChunk += oldLines[oldIndex] ?? ""
|
|
306
|
+
previousOffset += oldLines[oldIndex]?.length ?? 0
|
|
307
|
+
oldIndex++
|
|
308
|
+
} else {
|
|
309
|
+
newChunk += newLines[newIndex] ?? ""
|
|
310
|
+
currentOffset += newLines[newIndex]?.length ?? 0
|
|
311
|
+
newIndex++
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
while (oldIndex < oldLines.length) {
|
|
315
|
+
oldChunk += oldLines[oldIndex] ?? ""
|
|
316
|
+
previousOffset += oldLines[oldIndex]?.length ?? 0
|
|
317
|
+
oldIndex++
|
|
318
|
+
}
|
|
319
|
+
while (newIndex < newLines.length) {
|
|
320
|
+
newChunk += newLines[newIndex] ?? ""
|
|
321
|
+
currentOffset += newLines[newIndex]?.length ?? 0
|
|
322
|
+
newIndex++
|
|
323
|
+
}
|
|
324
|
+
flushChunk()
|
|
325
|
+
addRetained(retained, previousOffset, currentOffset, previousText.length - previousOffset)
|
|
326
|
+
|
|
327
|
+
return { ranges, deletions, retained }
|
|
328
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// A token is exempt as an identifier when it has an interior signal prose
|
|
2
|
+
// words lack: a camelCase hump, an underscore, a dotted or :: path, or a
|
|
3
|
+
// call suffix. Mixed-case prose words (brand names) are an accepted loss.
|
|
4
|
+
const IDENTIFIER_CANDIDATE =
|
|
5
|
+
/[A-Za-z_$][\w$]*(?:(?:\.|::)(?:[A-Za-z$][\w$]*|_+[A-Za-z0-9$][\w$]*))*(?:\(\))?/g
|
|
6
|
+
|
|
7
|
+
const isIdentifier = (token: string): boolean =>
|
|
8
|
+
token.endsWith("()") ||
|
|
9
|
+
token.includes(".") ||
|
|
10
|
+
token.includes("::") ||
|
|
11
|
+
/[a-z][A-Z]/.test(token) ||
|
|
12
|
+
/[A-Za-z0-9$]_[A-Za-z0-9_$]/.test(token)
|
|
13
|
+
|
|
14
|
+
export function blankIdentifiers(lines: readonly string[]): string[] {
|
|
15
|
+
return lines.map((line) =>
|
|
16
|
+
line.replace(IDENTIFIER_CANDIDATE, (match) =>
|
|
17
|
+
isIdentifier(match) ? " ".repeat(match.length) : match,
|
|
18
|
+
),
|
|
19
|
+
)
|
|
20
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { LintKind, SourceDialect } from "./types.ts"
|
|
2
|
+
|
|
3
|
+
const SLASH_EXTENSIONS = new Set([
|
|
4
|
+
"ts",
|
|
5
|
+
"tsx",
|
|
6
|
+
"js",
|
|
7
|
+
"jsx",
|
|
8
|
+
"mjs",
|
|
9
|
+
"cjs",
|
|
10
|
+
"go",
|
|
11
|
+
"rs",
|
|
12
|
+
"java",
|
|
13
|
+
"c",
|
|
14
|
+
"h",
|
|
15
|
+
"cpp",
|
|
16
|
+
"hpp",
|
|
17
|
+
"cc",
|
|
18
|
+
"cs",
|
|
19
|
+
"swift",
|
|
20
|
+
"kt",
|
|
21
|
+
"scala",
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
const HASH_EXTENSIONS = new Set(["sh", "bash", "zsh", "py", "rb", "yaml", "yml", "toml", "pl"])
|
|
25
|
+
|
|
26
|
+
export interface PathClassification {
|
|
27
|
+
readonly kind: LintKind
|
|
28
|
+
readonly sourceDialect: SourceDialect
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const classifyPath = (path: string): PathClassification => {
|
|
32
|
+
const dot = path.lastIndexOf(".")
|
|
33
|
+
if (dot <= Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"))) {
|
|
34
|
+
return { kind: "prose-file", sourceDialect: "general" }
|
|
35
|
+
}
|
|
36
|
+
const extension = path.slice(dot + 1).toLowerCase()
|
|
37
|
+
if (SLASH_EXTENSIONS.has(extension)) return { kind: "slash-source", sourceDialect: "general" }
|
|
38
|
+
if (HASH_EXTENSIONS.has(extension)) {
|
|
39
|
+
const sourceDialect = ["sh", "bash", "zsh"].includes(extension) ? "shell" : "general"
|
|
40
|
+
return { kind: "hash-source", sourceDialect }
|
|
41
|
+
}
|
|
42
|
+
return { kind: "prose-file", sourceDialect: "general" }
|
|
43
|
+
}
|