dsh-structured 0.9.6 → 0.9.9

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.
@@ -1,140 +1,145 @@
1
- import test from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { readFileSync } from 'node:fs'
4
- import vm from 'node:vm'
5
- import { parseHTML } from 'linkedom'
6
-
7
- const clientSource = readFileSync('src/client.js', 'utf8')
8
-
9
- function render(html) {
10
- const { window, document } = parseHTML(`<!doctype html><html><head></head><body>${html}</body></html>`)
11
- let definition
12
- const sandbox = {
13
- window: {
14
- __ModuleLoader__: {
15
- load(value) {
16
- definition = value
17
- },
18
- },
19
- },
20
- document,
21
- MutationObserver: class {
22
- observe() {}
23
- disconnect() {}
24
- },
25
- requestAnimationFrame(fn) {
26
- fn()
27
- return 1
28
- },
29
- cancelAnimationFrame() {},
30
- setTimeout,
31
- clearTimeout,
32
- console,
33
- }
34
- sandbox.globalThis = sandbox
35
- vm.runInNewContext(clientSource, sandbox)
36
- assert.ok(definition, 'client module definition captured')
37
- const plugin = definition.factory()
38
- plugin.apply({
39
- inject(_services, callback) {
40
- callback({ configForms: { get: () => null } })
41
- },
42
- effect() {},
43
- })
44
- return document
45
- }
46
-
47
- function block(document, id) {
48
- const element = document.querySelector(`#${id}`)
49
- assert.ok(element, `missing #${id}`)
50
- return element
51
- }
52
-
53
- function assertPlain(document, id) {
54
- const element = block(document, id)
55
- assert.equal(element.getAttribute('data-dsh-structured-card'), null, `${id} card`)
56
- assert.equal(element.getAttribute('data-dsh-structured-card-type'), null, `${id} type`)
57
- assert.equal(element.getAttribute('data-dsh-structured-tone'), null, `${id} tone`)
58
- assert.equal(element.getAttribute('data-dsh-structured-lead'), null, `${id} lead`)
59
- }
60
-
61
- function assertCard(document, id, type, tone) {
62
- const element = block(document, id)
63
- assert.equal(element.getAttribute('data-dsh-structured-card'), '1', `${id} card`)
64
- assert.equal(element.getAttribute('data-dsh-structured-card-type'), type, `${id} type`)
65
- assert.equal(element.getAttribute('data-dsh-structured-tone'), tone, `${id} tone`)
66
- assert.equal(element.getAttribute('data-dsh-structured-lead'), null, `${id} lead`)
67
- }
68
-
69
- test('严格卡片:复合标签、普通引用不升级,完整标签才成卡', () => {
70
- const document = render(`
71
- <div class="_markdown_q2"><blockquote id="q2"><p><strong>风险提示</strong><br>影响:宿主升级后可能失效。</p></blockquote></div>
72
- <div class="_markdown_test">
73
- <blockquote id="q1"><p>风险提示:宿主升级后可能失效。</p></blockquote>
74
- <blockquote id="q3"><p>结论:这是用户引用的原文。</p></blockquote>
75
- <blockquote id="q4"><p>这是普通引用,没有加粗标题。</p></blockquote>
76
- <blockquote id="q9"><p><strong>判断依据</strong><br>不应成卡。</p></blockquote>
77
- </div>
78
- <div class="_markdown_q10"><blockquote id="q10"><p>前缀 <strong>标题</strong></p></blockquote></div>
79
- <div class="_markdown_q11"><blockquote id="q11"><p> <strong>标题</strong></p></blockquote></div>
80
- <div class="_markdown_q18"><blockquote id="q18"><p>前缀 <strong>标题</strong>尾正文</p></blockquote></div>
81
- <div class="_markdown_q16"><blockquote id="q16"><p><strong>结论性说明</strong><br>不应成卡。</p></blockquote></div>
82
- <div class="_markdown_q17"><blockquote id="q17"><p><strong>指标数据</strong><br>不应成卡。</p></blockquote></div>
83
- <div class="_markdown_test2">
84
- <blockquote id="q5"><p><strong>Phase 1 完成</strong><br>28/28 通过。</p></blockquote>
85
- <blockquote id="q5b"><p><strong>Phase 2 完成</strong><br>不应升级为 statement。</p></blockquote>
86
- <blockquote id="q6"><p><strong>风险</strong><br>影响:宿主升级后可能失效。</p></blockquote>
87
- <blockquote id="q7"><p><strong>下一步</strong><br>1. 安装测试 profile。</p></blockquote>
88
- <blockquote id="q8"><p><strong>验收</strong><br>✅ 3/3</p></blockquote>
89
- <blockquote id="q12"><p><strong>结论</strong><br>结论卡。</p></blockquote>
90
- <blockquote id="q13"><p><strong>决策</strong><br>决策卡。</p></blockquote>
91
- <blockquote id="q14"><p><strong>指标</strong><br>指标卡。</p></blockquote>
92
- <blockquote id="q15"><p><strong>重点</strong><br>重点卡。</p></blockquote>
93
- <div data-genui="1">
94
- <blockquote id="gen"><p><strong>风险</strong><br>GenUI 内不处理。</p></blockquote>
95
- </div>
96
- </div>
97
- <div class="_markdown_test5">
98
- <div data-genui="1">
99
- <blockquote id="genfirst"><p><strong>风险</strong><br>GenUI 内不处理,也不应阻断真实首块。</p></blockquote>
100
- </div>
101
- <blockquote id="real"><p><strong>Phase 9 完成</strong><br>真实首块仍应升级 statement。</p></blockquote>
102
- </div>
103
- `)
104
-
105
- // 反例:前缀/复合词、普通引用、strong 前有正文或空白
106
- assertPlain(document, 'q1')
107
- assertPlain(document, 'q2')
108
- assertPlain(document, 'q3')
109
- assertPlain(document, 'q4')
110
- assertPlain(document, 'q9')
111
- assertPlain(document, 'q10')
112
- assertPlain(document, 'q11')
113
- assertPlain(document, 'q18')
114
- assertPlain(document, 'q16')
115
- assertPlain(document, 'q17')
116
-
117
- // 正例:全部卡型 + 仅第一块可升级 + GenUI 前置不阻断真实首块
118
- assertCard(document, 'q5', 'statement', 'info')
119
- assertCard(document, 'q6', 'risk', 'warn')
120
- assertCard(document, 'q7', 'next', 'info')
121
- assertCard(document, 'q8', 'accept', 'success')
122
- assertCard(document, 'q12', 'conclusion', 'info')
123
- assertCard(document, 'q13', 'decision', 'info')
124
- assertCard(document, 'q14', 'metric', 'info')
125
- assertCard(document, 'q15', 'key', 'info')
126
- assertCard(document, 'real', 'statement', 'info')
127
- assertPlain(document, 'q5b')
128
-
129
- // GenUI 区域跳过
130
- assertPlain(document, 'gen')
131
- assertPlain(document, 'genfirst')
132
- })
133
-
134
- test('严格卡片:不再有普通首引用 lead 与文本前缀 tone 逻辑', () => {
135
- assert.doesNotMatch(clientSource, /isFirstBlockquote/)
136
- assert.doesNotMatch(clientSource, /toneFor/)
137
- assert.doesNotMatch(clientSource, /setAttribute\(LEAD_ATTR,\s*'lead'\)/)
138
- assert.match(clientSource, /CARD_TYPES = new Map/)
139
- assert.match(clientSource, /isStatementCard/)
140
- })
1
+ import test from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { readFileSync } from 'node:fs'
4
+ import vm from 'node:vm'
5
+ import { parseHTML } from 'linkedom'
6
+
7
+ const clientSource = readFileSync('src/client.js', 'utf8')
8
+
9
+ function render(html) {
10
+ const { window, document } = parseHTML(`<!doctype html><html><head></head><body>${html}</body></html>`)
11
+ let definition
12
+ const sandbox = {
13
+ window: {
14
+ __ModuleLoader__: {
15
+ load(value) {
16
+ definition = value
17
+ },
18
+ },
19
+ },
20
+ document,
21
+ MutationObserver: class {
22
+ observe() {}
23
+ disconnect() {}
24
+ },
25
+ requestAnimationFrame(fn) {
26
+ fn()
27
+ return 1
28
+ },
29
+ cancelAnimationFrame() {},
30
+ setTimeout,
31
+ clearTimeout,
32
+ console,
33
+ }
34
+ sandbox.globalThis = sandbox
35
+ vm.runInNewContext(clientSource, sandbox)
36
+ assert.ok(definition, 'client module definition captured')
37
+ const plugin = definition.factory()
38
+ plugin.apply({
39
+ inject(_services, callback) {
40
+ callback({ configForms: { get: () => null } })
41
+ },
42
+ effect() {},
43
+ })
44
+ return document
45
+ }
46
+
47
+ function block(document, id) {
48
+ const element = document.querySelector(`#${id}`)
49
+ assert.ok(element, `missing #${id}`)
50
+ return element
51
+ }
52
+
53
+ function assertPlain(document, id) {
54
+ const element = block(document, id)
55
+ assert.equal(element.getAttribute('data-dsh-structured-card'), null, `${id} card`)
56
+ assert.equal(element.getAttribute('data-dsh-structured-card-type'), null, `${id} type`)
57
+ assert.equal(element.getAttribute('data-dsh-structured-tone'), null, `${id} tone`)
58
+ assert.equal(element.getAttribute('data-dsh-structured-lead'), null, `${id} lead`)
59
+ }
60
+
61
+ function assertCard(document, id, type, tone) {
62
+ const element = block(document, id)
63
+ assert.equal(element.getAttribute('data-dsh-structured-card'), '1', `${id} card`)
64
+ assert.equal(element.getAttribute('data-dsh-structured-card-type'), type, `${id} type`)
65
+ assert.equal(element.getAttribute('data-dsh-structured-tone'), tone, `${id} tone`)
66
+ assert.equal(element.getAttribute('data-dsh-structured-lead'), null, `${id} lead`)
67
+ }
68
+
69
+ test('0.9.7 client 不改写消息里的 $...$', () => {
70
+ const document = render('<p id="dollar">成本 $5,公式 $x^2$</p>')
71
+ assert.equal(document.getElementById('dollar').textContent, '成本 $5,公式 $x^2$')
72
+ })
73
+
74
+ test('严格卡片:复合标签、普通引用不升级,完整标签才成卡', () => {
75
+ const document = render(`
76
+ <div class="_markdown_q2"><blockquote id="q2"><p><strong>风险提示</strong><br>影响:宿主升级后可能失效。</p></blockquote></div>
77
+ <div class="_markdown_test">
78
+ <blockquote id="q1"><p>风险提示:宿主升级后可能失效。</p></blockquote>
79
+ <blockquote id="q3"><p>结论:这是用户引用的原文。</p></blockquote>
80
+ <blockquote id="q4"><p>这是普通引用,没有加粗标题。</p></blockquote>
81
+ <blockquote id="q9"><p><strong>判断依据</strong><br>不应成卡。</p></blockquote>
82
+ </div>
83
+ <div class="_markdown_q10"><blockquote id="q10"><p>前缀 <strong>标题</strong></p></blockquote></div>
84
+ <div class="_markdown_q11"><blockquote id="q11"><p> <strong>标题</strong></p></blockquote></div>
85
+ <div class="_markdown_q18"><blockquote id="q18"><p>前缀 <strong>标题</strong>尾正文</p></blockquote></div>
86
+ <div class="_markdown_q16"><blockquote id="q16"><p><strong>结论性说明</strong><br>不应成卡。</p></blockquote></div>
87
+ <div class="_markdown_q17"><blockquote id="q17"><p><strong>指标数据</strong><br>不应成卡。</p></blockquote></div>
88
+ <div class="_markdown_test2">
89
+ <blockquote id="q5"><p><strong>Phase 1 完成</strong><br>28/28 通过。</p></blockquote>
90
+ <blockquote id="q5b"><p><strong>Phase 2 完成</strong><br>不应升级为 statement。</p></blockquote>
91
+ <blockquote id="q6"><p><strong>风险</strong><br>影响:宿主升级后可能失效。</p></blockquote>
92
+ <blockquote id="q7"><p><strong>下一步</strong><br>1. 安装测试 profile。</p></blockquote>
93
+ <blockquote id="q8"><p><strong>验收</strong><br>✅ 3/3</p></blockquote>
94
+ <blockquote id="q12"><p><strong>结论</strong><br>结论卡。</p></blockquote>
95
+ <blockquote id="q13"><p><strong>决策</strong><br>决策卡。</p></blockquote>
96
+ <blockquote id="q14"><p><strong>指标</strong><br>指标卡。</p></blockquote>
97
+ <blockquote id="q15"><p><strong>重点</strong><br>重点卡。</p></blockquote>
98
+ <div data-genui="1">
99
+ <blockquote id="gen"><p><strong>风险</strong><br>GenUI 内不处理。</p></blockquote>
100
+ </div>
101
+ </div>
102
+ <div class="_markdown_test5">
103
+ <div data-genui="1">
104
+ <blockquote id="genfirst"><p><strong>风险</strong><br>GenUI 内不处理,也不应阻断真实首块。</p></blockquote>
105
+ </div>
106
+ <blockquote id="real"><p><strong>Phase 9 完成</strong><br>真实首块仍应升级 statement。</p></blockquote>
107
+ </div>
108
+ `)
109
+
110
+ // 反例:前缀/复合词、普通引用、strong 前有正文或空白
111
+ assertPlain(document, 'q1')
112
+ assertPlain(document, 'q2')
113
+ assertPlain(document, 'q3')
114
+ assertPlain(document, 'q4')
115
+ assertPlain(document, 'q9')
116
+ assertPlain(document, 'q10')
117
+ assertPlain(document, 'q11')
118
+ assertPlain(document, 'q18')
119
+ assertPlain(document, 'q16')
120
+ assertPlain(document, 'q17')
121
+
122
+ // 正例:全部卡型 + 仅第一块可升级 + GenUI 前置不阻断真实首块
123
+ assertCard(document, 'q5', 'statement', 'info')
124
+ assertCard(document, 'q6', 'risk', 'warn')
125
+ assertCard(document, 'q7', 'next', 'info')
126
+ assertCard(document, 'q8', 'accept', 'success')
127
+ assertCard(document, 'q12', 'conclusion', 'info')
128
+ assertCard(document, 'q13', 'decision', 'info')
129
+ assertCard(document, 'q14', 'metric', 'info')
130
+ assertCard(document, 'q15', 'key', 'info')
131
+ assertCard(document, 'real', 'statement', 'info')
132
+ assertPlain(document, 'q5b')
133
+
134
+ // GenUI 区域跳过
135
+ assertPlain(document, 'gen')
136
+ assertPlain(document, 'genfirst')
137
+ })
138
+
139
+ test('严格卡片:不再有普通首引用 lead 与文本前缀 tone 逻辑', () => {
140
+ assert.doesNotMatch(clientSource, /isFirstBlockquote/)
141
+ assert.doesNotMatch(clientSource, /toneFor/)
142
+ assert.doesNotMatch(clientSource, /setAttribute\(LEAD_ATTR,\s*'lead'\)/)
143
+ assert.match(clientSource, /CARD_TYPES = new Map/)
144
+ assert.match(clientSource, /isStatementCard/)
145
+ })
@@ -0,0 +1,8 @@
1
+ # Gate fixture:无 persona
2
+
3
+ 仅使用 `BUILTIN_STYLE_TEXT` 的结构规则;不追加 persona。
4
+
5
+ ## 观察点
6
+
7
+ - 首屏标题卡/表格/列表/结论是否正常。
8
+ - 无个人信息、无额外 system 文本。
@@ -0,0 +1,9 @@
1
+ # Gate fixture:persona.md 追加
2
+
3
+ 模拟 1.0 目标:结构规则 + `persona.md` 追加层。
4
+
5
+ ## 观察点
6
+
7
+ - 结构规则仍存在;persona 只追加,不覆盖。
8
+ - 追加文本不执行命令、不改变工具权限/GenUI/代码隔离。
9
+ - 记录注入与输出 token、结构保留、偏好、P0。
@@ -0,0 +1,8 @@
1
+ # Gate fixture:style.md 覆盖
2
+
3
+ 模拟用户存在非空 `style.md`,整份覆盖 base。
4
+
5
+ ## 观察点
6
+
7
+ - 结构规则是否按现有契约消失/保留(由 style.md 内容决定)。
8
+ - persona 不参与本组;不出现追加层。
@@ -28,6 +28,14 @@ import {
28
28
  } from '../src/index.js'
29
29
  import { classify, firstScreenStructured } from '../scripts/measure-prose.mjs'
30
30
 
31
+ test('0.9.7 公式规避规则:金额用转义/反引号,不写裸 $...$', () => {
32
+ assert.match(BUILTIN_STYLE_TEXT, /金额\/美元符号/)
33
+ assert.match(BUILTIN_STYLE_TEXT, /\\\$/)
34
+ assert.match(BUILTIN_STYLE_TEXT, /反引号/)
35
+ assert.match(BUILTIN_STYLE_TEXT, /不要写裸 `\$\.\.\.\$`/)
36
+ })
37
+
38
+
31
39
  function isolate() {
32
40
  const home = mkdtempSync(join(tmpdir(), 'dsh-structured-test-'))
33
41
  const saved = { dir: process.env.DSH_PROFILE_DIR, profile: process.env.DSH_PROFILE }
@@ -3,6 +3,7 @@ import { readFileSync } from 'node:fs'
3
3
  import test from 'node:test'
4
4
  import { Config, statusText, THEMES } from '../src/index.js'
5
5
  import { checkThemeContrast, THEME_TOKENS } from '../scripts/theme-contrast.mjs'
6
+ import { checkHueSeparation } from '../scripts/theme-hue-distance.mjs'
6
7
 
7
8
  const value = (field) => (field && typeof field.get === 'function' ? field.get() : field)
8
9
 
@@ -14,7 +15,13 @@ test('0.9.0 Config:theme 默认 deepseek,三值可选', () => {
14
15
  assert.equal(value(Config({ defaultEnabled: false }).defaultEnabled), false)
15
16
  })
16
17
 
17
- test('0.9.1 Config:六个字段的默认值与显式值', () => {
18
+ test('0.9.8 accent/status hue 分离:12 项通过', () => {
19
+ const { results, failures } = checkHueSeparation()
20
+ assert.equal(failures.length, 0, JSON.stringify(failures, null, 2))
21
+ assert.equal(results.length, 12)
22
+ })
23
+
24
+ test('0.9.8 Config:六个字段的默认值与显式值', () => {
18
25
  const defaults = Config({})
19
26
  assert.equal(value(defaults.theme), 'deepseek')
20
27
  assert.equal(value(defaults.visual), true)