@movemama/opencode-legacy 1.0.2 → 1.0.3
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 +7 -5
- package/legacy-rules.json +3 -3
- package/package.json +1 -1
- package/plugin-meta.js +1 -1
- package/tools/agents-rules.js +17 -0
- package/tools/edit.js +17 -2
- package/tools/legacy-edit-feedback.js +68 -0
- package/tools/legacy-router.mjs +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @movemama/opencode-legacy
|
|
2
2
|
|
|
3
|
-
OpenCode legacy
|
|
3
|
+
OpenCode legacy 文本处理插件,默认更偏向 `GBK` 文本兼容,同时支持手动指定 `GB2312`、脚本型 `.txt` 编辑和 legacy 规则路由场景。
|
|
4
4
|
|
|
5
5
|
当前主链路已经改为纯 JS 编码实现,不再依赖系统外部 `iconv.exe`。
|
|
6
6
|
|
|
@@ -74,7 +74,7 @@ OpenCode legacy 文本处理插件,面向 `GB2312` 文本、脚本型 `.txt`
|
|
|
74
74
|
|
|
75
75
|
## 自定义扩展规则
|
|
76
76
|
|
|
77
|
-
如果用户希望让其他文件类型也走 `
|
|
77
|
+
如果用户希望让其他文件类型也走 `GBK` / `GB2312` 的 legacy 读写链路,当前可以通过项目级规则扩展实现。
|
|
78
78
|
|
|
79
79
|
推荐在当前项目放置:
|
|
80
80
|
|
|
@@ -87,7 +87,7 @@ OpenCode legacy 文本处理插件,面向 `GB2312` 文本、脚本型 `.txt`
|
|
|
87
87
|
"rules": [
|
|
88
88
|
{
|
|
89
89
|
"glob": "**/*.{npc,msg,dialog}",
|
|
90
|
-
"encoding": "
|
|
90
|
+
"encoding": "gbk",
|
|
91
91
|
"strict": true,
|
|
92
92
|
"tool": "txt-gb2312",
|
|
93
93
|
"priority": 50,
|
|
@@ -103,7 +103,7 @@ OpenCode legacy 文本处理插件,面向 `GB2312` 文本、脚本型 `.txt`
|
|
|
103
103
|
字段说明:
|
|
104
104
|
|
|
105
105
|
- `glob`:匹配要走 legacy 读写链路的文件范围
|
|
106
|
-
- `encoding`:指定文件使用的编码,例如 `gb2312
|
|
106
|
+
- `encoding`:指定文件使用的编码,例如 `gbk`、`gb2312`
|
|
107
107
|
- `strict`:是否使用严格模式;通常脚本类文本建议保持 `true`
|
|
108
108
|
- `tool`:建议使用的处理器名称,当前常见值为 `txt-gb2312` 或 `legacy-text`
|
|
109
109
|
- `priority`:规则优先级,数值越大越优先匹配
|
|
@@ -112,7 +112,7 @@ OpenCode legacy 文本处理插件,面向 `GB2312` 文本、脚本型 `.txt`
|
|
|
112
112
|
- `fallbackMode`:主策略失败后的回退方式,例如 `legacy-safe-replace`、`widget-field-update`
|
|
113
113
|
- `scriptMarkers`:用于辅助识别脚本型文件或 DSL 结构的关键标记数组
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
如果客户只是想让某类新文件按中文常见编码读写,通常建议优先使用 `GBK`;如果确实需要严格限制在 `GB2312`,也可以手动指定。最小配置通常只需要先关心:
|
|
116
116
|
|
|
117
117
|
- `glob`
|
|
118
118
|
- `encoding`
|
|
@@ -142,6 +142,8 @@ OpenCode legacy 文本处理插件,面向 `GB2312` 文本、脚本型 `.txt`
|
|
|
142
142
|
- `classic-tag` 文本可根据 `profile` 和 `scriptMarkers` 判断为更偏 block / line-normalized 的策略
|
|
143
143
|
- `rich-ui-dsl` 文本会优先识别 `<Text|...>` / `<Button|...>` 组件,并在 exact 失败后尝试 widget 字段级更新
|
|
144
144
|
- 失败时会返回当前策略、格式族和 fallback 提示,便于继续排障
|
|
145
|
+
- 编辑成功时也会返回:检测格式、修改策略、修改次数、命中位置,以及旧片段 / 新片段摘要,便于像内置 `edit` 一样快速确认改动位置
|
|
146
|
+
- 插件还会通过 `tool.execute.after` 对 legacy edit 结果做统一包装,补充一致的标题与 metadata,方便后续继续往内置 `edit` 体验靠拢
|
|
145
147
|
|
|
146
148
|
## 支持的格式族
|
|
147
149
|
|
package/legacy-rules.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_comment": "rules 为实际生效规则;examples 为示例模板,不会自动生效。新增文件类型时,优先复制 examples 中最接近的一条到 rules。tool 可选值当前建议使用 txt-gb2312 或 legacy-text
|
|
2
|
+
"_comment": "rules 为实际生效规则;examples 为示例模板,不会自动生效。新增文件类型时,优先复制 examples 中最接近的一条到 rules。tool 可选值当前建议使用 txt-gb2312 或 legacy-text。默认 .txt 规则现已优先使用 gbk,以获得更好的中文兼容性。",
|
|
3
3
|
"rules": [
|
|
4
4
|
{
|
|
5
5
|
"glob": "**/*.txt",
|
|
6
|
-
"encoding": "
|
|
6
|
+
"encoding": "gbk",
|
|
7
7
|
"strict": true,
|
|
8
8
|
"tool": "txt-gb2312",
|
|
9
9
|
"priority": 10,
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"glob": "**/*.txt",
|
|
17
|
-
"encoding": "
|
|
17
|
+
"encoding": "gbk",
|
|
18
18
|
"strict": true,
|
|
19
19
|
"tool": "txt-gb2312",
|
|
20
20
|
"priority": 20,
|
package/package.json
CHANGED
package/plugin-meta.js
CHANGED
package/tools/agents-rules.js
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync, readFileSync } from 'node:fs'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
|
|
4
4
|
const EDIT_GUARDED_TOOLS = new Set(['edit', 'write', 'script-edit', 'legacy_edit', 'legacy_write'])
|
|
5
|
+
const LEGACY_EDIT_FEEDBACK_TOOLS = new Set(['edit', 'legacy_edit', 'script-edit'])
|
|
5
6
|
|
|
6
7
|
function normalizeLineEndings(content) {
|
|
7
8
|
return content.replace(/\r\n/g, '\n').trim()
|
|
@@ -72,5 +73,21 @@ export function createAgentsRuleHooks(input = {}) {
|
|
|
72
73
|
throw new Error('当前会话尚未完成 AGENTS.md 规则注入,已禁止编辑类工具执行。请先让插件读取全局与项目 AGENTS.md。')
|
|
73
74
|
}
|
|
74
75
|
},
|
|
76
|
+
'tool.execute.after': async (hookInput, output) => {
|
|
77
|
+
if (!LEGACY_EDIT_FEEDBACK_TOOLS.has(hookInput.tool)) {
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (typeof output?.output !== 'string' || !output.output.includes('修改策略:')) {
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
output.title = 'legacy edit'
|
|
86
|
+
output.metadata = {
|
|
87
|
+
...(output.metadata || {}),
|
|
88
|
+
kind: 'legacy-edit-feedback',
|
|
89
|
+
tool: hookInput.tool,
|
|
90
|
+
}
|
|
91
|
+
},
|
|
75
92
|
}
|
|
76
93
|
}
|
package/tools/edit.js
CHANGED
|
@@ -4,6 +4,7 @@ import path from 'node:path'
|
|
|
4
4
|
import readTool from './read.js'
|
|
5
5
|
import writeTool from './write.js'
|
|
6
6
|
import { applyLegacyEdit } from './legacy-edit-core.mjs'
|
|
7
|
+
import { buildEditFeedback } from './legacy-edit-feedback.js'
|
|
7
8
|
import { matchLegacyRule } from './legacy-router.mjs'
|
|
8
9
|
import { loadLegacyRules } from './legacy-rules-loader.js'
|
|
9
10
|
import { chooseEditStrategy } from './legacy-strategy.js'
|
|
@@ -34,7 +35,14 @@ export default tool({
|
|
|
34
35
|
const result = applyLegacyEdit(content, args.oldString, args.newString, Boolean(args.replaceAll))
|
|
35
36
|
|
|
36
37
|
if (result.changed) {
|
|
37
|
-
|
|
38
|
+
const writeMessage = await writeTool.execute({ filePath, content: result.content }, context)
|
|
39
|
+
return buildEditFeedback({
|
|
40
|
+
writeMessage,
|
|
41
|
+
detectedFamily: strategy.detectedFamily,
|
|
42
|
+
strategy: 'exact-first',
|
|
43
|
+
originalContent: content,
|
|
44
|
+
updatedContent: result.content,
|
|
45
|
+
})
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
const fallbackResult = applyFallbackEditChain({
|
|
@@ -45,7 +53,14 @@ export default tool({
|
|
|
45
53
|
})
|
|
46
54
|
|
|
47
55
|
if (fallbackResult.changed) {
|
|
48
|
-
|
|
56
|
+
const writeMessage = await writeTool.execute({ filePath, content: fallbackResult.content }, context)
|
|
57
|
+
return buildEditFeedback({
|
|
58
|
+
writeMessage,
|
|
59
|
+
detectedFamily: strategy.detectedFamily,
|
|
60
|
+
strategy: fallbackResult.strategy,
|
|
61
|
+
originalContent: content,
|
|
62
|
+
updatedContent: fallbackResult.content,
|
|
63
|
+
})
|
|
49
64
|
}
|
|
50
65
|
|
|
51
66
|
throw new Error(
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
function normalizeNewlines(text) {
|
|
2
|
+
return text.replace(/\r\n/g, '\n')
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function buildPositionLabel(startLine, endLine) {
|
|
6
|
+
if (startLine === endLine) {
|
|
7
|
+
return `${startLine}`
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return `${startLine}-${endLine}`
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function summarizeChangedBlock(original, updated) {
|
|
14
|
+
const originalLines = normalizeNewlines(original).split('\n')
|
|
15
|
+
const updatedLines = normalizeNewlines(updated).split('\n')
|
|
16
|
+
|
|
17
|
+
let prefix = 0
|
|
18
|
+
while (
|
|
19
|
+
prefix < originalLines.length
|
|
20
|
+
&& prefix < updatedLines.length
|
|
21
|
+
&& originalLines[prefix] === updatedLines[prefix]
|
|
22
|
+
) {
|
|
23
|
+
prefix += 1
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let suffix = 0
|
|
27
|
+
while (
|
|
28
|
+
suffix < originalLines.length - prefix
|
|
29
|
+
&& suffix < updatedLines.length - prefix
|
|
30
|
+
&& originalLines[originalLines.length - 1 - suffix] === updatedLines[updatedLines.length - 1 - suffix]
|
|
31
|
+
) {
|
|
32
|
+
suffix += 1
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const originalEnd = Math.max(prefix, originalLines.length - suffix)
|
|
36
|
+
const updatedEnd = Math.max(prefix, updatedLines.length - suffix)
|
|
37
|
+
const oldBlock = originalLines.slice(prefix, originalEnd)
|
|
38
|
+
const newBlock = updatedLines.slice(prefix, updatedEnd)
|
|
39
|
+
const startLine = prefix + 1
|
|
40
|
+
const endLine = Math.max(prefix + oldBlock.length, prefix + newBlock.length)
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
changeCount: 1,
|
|
44
|
+
position: buildPositionLabel(startLine, endLine),
|
|
45
|
+
oldSnippet: oldBlock.join('\n'),
|
|
46
|
+
newSnippet: newBlock.join('\n'),
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function buildEditFeedback({
|
|
51
|
+
writeMessage,
|
|
52
|
+
detectedFamily,
|
|
53
|
+
strategy,
|
|
54
|
+
originalContent,
|
|
55
|
+
updatedContent,
|
|
56
|
+
}) {
|
|
57
|
+
const summary = summarizeChangedBlock(originalContent, updatedContent)
|
|
58
|
+
|
|
59
|
+
return [
|
|
60
|
+
writeMessage,
|
|
61
|
+
`检测格式:${detectedFamily}`,
|
|
62
|
+
`修改策略:${strategy}`,
|
|
63
|
+
`修改次数:${summary.changeCount}`,
|
|
64
|
+
`命中位置:${summary.position}`,
|
|
65
|
+
`旧片段:${summary.oldSnippet}`,
|
|
66
|
+
`新片段:${summary.newSnippet}`,
|
|
67
|
+
].join('\n')
|
|
68
|
+
}
|
package/tools/legacy-router.mjs
CHANGED
|
@@ -55,7 +55,7 @@ function globToRegExp(glob) {
|
|
|
55
55
|
|
|
56
56
|
export function createDefaultLegacyRules() {
|
|
57
57
|
return [
|
|
58
|
-
{ glob: '**/*.txt', encoding: '
|
|
58
|
+
{ glob: '**/*.txt', encoding: 'gbk', strict: true },
|
|
59
59
|
{ glob: '**/*.{ini,cfg,dat}', encoding: 'gbk', strict: false },
|
|
60
60
|
];
|
|
61
61
|
}
|