dsh-global-todo 0.12.12 → 0.12.13
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 +14 -0
- package/lib/capture.js +42 -4
- package/lib/index.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [0.12.13] — 2026-10-21
|
|
2
|
+
|
|
3
|
+
The dedupe promise now holds for short titles (#4).
|
|
4
|
+
|
|
5
|
+
- **An appended addendum counts as the same errand, at any title length.** `similar-recently-done`
|
|
6
|
+
relied on symmetric Dice, which mathematically cannot reach 0.8 once 「(再办一次)」 is appended
|
|
7
|
+
to a short title: 「X」 scored 0.00 and 「T3 逾期项」 0.67, both accepted, while the padded test
|
|
8
|
+
title passed. Titles are now compared with their trailing brackets stripped, and rejected when
|
|
9
|
+
the rest is identical and one raw title is a prefix of the other.
|
|
10
|
+
- **A *replaced* parenthetical is still a different errand.** 「修 A(后端)」 vs 「修 A(前端)」 strips
|
|
11
|
+
to the same text but replaces a word instead of appending one, so the prefix test refuses it.
|
|
12
|
+
- **The tests use the examples the comments and release notes promised** — `X`, `T3 逾期项`,
|
|
13
|
+
`写周报`, `配置小米 NAS 证书`, `A` — instead of only a padded title that happened to pass.
|
|
14
|
+
|
|
1
15
|
## [0.12.12] — 2026-10-21
|
|
2
16
|
|
|
3
17
|
Three more silent coercions, found by reading the settings path instead of the tests.
|
package/lib/capture.js
CHANGED
|
@@ -30,6 +30,42 @@ export function normalizeTitle(title) {
|
|
|
30
30
|
return String(title ?? '').normalize('NFKC').toLowerCase().replace(/[\s\p{P}\p{S}]+/gu, '')
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A trailing parenthetical/【…】 addendum, e.g. 「(再办一次)」.
|
|
36
|
+
*
|
|
37
|
+
* Titles like that are the common way a person says "the same errand again", and symmetric
|
|
38
|
+
* Dice cannot see them on short titles: the addendum inflates the denominator, so 「X」 vs
|
|
39
|
+
* 「X(再办一次)」 scores 0 no matter what the threshold is.
|
|
40
|
+
*
|
|
41
|
+
* @param {unknown} title
|
|
42
|
+
* @returns {string}
|
|
43
|
+
*/
|
|
44
|
+
export function stripAddenda(title) {
|
|
45
|
+
const text = String(title ?? '')
|
|
46
|
+
return text.replace(/(\s*[((【\[][^))】\]]*[))】\]]\s*)+$/u, '')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Is `candidate` the same errand as `existing`, with only an addendum appended?
|
|
51
|
+
*
|
|
52
|
+
* The stripped titles must be identical, and one raw title must be a prefix of the other:
|
|
53
|
+
* that rules out 「修 A(后端)」 vs 「修 A(前端)」, which strip to the same text but replace
|
|
54
|
+
* a word instead of appending one.
|
|
55
|
+
*
|
|
56
|
+
* @param {unknown} candidate
|
|
57
|
+
* @param {unknown} existing
|
|
58
|
+
* @returns {boolean}
|
|
59
|
+
*/
|
|
60
|
+
function isAppendedVariant(candidate, existing) {
|
|
61
|
+
const a = normalizeTitle(candidate)
|
|
62
|
+
const b = normalizeTitle(existing)
|
|
63
|
+
if (a === '' || b === '') return false
|
|
64
|
+
const sa = normalizeTitle(stripAddenda(candidate))
|
|
65
|
+
const sb = normalizeTitle(stripAddenda(existing))
|
|
66
|
+
if (sa === '' || sb === '' || sa !== sb) return false
|
|
67
|
+
return a === b || a.startsWith(b) || b.startsWith(a)
|
|
68
|
+
}
|
|
33
69
|
/** @param {string} text @returns {Set<string>} */
|
|
34
70
|
function bigrams(text) {
|
|
35
71
|
const chars = [...text]
|
|
@@ -91,10 +127,12 @@ export function findDuplicate(input) {
|
|
|
91
127
|
if (now - created < windowMs && similarity(title, todo.title) >= threshold) {
|
|
92
128
|
return { duplicate: true, reason: 'similar-active', matchId: todo.id }
|
|
93
129
|
}
|
|
94
|
-
} else if (
|
|
95
|
-
|
|
96
|
-
//
|
|
97
|
-
//
|
|
130
|
+
} else if (
|
|
131
|
+
now - doneAt < cooldownMs &&
|
|
132
|
+
// Symmetric Dice alone only catches long titles: 「X」 vs 「X(再办一次)」 scores 0.
|
|
133
|
+
// An appended addendum is its own signal, so it is checked before the score.
|
|
134
|
+
(isAppendedVariant(title, todo.title) || similarity(title, todo.title) >= threshold)
|
|
135
|
+
) {
|
|
98
136
|
return { duplicate: true, reason: 'similar-recently-done', matchId: todo.id }
|
|
99
137
|
}
|
|
100
138
|
}
|
package/lib/index.js
CHANGED
|
@@ -37,7 +37,7 @@ import { createStore, resolveStorePath } from './store.js'
|
|
|
37
37
|
import { createTools } from './tools.js'
|
|
38
38
|
|
|
39
39
|
/** Must equal `package.json`'s version; `npm run check` enforces that. */
|
|
40
|
-
export const VERSION = '0.12.
|
|
40
|
+
export const VERSION = '0.12.13'
|
|
41
41
|
|
|
42
42
|
/** Plugin id. Also the Loader row id in `cordis.patch.yml`. */
|
|
43
43
|
export const name = 'dsh-global-todo'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-global-todo",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.13",
|
|
4
4
|
"icon": "./docs/icon.svg",
|
|
5
5
|
"description": "Cross-session todo list for DeepSeek Harness: one JSON file a person and an agent both write to, with step plans that ship as a numbered hand-off, session read-back, and a human confirmation before an agent's \"done\" counts.",
|
|
6
6
|
"type": "module",
|