dsh-plan-completion 0.1.3 → 0.1.4
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/CHANGELOG.md +16 -0
- package/README.md +6 -0
- package/docs/publish-assessment.md +3 -1
- package/host/text.js +80 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
All notable changes to this project are documented here.
|
|
4
4
|
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), versioning: SemVer.
|
|
5
5
|
|
|
6
|
+
## [0.1.4] - 2026-11-02
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
- Suggestion/Markdown cleaning no longer destroys identifiers, file names or globs: `transaction_id`,
|
|
10
|
+
`payment_test.py`, `C:\work\my_file.js` and `src/**/*.js` keep their underscores/stars. Only paired
|
|
11
|
+
emphasis markers (`**bold**`, `*italic*`, `_emphasis_`, `~~strike~~`) are stripped; inline code keeps
|
|
12
|
+
its content with backticks removed.
|
|
13
|
+
- Semantic shortening replaces the raw 40-character cut: prefer the last clause boundary, then a
|
|
14
|
+
whitespace boundary, then cut before an ASCII token; never leave a truncated identifier/path or a
|
|
15
|
+
dangling command prefix (`npm`, `npm te`), and drop dangling separators.
|
|
16
|
+
- New `test/text.test.js` covers the identifier/glob/command cases and the old guarantees
|
|
17
|
+
(single line, ≤40 chars, dangerous suggestions filtered).
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- `npm test` now reports **132/132** tests.
|
|
21
|
+
|
|
6
22
|
## [0.1.3] - 2026-11-02
|
|
7
23
|
|
|
8
24
|
### Added
|
package/README.md
CHANGED
|
@@ -85,6 +85,12 @@ Client 侧关键 API(实测):`inputActions.captureInsertion()` 返回 `{st
|
|
|
85
85
|
- 默认目录 `~/.dsh/plan-completion-data`;可用 `storageDir` 改到别处,`persistPlans: false` 退回纯内存;
|
|
86
86
|
- 注意:存储为本地明文 JSON(含计划/建议文本,可能来自会话);不加密、不自动清理,不适合多用户共享机器。
|
|
87
87
|
|
|
88
|
+
## 建议文本安全(v0.1.4)
|
|
89
|
+
|
|
90
|
+
- 去 Markdown 只剥离**成对**的行内标记;`transaction_id`、`payment_test.py`、`C:\a_b\file.js`、`src/**/*.js` 等标识符/路径/glob 原样保留;
|
|
91
|
+
- 超过 40 字时按分句/空白/ASCII token 边界语义化缩短,不再把 `npm test` 截成 `npm`/`npm te`,也不会切断标识符或路径;
|
|
92
|
+
- 危险命令过滤(rm -rf、format、Drop DB 等)不变;单行、≤40 字、可直接发送的约束不变。
|
|
93
|
+
|
|
88
94
|
## 模型路由与隐私
|
|
89
95
|
|
|
90
96
|
- 仅使用宿主 `agentDefaultModel.currentSelection()` 当前默认的 provider/model,不自动改投其他已配置供应商;无可用路由时使用启发式。该默认值不一定是历史会话原先使用的模型。
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# GitHub 发布评估 · 会话计划补全插件(2026-11-02)
|
|
2
2
|
|
|
3
|
-
> 历史文档:以下保留 v0.1.0 的交付/评估/计划记录。**当前最新为 v0.1.
|
|
3
|
+
> 历史文档:以下保留 v0.1.0 的交付/评估/计划记录。**当前最新为 v0.1.3**(核对进度 + 本地计划持久化 + 鉴权/状态一致性/隐私边界修复),详见 [security.md](security.md) 与 [CHANGELOG](../CHANGELOG.md);旧记录中的“无鉴权”、原始诊断字段与独立评审不代表当前实现。
|
|
4
|
+
>
|
|
5
|
+
> v0.1.3 发布记录(2026-11-02):tag `v0.1.3` + [Release](https://github.com/Xumi0920/dsh-plan-completion/releases/tag/v0.1.3);GitHub Actions [run 41368609382](https://github.com/Xumi0920/dsh-plan-completion/actions/runs/41368609382)(Ubuntu/Windows × Node20/22 四 job 全绿,126/126 tests);npm `dsh-plan-completion@0.1.3`(31 files,shasum `11adee0fe9acff199a180be511ca8bc600afdf6f`);canary 验证 `/plan-completion/verify` 未认证 401、客户端鉴权请求正常。
|
|
4
6
|
>
|
|
5
7
|
> v0.1.1 发布记录(2026-11-02):tag `v0.1.1` + [Release](https://github.com/Xumi0920/dsh-plan-completion/releases/tag/v0.1.1);GitHub Actions [run 41366651557](https://github.com/Xumi0920/dsh-plan-completion/actions/runs/41366651557)(Ubuntu/Windows × Node20/22 四 job 全绿);npm `dsh-plan-completion@0.1.1`(31 files,shasum `18989b0f50b72d5c8c332f3fdee7f43ccb5fd91a`)。
|
|
6
8
|
>
|
package/host/text.js
CHANGED
|
@@ -49,12 +49,17 @@ export function oneLine(value) {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
const MD_LINE_PREFIX = /^\s{0,3}(?:#{1,6}\s+|>\s*|[-*+]\s+|\d+[.)]\s+)/;
|
|
52
|
-
const MD_INLINE = /(\*\*|__|\*|_|`)/g;
|
|
53
52
|
const MD_LINK = /\[([^\]]*)\]\([^)]*\)/g;
|
|
54
53
|
const WRAPPING_QUOTES = /^["'“”‘’「」『』]+|["'“”‘’「」『』]+$/g;
|
|
54
|
+
const CLAUSE_BOUNDARY = /[,。;!?、,;!?]/;
|
|
55
|
+
const ASCII_TOKEN_CHAR = /[A-Za-z0-9_./\\-]/;
|
|
55
56
|
|
|
56
57
|
/**
|
|
57
58
|
* 去掉常见 Markdown 痕迹,得到适合直接发送的纯文本。
|
|
59
|
+
*
|
|
60
|
+
* 只剥离「成对」的行内标记:`transaction_id`、`C:\a_b\file.js`、`src/**\/*.js`
|
|
61
|
+
* 这类标识符/路径/glob 里的 `_`、`*` 不再被误删;
|
|
62
|
+
* 反引号只去掉包裹符号,代码内容原样保留。
|
|
58
63
|
* @param {unknown} value
|
|
59
64
|
* @returns {string}
|
|
60
65
|
*/
|
|
@@ -66,18 +71,89 @@ export function stripMarkdown(value) {
|
|
|
66
71
|
.map((line) => line.replace(MD_LINE_PREFIX, ''))
|
|
67
72
|
.join(' ');
|
|
68
73
|
text = text.replace(MD_LINK, '$1');
|
|
69
|
-
|
|
74
|
+
// Protect glob/star patterns (src/**/*.js, *.py, **/) before emphasis stripping.
|
|
75
|
+
const stars = [];
|
|
76
|
+
text = text.replace(/\*\*\/|\*\/|\*\.[A-Za-z0-9_-]+/g, (match) => {
|
|
77
|
+
stars.push(match);
|
|
78
|
+
return `\u0000${stars.length - 1}\u0000`;
|
|
79
|
+
});
|
|
80
|
+
// Inline code: drop the backtick delimiters, keep the code text (underscores intact).
|
|
81
|
+
text = text.replace(/`([^`\n]+)`/g, '$1').replace(/`/g, '');
|
|
82
|
+
// Paired emphasis markers only. Never strip a lone _/* that belongs to an identifier,
|
|
83
|
+
// file name or glob (e.g. transaction_id, foo_bar, *.js).
|
|
84
|
+
text = text
|
|
85
|
+
.replace(/\*\*([^*\n]+)\*\*/g, '$1')
|
|
86
|
+
.replace(/__([^_\n]+)__/g, '$1')
|
|
87
|
+
.replace(/~~([^~\n]+)~~/g, '$1')
|
|
88
|
+
.replace(/\*([^*\n]+)\*/g, '$1')
|
|
89
|
+
.replace(/(^|[^\p{L}\p{N}_])_([^_\n]+)_(?=[^\p{L}\p{N}_]|$)/gu, '$1$2');
|
|
90
|
+
text = text.replace(/\u0000(\d+)\u0000/g, (_match, index) => stars[Number(index)] ?? '');
|
|
70
91
|
return oneLine(text).replace(WRAPPING_QUOTES, '').trim();
|
|
71
92
|
}
|
|
72
93
|
|
|
73
94
|
/**
|
|
74
|
-
*
|
|
95
|
+
* 语义化截短:不把标识符/路径/命令从中间切断。
|
|
96
|
+
*
|
|
97
|
+
* 依次尝试:句子/分句边界(,。;!?、,;!?)→ 空白边界 → 回退到 ASCII token 起点。
|
|
98
|
+
* 实在没有边界时才硬截断,保证返回长度 ≤ max。
|
|
99
|
+
* @param {unknown} value
|
|
100
|
+
* @param {number} [max]
|
|
101
|
+
* @returns {string}
|
|
102
|
+
*/
|
|
103
|
+
export function shortenSuggestion(value, max = DEFAULTS.suggestionMaxChars) {
|
|
104
|
+
const text = oneLine(value);
|
|
105
|
+
const limit = Number.isFinite(max) ? Math.floor(max) : 0;
|
|
106
|
+
if (limit <= 0) return '';
|
|
107
|
+
const chars = Array.from(text);
|
|
108
|
+
if (chars.length <= limit) return text;
|
|
109
|
+
|
|
110
|
+
const prefix = chars.slice(0, limit).join('');
|
|
111
|
+
const minUseful = Math.max(1, Math.floor(limit * 0.4));
|
|
112
|
+
|
|
113
|
+
// 1) Prefer the last clause boundary so the returned text stays a complete clause.
|
|
114
|
+
for (let i = prefix.length - 1; i >= minUseful; i -= 1) {
|
|
115
|
+
if (!CLAUSE_BOUNDARY.test(chars[i])) continue;
|
|
116
|
+
// Keep sentence-ending punctuation; drop separators such as ,、;: so the
|
|
117
|
+
// returned suggestion still reads like a message the user can send directly.
|
|
118
|
+
const keep = /[。!?!?]/.test(chars[i]) ? 1 : 0;
|
|
119
|
+
const head = chars.slice(0, i + keep).join('').trim();
|
|
120
|
+
if (head) return head;
|
|
121
|
+
}
|
|
122
|
+
// 2) Whitespace boundary: never leaves a partially copied word after it.
|
|
123
|
+
for (let i = prefix.length - 1; i >= minUseful; i -= 1) {
|
|
124
|
+
if (/\s/.test(chars[i])) {
|
|
125
|
+
let head = chars.slice(0, i).join('').trim();
|
|
126
|
+
// If the cut leaves a lone ASCII command prefix ("…再重跑 npm") and the next
|
|
127
|
+
// character is another ASCII token, drop that dangling token too.
|
|
128
|
+
const next = chars[i + 1] ?? '';
|
|
129
|
+
const tail = head.match(/(?:^|\s)([A-Za-z0-9_./\\-]+)$/);
|
|
130
|
+
if (tail && ASCII_TOKEN_CHAR.test(next)) {
|
|
131
|
+
head = head.slice(0, head.length - tail[1].length).trim().replace(/[\s,。;、,;::-]+$/, '');
|
|
132
|
+
}
|
|
133
|
+
if (head) return head;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// 3) Do not split an ASCII identifier/path/command token: cut before the token.
|
|
137
|
+
let cut = limit;
|
|
138
|
+
while (cut > 0 && ASCII_TOKEN_CHAR.test(chars[cut - 1] ?? '') && ASCII_TOKEN_CHAR.test(chars[cut] ?? '')) {
|
|
139
|
+
cut -= 1;
|
|
140
|
+
}
|
|
141
|
+
if (cut > 0) {
|
|
142
|
+
const head = chars.slice(0, cut).join('').trim().replace(/[\s,。;、,;::-]+$/, '');
|
|
143
|
+
if (head) return head;
|
|
144
|
+
}
|
|
145
|
+
// 4) Single oversized token: hard clamp (still ≤ limit).
|
|
146
|
+
return prefix;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 建议文本规范化:单行 + 去 Markdown + 语义化截断到 max 字。
|
|
75
151
|
* @param {unknown} value
|
|
76
152
|
* @param {number} [max]
|
|
77
153
|
* @returns {string}
|
|
78
154
|
*/
|
|
79
155
|
export function clampSuggestion(value, max = DEFAULTS.suggestionMaxChars) {
|
|
80
|
-
return
|
|
156
|
+
return shortenSuggestion(stripMarkdown(value), max);
|
|
81
157
|
}
|
|
82
158
|
|
|
83
159
|
/**
|