aura-basetools 0.7.0 → 0.13.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 +223 -13
- package/bin/aura-mcp.mjs +169 -27
- package/bin/aura.mjs +257 -37
- package/lib/observation-store.mjs +150 -17
- package/lib/ops.mjs +183 -19
- package/lib/transaction-store.mjs +1 -1
- package/lib/workspace-lock.mjs +25 -4
- package/locator-lab/anchor-core.mjs +1 -1
- package/locator-lab/args.mjs +103 -6
- package/locator-lab/locator-api.mjs +260 -22
- package/locator-lab/read-core.mjs +634 -563
- package/locator-lab/repo-map.mjs +1 -1
- package/locator-lab/struct-core.mjs +92 -34
- package/locator-lab/write-path.mjs +661 -112
- package/package.json +8 -8
- package/test/argument-surface-test.mjs +119 -0
- package/test/doctor-test.mjs +73 -0
- package/test/observation-concurrency-test.mjs +90 -0
- package/test/standalone-test.mjs +202 -128
- package/test/write-test.mjs +294 -0
package/locator-lab/repo-map.mjs
CHANGED
|
@@ -246,7 +246,12 @@ function tokenize(src, lang) {
|
|
|
246
246
|
// 数字
|
|
247
247
|
if (/[0-9]/.test(c)) {
|
|
248
248
|
const start = i
|
|
249
|
-
while (i < n && /[0-9a-fA-FxX._]/.test(src[i]))
|
|
249
|
+
while (i < n && /[0-9a-fA-FxX._]/.test(src[i])) {
|
|
250
|
+
// Erlang 的 `.` 既是语句终结符、又可能是小数点:`1.` 是整数 1 + 终结符,
|
|
251
|
+
// `1.0` 才是浮点。只在 `.` 后面还有数字时把它算进数字,否则留给标点层。
|
|
252
|
+
if (lang.dotTerminatesNumber === true && src[i] === '.' && !/[0-9]/.test(src[i + 1] ?? '')) break
|
|
253
|
+
i += 1
|
|
254
|
+
}
|
|
250
255
|
push('number', start, i)
|
|
251
256
|
continue
|
|
252
257
|
}
|
|
@@ -293,7 +298,7 @@ function tokenize(src, lang) {
|
|
|
293
298
|
* 它 10 个顶层 `function` 声明里,**索引只认出 2 个**,其余 8 个凭空消失
|
|
294
299
|
* (`renderChanged` `renderFull` `renderMap` `renderAround` `renderLines`
|
|
295
300
|
* `renderSearch` `windowEnvelope` `fitWithin`),而已认出的 `renderDigest`
|
|
296
|
-
* 跨度是 `69-69`(真实 69-164)。于是 `
|
|
301
|
+
* 跨度是 `69-69`(真实 69-164)。于是 `aura_basetools_edit target=<这些名字>` 全部
|
|
297
302
|
* `STRUCT_NOT_FOUND`,而 `map` 把这些函数报成不存在——**本工具连自己的引擎都定位不了。**
|
|
298
303
|
*
|
|
299
304
|
* 机制:`/` 被当成普通标点,于是
|
|
@@ -334,7 +339,7 @@ const NO_HEADER_KEYWORDS = new Set([])
|
|
|
334
339
|
* 块体**终结规则**:`bodyOpen: null` 的语言靠它把跨度补到真实末端。
|
|
335
340
|
*
|
|
336
341
|
* 这是配置表里第三条语言相关的真差异(前两条是注释与字符串语法)。缺了它,跨度就只有签名行,
|
|
337
|
-
* 而后果是**静默**的:`
|
|
342
|
+
* 而后果是**静默**的:`aura_basetools_edit target=<函数名>` 只换掉签名、旧函数体原样留下,
|
|
338
343
|
* `python -m py_compile` 仍然返回 0。因此判据在这里,与其它差异并列:
|
|
339
344
|
*
|
|
340
345
|
* { style: 'indent' } 缩进决定块体(Python / Cython)
|
|
@@ -356,10 +361,16 @@ const INDENT_BLOCK = { style: 'indent', headerEnds: new Set([':']) }
|
|
|
356
361
|
* 而这里的语言压根没有配对的收尾词——SQL 只有语句终结符,Erlang 的函数子句只有一个 `.`。
|
|
357
362
|
* 硬套 `closer` 要给每条规则编一个假开块词,那是在配置里写谎话。
|
|
358
363
|
*
|
|
359
|
-
* `
|
|
360
|
-
*
|
|
364
|
+
* `newlineStops` 默认 true:没有终结符时,换行是安全方向的兜底(SQL 的多语句、Vim 的无尾声明)。
|
|
365
|
+
* Erlang 关掉它:函数体的换行是普通空白,唯一合法的收尾是 `.`;开启会把多行函数体截在签名行。
|
|
366
|
+
* 关掉后必须在文件内找到终结符,否则跨度判为不完整(fail-closed)——见 terminatorBlockEnd。
|
|
361
367
|
*/
|
|
362
|
-
const TERMINATOR_BLOCK = (terminators
|
|
368
|
+
const TERMINATOR_BLOCK = (terminators, { newlineStops = true } = {}) => ({
|
|
369
|
+
style: 'terminator',
|
|
370
|
+
terminators: new Set(terminators),
|
|
371
|
+
newlineStops,
|
|
372
|
+
})
|
|
373
|
+
|
|
363
374
|
|
|
364
375
|
/**
|
|
365
376
|
* 除 `bodyOpen` 之外还算块开符的字符:`[` —— `const X = [ … ]` 这种字面量体。
|
|
@@ -508,9 +519,9 @@ const RUST = {
|
|
|
508
519
|
{ kind: 'trait', pattern: ['$mods*', 'trait', '$name', '$param*'] },
|
|
509
520
|
{ kind: 'impl', pattern: ['impl', '$param*', '$name'] },
|
|
510
521
|
{ kind: 'mod', pattern: ['$mods*', 'mod', '$name'] },
|
|
511
|
-
// Rust
|
|
512
|
-
//
|
|
513
|
-
//
|
|
522
|
+
// Rust 的常量/静态绑定:`const`/`static` 本身也在 modifiers 里,规则必须带 `$mods*`,
|
|
523
|
+
// 否则 `pub const R` 会被“前一 token 是修饰符”的去重闸门判掉。
|
|
524
|
+
// `$mods*` 的逐长度回溯(matchPattern)让 `pub const` 能停在 `pub` 之后、把 `const` 留给字面量。
|
|
514
525
|
{ kind: 'variable', pattern: ['$mods*', 'const', '$name'], guard: 'notKeyword' },
|
|
515
526
|
{ kind: 'variable', pattern: ['$mods*', 'static', '$name'], guard: 'notKeyword' },
|
|
516
527
|
],
|
|
@@ -1183,7 +1194,10 @@ const ERLANG = {
|
|
|
1183
1194
|
multiPunct: ['->', '<-', '==', '/=', '=<', '>=', '++', '--', '::', '||', '|>', '<<', '>>'],
|
|
1184
1195
|
brackets: { '(': ')', '[': ']', '{': '}' },
|
|
1185
1196
|
bodyOpen: null,
|
|
1186
|
-
|
|
1197
|
+
dotTerminatesNumber: true,
|
|
1198
|
+
|
|
1199
|
+
blockEnd: TERMINATOR_BLOCK(['.'], { newlineStops: false }),
|
|
1200
|
+
|
|
1187
1201
|
rules: [
|
|
1188
1202
|
// `$param` 是**跳过**一个括号组、不捕获任何名字,所以属性名必须写 `'(' '$name'`:
|
|
1189
1203
|
// 用 `$param` 的结果是 `cap.$name` 为空 ⇒ 符号被 `indexStructure` 静默丢弃(实测 0 个符号)。
|
|
@@ -1611,7 +1625,7 @@ function matchAngle(tokens, open) {
|
|
|
1611
1625
|
* `$name` 捕获符号名(必须是标识符;决定声明起点)
|
|
1612
1626
|
* `$param` 跳过一个配平括号组(必选)
|
|
1613
1627
|
* `$param*` 可选:有 `()` 或 `<>` 就跳,没有就跳过这一段
|
|
1614
|
-
* `$mods*` 可选:吃掉任意个 `modifiers`
|
|
1628
|
+
* `$mods*` 可选:吃掉任意个 `modifiers` 词表里的修饰符;**逐长度回溯**(字面量本身在词表里时能停在中途)
|
|
1615
1629
|
* `$ident*` 可选(**非贪婪**):吃掉任意个非关键字标识符
|
|
1616
1630
|
* `$rest` 跳到**当前所在括号组的收尾 token**(见 `skipToGroupClose`)
|
|
1617
1631
|
*
|
|
@@ -1645,7 +1659,27 @@ function matchPattern(tokens, start, pattern, pi, cap, lang, pairs) {
|
|
|
1645
1659
|
const part = pattern[pi]
|
|
1646
1660
|
const t = tokens[start]
|
|
1647
1661
|
|
|
1648
|
-
if (part === '$
|
|
1662
|
+
if (part === '$mods*') {
|
|
1663
|
+
/**
|
|
1664
|
+
* 修饰符段要**逐个长度回溯**,不能只试「全吃」与「不吃」。
|
|
1665
|
+
*
|
|
1666
|
+
* `pub const G: i32 = 1` 里 `const` 同时是 Rust 的 modifier 和规则字面量:
|
|
1667
|
+
* 全吃 ⇒ 后面的字面量 `const` 永远等不到;不吃 ⇒ 字面量对不上 `pub`。
|
|
1668
|
+
* 中间那一步(吃 `pub`、留 `const`)才是解。`pub const fn f` 仍由全吃那一支命中。
|
|
1669
|
+
*/
|
|
1670
|
+
const stops = [start]
|
|
1671
|
+
let i = start
|
|
1672
|
+
while (tokens[i] !== undefined && tokens[i].type === 'ident' && lang.modifiers !== undefined && lang.modifiers.has(tokens[i].value)) {
|
|
1673
|
+
i += 1
|
|
1674
|
+
stops.push(i)
|
|
1675
|
+
}
|
|
1676
|
+
for (let s = stops.length - 1; s >= 0; s -= 1) {
|
|
1677
|
+
const hit = matchPattern(tokens, stops[s], pattern, pi + 1, cap, lang, pairs)
|
|
1678
|
+
if (hit !== null) return hit
|
|
1679
|
+
}
|
|
1680
|
+
return null
|
|
1681
|
+
}
|
|
1682
|
+
if (part === '$param*' || part === '$ident*') {
|
|
1649
1683
|
// 先试"跳"的版本,失败再试"不跳"的版本——这就是回溯
|
|
1650
1684
|
const advanced = skipOptional(tokens, start, part, lang, pairs)
|
|
1651
1685
|
if (advanced !== start) {
|
|
@@ -1692,13 +1726,8 @@ function matchPattern(tokens, start, pattern, pi, cap, lang, pairs) {
|
|
|
1692
1726
|
return matchPattern(tokens, start + 1, pattern, pi + 1, cap, lang, pairs)
|
|
1693
1727
|
}
|
|
1694
1728
|
|
|
1695
|
-
/** 可选段的"跳"
|
|
1729
|
+
/** 可选段的"跳"这一步(`$param*` / `$ident*`):跳不动就原地返回,由调用方回溯。 */
|
|
1696
1730
|
function skipOptional(tokens, k, part, lang, pairs) {
|
|
1697
|
-
if (part === '$mods*') {
|
|
1698
|
-
let i = k
|
|
1699
|
-
while (tokens[i] !== undefined && tokens[i].type === 'ident' && lang.modifiers !== undefined && lang.modifiers.has(tokens[i].value)) i += 1
|
|
1700
|
-
return i
|
|
1701
|
-
}
|
|
1702
1731
|
if (part === '$ident*') {
|
|
1703
1732
|
// 只吃**非关键字**标识符:关键字是模式里的字面量,不该被这个段吞掉
|
|
1704
1733
|
let i = k
|
|
@@ -1784,13 +1813,38 @@ function matchRule(tokens, i, rule, lang, pairs, lineAt) {
|
|
|
1784
1813
|
*/
|
|
1785
1814
|
if (rule.guard === 'topLevel' && !clauseInitial(tokens, startTok, lineAt)) return null
|
|
1786
1815
|
if (rule.guard === 'attributeClause') {
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1816
|
+
/**
|
|
1817
|
+
* Erlang 函数子句是**语句级**声明:只能出现在文件开头、上一语句的 `.` 之后,
|
|
1818
|
+
* 或同一函数上一子句的 `;` 之后。
|
|
1819
|
+
*
|
|
1820
|
+
* 这条边界同时挡住三类假符号:属性里的名字(`-module(alpha)` / `-define(ALPHA, …)`)、
|
|
1821
|
+
* `-spec f(A) -> …` 里的 `f`(spec 内部也有 `->`,只看箭头会误收)、以及函数体里的
|
|
1822
|
+
* 调用 `foo(X)`(前一 token 是 `(` / `,` / `=` 等)。
|
|
1823
|
+
*
|
|
1824
|
+
* 前一 token 是 `;` 表示同一函数的后续子句:它们共享到最后一个 `.` 的跨度,
|
|
1825
|
+
* 重复登记会让 `target=f` 报 STRUCT_AMBIGUOUS,所以只登记第一个子句。
|
|
1826
|
+
*/
|
|
1790
1827
|
const prev = tokens[i - 1]
|
|
1791
|
-
if (prev !== undefined
|
|
1792
|
-
|
|
1793
|
-
|
|
1828
|
+
if (prev !== undefined) {
|
|
1829
|
+
if (prev.type !== 'punct' || (prev.value !== '.' && prev.value !== ';')) return null
|
|
1830
|
+
if (prev.value === ';') return null
|
|
1831
|
+
}
|
|
1832
|
+
const open = tokens[startTok + 1]
|
|
1833
|
+
if (open === undefined || open.type !== 'punct' || open.value !== '(' || !pairs.has(startTok + 1)) return null
|
|
1834
|
+
// 参数组之后可以是 `->`,也可以先是 `when` 守卫(`f(X) when X > 0 -> …`)。
|
|
1835
|
+
// 守卫里没有 `->`;遇到 `;` / `.` 仍没有 `->` 说明这不是函数子句。
|
|
1836
|
+
let j = end
|
|
1837
|
+
for (; j < tokens.length; j += 1) {
|
|
1838
|
+
const t = tokens[j]
|
|
1839
|
+
if (t.type !== 'punct') continue
|
|
1840
|
+
if (t.value === ';' || t.value === '.') return null
|
|
1841
|
+
if (pairs.has(j)) {
|
|
1842
|
+
j = pairs.get(j)
|
|
1843
|
+
continue
|
|
1844
|
+
}
|
|
1845
|
+
if (t.value === '->') break
|
|
1846
|
+
}
|
|
1847
|
+
if (j >= tokens.length) return null
|
|
1794
1848
|
}
|
|
1795
1849
|
// 放最后,因为它与其它守卫互不依赖。
|
|
1796
1850
|
if (looksLikeBinding(tokens, startTok, k, lang)) return null
|
|
@@ -2107,7 +2161,7 @@ function isTerminatorToken(t, terminators) {
|
|
|
2107
2161
|
* {@link terminatorBlockEnd} 用的两张后缀最小值表。判定优先级与逐 token 扫描同一个 k 上的次序相同。
|
|
2108
2162
|
* 只在该语言真的走终结符风格时才会被调用。
|
|
2109
2163
|
*/
|
|
2110
|
-
function terminatorScan(tokens, pairs, lineAt, terminators) {
|
|
2164
|
+
function terminatorScan(tokens, pairs, lineAt, terminators, newlineStops) {
|
|
2111
2165
|
const n = tokens.length
|
|
2112
2166
|
const nextStop = new Int32Array(n)
|
|
2113
2167
|
const nextPairOpen = new Int32Array(n)
|
|
@@ -2132,7 +2186,8 @@ function terminatorScan(tokens, pairs, lineAt, terminators) {
|
|
|
2132
2186
|
chainEnd[i] = after < n && nextPairOpen[after] !== -1 ? chainEnd[nextPairOpen[after]] : i
|
|
2133
2187
|
}
|
|
2134
2188
|
// 终结符优先于括号跳层,括号跳层优先于换行 —— 与原循环在同一个 k 上的判定次序一一对应
|
|
2135
|
-
|
|
2189
|
+
// 终结符优先于括号跳层,括号跳层优先于换行(newlineStops=false 时换行不是停止条件)
|
|
2190
|
+
if (isTerminatorToken(t, terminators) || (newlineStops && !isOpen && i >= 1 && lineAt(t.start) > lineAt(tokens[i - 1].end))) {
|
|
2136
2191
|
stop = i
|
|
2137
2192
|
}
|
|
2138
2193
|
nextStop[i] = stop
|
|
@@ -2148,8 +2203,8 @@ function terminatorScan(tokens, pairs, lineAt, terminators) {
|
|
|
2148
2203
|
*/
|
|
2149
2204
|
function lazyTerminatorScan(tokens, pairs, lineAt) {
|
|
2150
2205
|
let table = null
|
|
2151
|
-
return (
|
|
2152
|
-
if (table === null) table = terminatorScan(tokens, pairs, lineAt, terminators)
|
|
2206
|
+
return (spec) => {
|
|
2207
|
+
if (table === null) table = terminatorScan(tokens, pairs, lineAt, spec.terminators, spec.newlineStops !== false)
|
|
2153
2208
|
return table
|
|
2154
2209
|
}
|
|
2155
2210
|
}
|
|
@@ -2160,6 +2215,8 @@ function lazyTerminatorScan(tokens, pairs, lineAt) {
|
|
|
2160
2215
|
* 判据与跳表分别落在 {@link isTerminatorToken} 与 {@link terminatorScan} 上)。
|
|
2161
2216
|
*/
|
|
2162
2217
|
function terminatorBlockEnd(tokens, pairs, fromTok, spec, lineAt, scan) {
|
|
2218
|
+
// 只认终结符(Erlang):找不到就是跨度不完整,不能拿换行/组尾兜底当一个完整跨度。
|
|
2219
|
+
const onlyTerminator = spec.newlineStops === false
|
|
2163
2220
|
let skippedEnd = -1
|
|
2164
2221
|
let k = fromTok + 1
|
|
2165
2222
|
if (k >= tokens.length) return { endTok: -1, note: '' }
|
|
@@ -2177,15 +2234,16 @@ function terminatorBlockEnd(tokens, pairs, fromTok, spec, lineAt, scan) {
|
|
|
2177
2234
|
if (s !== -1 && (o === -1 || s < o)) {
|
|
2178
2235
|
// stop 位置一定不是括号开符(建表时就排除了),所以 `s === o` 不可能出现
|
|
2179
2236
|
if (isTerminatorToken(tokens[s], spec.terminators)) return { endTok: s, note: '' }
|
|
2237
|
+
if (onlyTerminator) return { endTok: -1, note: '' }
|
|
2180
2238
|
return { endTok: skippedEnd === -1 ? s - 1 : skippedEnd, note: '' }
|
|
2181
2239
|
}
|
|
2182
|
-
if (o === -1) return { endTok: skippedEnd, note: '' }
|
|
2240
|
+
if (o === -1) return { endTok: onlyTerminator ? -1 : skippedEnd, note: '' }
|
|
2183
2241
|
// 这一段里一个停止条件都没有:原循环会一路跨到**最后一个**组尾 ⇒ 用链尾表一次到位(O(1))
|
|
2184
|
-
if (s === -1) return { endTok: pairs.get(chainEndFrom[k]), note: '' }
|
|
2242
|
+
if (s === -1) return { endTok: onlyTerminator ? -1 : pairs.get(chainEndFrom[k]), note: '' }
|
|
2185
2243
|
skippedEnd = pairs.get(o)
|
|
2186
2244
|
k = skippedEnd + 1
|
|
2187
2245
|
}
|
|
2188
|
-
return { endTok: skippedEnd, note: '' }
|
|
2246
|
+
return { endTok: onlyTerminator ? -1 : skippedEnd, note: '' }
|
|
2189
2247
|
}
|
|
2190
2248
|
|
|
2191
2249
|
/**
|
|
@@ -2324,7 +2382,7 @@ function headerContinues(tokens, k, prev, clauses) {
|
|
|
2324
2382
|
* (`bodyOpen` 或 `blockEnd`),扫描器里不写语言分支。
|
|
2325
2383
|
*
|
|
2326
2384
|
* **跨度必须覆盖符号真实占据的整段**:拿不到完整跨度时不是退化成签名行,而是标记
|
|
2327
|
-
* `spanComplete: false` —— 调用方(`locate`,进而 `
|
|
2385
|
+
* `spanComplete: false` —— 调用方(`locate`,进而 `aura_basetools_edit`)据此刻拒绝 `target=` 替换。
|
|
2328
2386
|
* 理由是实测的:偏短的跨度会让"只换签名行"这件事**静默**发生,落盘之后语法检查毫无反应。
|
|
2329
2387
|
*
|
|
2330
2388
|
* 这里还要分辨一件事:`const describe = (x) => { ... }` 是**函数绑定**,不是普通变量。
|
|
@@ -2378,7 +2436,7 @@ function declarationSpan(tokens, pairs, startTok, endTok, lang, lineAt, kind, li
|
|
|
2378
2436
|
spanNote = hit.note
|
|
2379
2437
|
} else last = hit.endTok
|
|
2380
2438
|
} else if (spec.style === 'terminator') {
|
|
2381
|
-
const hit = terminatorBlockEnd(tokens, pairs, endTok, spec, lineAt, termScan(spec
|
|
2439
|
+
const hit = terminatorBlockEnd(tokens, pairs, endTok, spec, lineAt, termScan(spec))
|
|
2382
2440
|
if (hit.endTok === -1) {
|
|
2383
2441
|
spanComplete = false
|
|
2384
2442
|
spanNote = `line ${lineAt(tokens[startTok].start)}: no statement terminator closes this declaration, and no later line starts a new one, so the span cannot be delimited`
|
|
@@ -2410,7 +2468,7 @@ function declarationSpan(tokens, pairs, startTok, endTok, lang, lineAt, kind, li
|
|
|
2410
2468
|
|
|
2411
2469
|
// ── 结构索引的记忆化 ───────────────────────────────────────────────────────
|
|
2412
2470
|
/**
|
|
2413
|
-
* `indexStructure` 是热路径:`
|
|
2471
|
+
* `indexStructure` 是热路径:`aura_basetools_read` 的每个结构化模式、`aura_basetools_edit` 每次结构化编辑都会建一次
|
|
2414
2472
|
* 完整索引(token 流 + 括号配平 + 符号表)。实测本仓最大的引擎文件(本文件,施工前 97 KB /
|
|
2415
2473
|
* 2573 行)建一次 **15.8 ms**,而同一份内容在一轮里常被连问几次(read 的 map → digest → target
|
|
2416
2474
|
* 各一次,edit 的 plan 与 verify 各一次)——同一份工作被反复做。这一层把它记住。
|