@chengzhao-dev/dsh-cpp-workflow 0.6.8 → 0.6.10

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.en.md CHANGED
@@ -19,20 +19,20 @@ The exception material follows the C++ Core Guidelines' Error handling section a
19
19
  Pinned npm install:
20
20
 
21
21
  ```bash
22
- dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.8
22
+ dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.10
23
23
  ```
24
24
 
25
25
  The matching Git tag:
26
26
 
27
27
  ```bash
28
- dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.8
28
+ dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.10
29
29
  ```
30
30
 
31
31
  The desktop app owns the `desktop` profile. If the command-line `add` is rejected, add the plugin in the app settings and use the same pinned npm version. A Git tag install uses the prebuilt `lib/` committed in the repository and runs no build on the target machine; the package has no install script that needs approval.
32
32
 
33
33
  ### Settings
34
34
 
35
- The plugin page's "C++ Workflow" card is the only entry point and manages file-header tags only: `@file` is always the file name, `@brief` is written by default, and `@author`, `@date`, `@copyright`, `@version` stay off and are written only when they hold a non-empty value; `@param` is required for every parameter; `@return` is written by default but may be omitted when the condition is self-evident; `@throws` only when the function throws. The preview follows the switches, saving takes effect on the next skill read, and existing sources are never rewritten. Settings navigation no longer duplicates the form.
35
+ The plugin page's "C++ Workflow" card is the only entry point and manages file-header tags only: `@file` is always the file name, and every file-header, type, and declaration tag is on by default except the fixed `//` body symbol; empty values are skipped. `@param` is required for every parameter; `@return` may be omitted when the condition is self-evident; `@throws` only when the function throws; `@pre`, `@post`, `@retval`, `@tparam`, `@note`, and `@see` are on by default and can be switched off individually. The preview follows the switches, saving takes effect on the next skill read, and existing sources are never rewritten. Settings navigation no longer duplicates the form.
36
36
 
37
37
  If the page still reports that it is not connected to a configuration form after an upgrade, the window is still running the previous client bundle: restart DeepSeek Harness or select "Reload page" on the plugin page. The page retries the connection while open and switches to the editable form as soon as the host serves it; reinstalling is unnecessary. Discard changes, Restore defaults and Save are separate: discard returns to the last value read from the host, restore fills the draft, and only Save writes it.
38
38
 
package/README.md CHANGED
@@ -19,20 +19,20 @@
19
19
  npm 安装钉死版本:
20
20
 
21
21
  ```bash
22
- dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.8
22
+ dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.10
23
23
  ```
24
24
 
25
25
  同版本的 Git 标签:
26
26
 
27
27
  ```bash
28
- dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.8
28
+ dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.10
29
29
  ```
30
30
 
31
31
  桌面应用独占 `desktop` profile。命令行 `add` 被拒绝时,在应用设置里添加插件,地址填同一个 npm 版本。Git 标签安装使用仓库内已构建的 `lib/`,不在目标机器上重新构建;包内也不含需要授权执行的安装脚本。
32
32
 
33
33
  ### 设置
34
34
 
35
- 插件页的「C++ 工程流程」是唯一入口:注释符号可选 `///` 或 `/** ... */`,页面按注释符号、文件头、类型、函数声明与只读预览分块。`@file` 固定为文件名;`@brief` 默认开启;`@author`、`@date`、`@copyright`、`@version` 默认关闭,空值跳过。函数参数默认要求 `@param`,返回值默认要求 `@return`(成立条件自明时可省),会抛才要求 `@throws`;`@pre`、`@post`、`@retval` 与类型块的 `@tparam`、`@note`、`@see` 默认关闭。函数体固定用 `//`。预览随改动即时更新,保存后下一次读取技能即生效,已有源码不会被改写。设置导航不再重复放一份。
35
+ 插件页的「C++ 工程流程」是唯一入口:注释符号可选 `///` 或 `/** ... */`,页面按注释符号、文件头、类型、函数声明与只读预览分块。`@file` 固定为文件名;除它和函数体 `//` 外,文件头、类型与函数声明标签的开关出厂全开,空值跳过。函数参数要求 `@param`,返回值要求 `@return`(成立条件自明时可省),会抛才要求 `@throws`;`@pre`、`@post`、`@retval` 与类型块的 `@tparam`、`@note`、`@see` 也出厂开启,需要时可逐项关掉。函数体固定用 `//`。预览随改动即时更新,保存后下一次读取技能即生效,已有源码不会被改写。设置导航不再重复放一份。
36
36
 
37
37
  升级插件后如果这一页暂时只显示出厂值,说明当前窗口还挂着旧的前端产物:页脚会显示写入的 `~/.dsh/profiles/<profile>/cordis.patch.yml`,并提示重载;重启 DeepSeek Harness,或在插件页点“重新载入页面”即可编辑,不需要重装插件。页面会在打开后自动重试连接,拿到表单就切换为可编辑。保存、撤销修改与恢复默认值分三个按钮:撤销只回到最近一次读到的值,恢复默认值只填草稿,点保存才写盘。
38
38
 
package/lib/client.js CHANGED
@@ -19,9 +19,9 @@ window.__ModuleLoader__.load({
19
19
  const HEADER_TAGS = ['brief', 'author', 'date', 'copyright', 'version'];
20
20
  /** 函数声明标签:开关决定是否要求写出。 */
21
21
  const CONTRACT_TAGS = ['param', 'returns', 'throws'];
22
- /** 函数声明里默认关闭的补充标签。 */
22
+ /** 函数声明里出厂开启的补充标签。 */
23
23
  const FUNCTION_EXTRA_TAGS = ['pre', 'post', 'retval'];
24
- /** 类型标签:默认关闭,函数模板共用同一组键。 */
24
+ /** 类型标签:出厂开启,函数模板共用同一组键。 */
25
25
  const TYPE_TAGS = ['tparam', 'note', 'see'];
26
26
  /** 全部可选标签,快照与测试按它排序。 */
27
27
  const EXTRA_TAGS = ['note', 'see', 'pre', 'post', 'tparam', 'retval'];
@@ -49,23 +49,25 @@ const TAG_LABEL = {
49
49
  const SAMPLE_BRIEF = '<一句话说明本文件>';
50
50
  /** 预览里的示例文件名:只是占位符,不对应任何工程的真实文件。 */
51
51
  const SAMPLE_FILE = '<文件名>';
52
- /** 出厂配置:声明用 `///`,文件头只留 @file 与 @brief,契约标签全开,其余关闭。 */
52
+ /**
53
+ * 出厂配置:声明用 `///`,除 @file 与函数体 // 外全部开启;空值仍不写文件头。
54
+ */
53
55
  const DEFAULT_CONFIG = {
54
56
  commentStyle: 'slash',
55
57
  brief: { enabled: true, value: '' },
56
- author: { enabled: false, value: '' },
57
- date: { enabled: false, value: '' },
58
- copyright: { enabled: false, value: '' },
59
- version: { enabled: false, value: '' },
58
+ author: { enabled: true, value: '' },
59
+ date: { enabled: true, value: '' },
60
+ copyright: { enabled: true, value: '' },
61
+ version: { enabled: true, value: '' },
60
62
  param: true,
61
63
  returns: true,
62
64
  throws: true,
63
- note: false,
64
- see: false,
65
- pre: false,
66
- post: false,
67
- tparam: false,
68
- retval: false,
65
+ note: true,
66
+ see: true,
67
+ pre: true,
68
+ post: true,
69
+ tparam: true,
70
+ retval: true,
69
71
  };
70
72
  /**
71
73
  * 宿主把 volatile 字段交给插件时给的是可读写单元(`.get()`),设置页快照给的是普通值;
@@ -236,28 +238,36 @@ const STRINGS = {
236
238
  zh: {
237
239
  nav: 'C++ 工程流程',
238
240
  heading: '文档注释',
241
+ lede: '文件头、类型与函数声明各自决定默认写入哪些标签;预览随改动即时更新。',
239
242
  style: '注释符号',
243
+ styleHint: '声明用哪种注释;函数体始终用 //。',
240
244
  bodyRow: '函数体',
241
- bodyPlaceholder: '函数体:固定 //',
245
+ bodyPlaceholder: '固定 //',
242
246
  header: '文件头',
247
+ headerHint: '开关打开后填写内容;空值不会写进文件。',
243
248
  type: '类型',
249
+ typeHint: '类型块与函数模板里的补充标签。',
244
250
  declaration: '函数声明',
251
+ declarationHint: '函数契约与前置、后置条件。',
245
252
  preview: '预览',
253
+ previewHint: '代码块随开关和输入即时更新。',
246
254
  filePlaceholder: '跟随文件名',
247
255
  briefPlaceholder: '文件摘要:留空则各文件自写',
248
- authorPlaceholder: '作者署名:默认不写',
249
- datePlaceholder: '日期:默认不写',
250
- copyrightPlaceholder: '版权:默认不写',
251
- versionPlaceholder: '版本:默认不写',
252
- paramPlaceholder: '参数:默认使用',
253
- returnPlaceholder: '返回值:默认使用',
254
- throwsPlaceholder: '异常:默认使用',
255
- prePlaceholder: '前置条件:默认不使用',
256
- postPlaceholder: '后置条件:默认不使用',
257
- retvalPlaceholder: '返回值枚举:默认不使用',
258
- tparamPlaceholder: '模板参数:默认不使用',
259
- notePlaceholder: '补充:默认不使用',
260
- seePlaceholder: '参见:默认不使用',
256
+ authorPlaceholder: '作者署名',
257
+ datePlaceholder: '日期',
258
+ copyrightPlaceholder: '版权',
259
+ versionPlaceholder: '版本',
260
+ paramPlaceholder: '逐项说明参数',
261
+ returnPlaceholder: '说明返回值',
262
+ throwsPlaceholder: '列出可能抛出的异常',
263
+ prePlaceholder: '前置条件',
264
+ postPlaceholder: '后置条件',
265
+ retvalPlaceholder: '按枚举值说明返回值',
266
+ tparamPlaceholder: '说明模板参数',
267
+ notePlaceholder: '补充说明',
268
+ seePlaceholder: '相关接口',
269
+ styleSlashExample: '/// @brief 定义棋盘。',
270
+ styleBlockExample: '/** @brief 定义棋盘。 */',
261
271
  save: '保存',
262
272
  reset: '撤销修改',
263
273
  defaults: '恢复默认值',
@@ -275,28 +285,36 @@ const STRINGS = {
275
285
  en: {
276
286
  nav: 'C++ Workflow',
277
287
  heading: 'Doxygen comments',
288
+ lede: 'The file header, types, and declarations each decide which tags are written by default; the preview updates as you edit.',
278
289
  style: 'Comment symbol',
290
+ styleHint: 'Symbol for declarations; function bodies always use //.',
279
291
  bodyRow: 'Body',
280
- bodyPlaceholder: 'Function bodies: always //',
292
+ bodyPlaceholder: 'Always //',
281
293
  header: 'File header',
294
+ headerHint: 'Turn a tag on, then fill in its value; empty values are not written.',
282
295
  type: 'Types',
296
+ typeHint: 'Extra tags for type blocks and function templates.',
283
297
  declaration: 'Function declarations',
298
+ declarationHint: 'Function contracts plus pre- and postconditions.',
284
299
  preview: 'Preview',
300
+ previewHint: 'The code block updates as you toggle switches and type.',
285
301
  filePlaceholder: 'Follows the file name',
286
302
  briefPlaceholder: 'File summary: leave empty for each file',
287
- authorPlaceholder: 'Author: off by default',
288
- datePlaceholder: 'Date: off by default',
289
- copyrightPlaceholder: 'Copyright: off by default',
290
- versionPlaceholder: 'Version: off by default',
291
- paramPlaceholder: 'Parameters: on by default',
292
- returnPlaceholder: 'Return value: on by default',
293
- throwsPlaceholder: 'Exceptions: on by default',
294
- prePlaceholder: 'Preconditions: off by default',
295
- postPlaceholder: 'Postconditions: off by default',
296
- retvalPlaceholder: 'Named return values: off by default',
297
- tparamPlaceholder: 'Template parameters: off by default',
298
- notePlaceholder: 'Notes: off by default',
299
- seePlaceholder: 'See also: off by default',
303
+ authorPlaceholder: 'Author',
304
+ datePlaceholder: 'Date',
305
+ copyrightPlaceholder: 'Copyright',
306
+ versionPlaceholder: 'Version',
307
+ paramPlaceholder: 'Describe each parameter',
308
+ returnPlaceholder: 'Describe the return value',
309
+ throwsPlaceholder: 'List exceptions that may be thrown',
310
+ prePlaceholder: 'Precondition',
311
+ postPlaceholder: 'Postcondition',
312
+ retvalPlaceholder: 'Describe a named return value',
313
+ tparamPlaceholder: 'Describe template parameters',
314
+ notePlaceholder: 'Additional notes',
315
+ seePlaceholder: 'Related interfaces',
316
+ styleSlashExample: '/// @brief Defines the board.',
317
+ styleBlockExample: '/** @brief Defines the board. */',
300
318
  save: 'Save',
301
319
  reset: 'Discard changes',
302
320
  defaults: 'Restore defaults',
@@ -326,68 +344,65 @@ const STYLE_ID = 'dsh-cpp-workflow-settings'
326
344
 
327
345
  // 外观只用宿主变量与两档中性色;没有变量时回落到浅色主题的一组合法颜色。
328
346
  const CSS = `
329
- .dcw-page{--dcw-fg:var(--dsw-alias-label-primary,#1f2328);--dcw-muted:var(--dsw-alias-label-secondary,#69707d);--dcw-faint:var(--dsw-alias-label-tertiary,#8a919c);--dcw-border:var(--dsw-alias-border-l2,rgba(31,35,40,.14));--dcw-bg1:var(--dsw-alias-bg-layer-1,#fff);--dcw-bg2:var(--dsw-alias-bg-layer-2,rgba(31,35,40,.035));--dcw-accent:var(--dsw-alias-brand-primary,#2f6fed);--dcw-danger:var(--dsw-alias-state-error-primary,#c0392b);color:var(--dcw-fg);max-width:760px;padding:2px 0 12px;font-size:13px;line-height:1.6}
330
- .dcw-head{margin:0 0 14px}
331
- .dcw-eyebrow{margin:0 0 4px;color:var(--dcw-muted);font-size:11.5px;letter-spacing:.02em}
332
- .dcw-title{margin:0 0 6px;font-size:16px;font-weight:620;letter-spacing:.005em}
333
- .dcw-lede{margin:0;color:var(--dcw-muted);font-size:12.5px;line-height:1.65;max-width:64ch}
347
+ .dcw-page{--dcw-fg:var(--dsw-alias-label-primary,#1f2328);--dcw-muted:var(--dsw-alias-label-secondary,#69707d);--dcw-faint:var(--dsw-alias-label-tertiary,#8a919c);--dcw-border:var(--dsw-alias-border-l2,rgba(31,35,40,.14));--dcw-bg1:var(--dsw-alias-bg-layer-1,#fff);--dcw-bg2:var(--dsw-alias-bg-layer-2,rgba(31,35,40,.035));--dcw-accent:var(--dsw-alias-brand-primary,#2f6fed);--dcw-danger:var(--dsw-alias-state-error-primary,#c0392b);--dcw-success:var(--dsw-alias-state-success-primary,#1a7f37);color:var(--dcw-fg);max-width:760px;padding:2px 0 12px;font-size:13px;line-height:1.6}
348
+ .dcw-head{margin:0 0 16px}
349
+ .dcw-title{margin:0 0 6px;font-size:17px;font-weight:620;letter-spacing:.005em}
350
+ .dcw-lede{margin:0;color:var(--dcw-muted);font-size:12.5px;line-height:1.65;max-width:66ch}
334
351
  .dcw-note{display:flex;gap:8px;align-items:center;margin:12px 0 0;padding:9px 11px;border:1px solid var(--dcw-border);border-radius:8px;background:var(--dcw-bg2);color:var(--dcw-muted);font-size:12px;line-height:1.55}
335
352
  .dcw-note[data-tone="warn"]{color:var(--dcw-danger);border-color:color-mix(in srgb,var(--dcw-danger) 35%,transparent)}
336
353
  .dcw-note-dot{flex:none;width:6px;height:6px;border-radius:50%;background:currentColor;opacity:.55}
337
- .dcw-card{margin:14px 0 0;border:1px solid var(--dcw-border);border-radius:10px;background:var(--dcw-bg1);overflow:hidden}
338
- .dcw-card-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 10px;padding:11px 14px 9px;border-bottom:1px solid var(--dcw-border);background:var(--dcw-bg2)}
339
- .dcw-card-title{margin:0;font-size:12.5px;font-weight:620;letter-spacing:.01em}
340
- .dcw-card-hint{margin:0;color:var(--dcw-muted);font-size:11.5px;line-height:1.5}
341
- .dcw-row{display:grid;grid-template-columns:84px 40px minmax(0,1fr);align-items:center;gap:8px 12px;padding:8px 14px;border-top:1px solid var(--dcw-border)}
342
- .dcw-row:first-of-type{border-top:0}
343
- .dcw-row-label{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;color:var(--dcw-fg)}
344
- .dcw-row-style{grid-template-columns:84px minmax(0,1fr)}
345
- .dcw-row-style .dcw-segmented{grid-column:2}
346
- .dcw-lock{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border:1px dashed var(--dcw-border);border-radius:6px;background:var(--dcw-bg2);color:var(--dcw-muted)}
347
- .dcw-segmented{display:inline-flex;padding:2px;border:1px solid var(--dcw-border);border-radius:8px;background:var(--dcw-bg2);justify-self:start}
348
- .dcw-segment{height:26px;padding:0 12px;border:0;border-radius:6px;background:transparent;color:var(--dcw-muted);font:inherit;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;cursor:pointer}
349
- .dcw-segment:hover:enabled{color:var(--dcw-fg)}
350
- .dcw-segment[aria-pressed="true"]{background:var(--dcw-bg1);color:var(--dcw-fg);box-shadow:0 1px 2px rgba(0,0,0,.12)}
351
- .dcw-segment:focus-visible{outline:2px solid var(--dcw-accent);outline-offset:1px}
352
- .dcw-segment:disabled{opacity:.45;cursor:default}
353
- .dcw-switch{position:relative;display:inline-flex;align-items:center;flex:none;width:34px;height:20px;cursor:pointer}
354
+ .dcw-note-code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;opacity:.8;margin-left:6px}
355
+ .dcw-note-action{margin-left:auto;flex:none;padding:3px 10px;border:1px solid currentColor;border-radius:6px;background:transparent;color:inherit;font:inherit;font-size:11.5px;cursor:pointer;white-space:nowrap}
356
+ .dcw-note-action:hover{background:var(--dcw-bg1)}
357
+ .dcw-card{margin:14px 0 0;border:1px solid var(--dcw-border);border-radius:12px;background:var(--dcw-bg1);overflow:hidden}
358
+ .dcw-card-head{padding:12px 16px 10px;border-bottom:1px solid var(--dcw-border);background:var(--dcw-bg2)}
359
+ .dcw-card-title{margin:0;font-size:13px;font-weight:620;letter-spacing:.01em}
360
+ .dcw-card-hint{margin:2px 0 0;color:var(--dcw-muted);font-size:11.5px;line-height:1.5}
361
+ .dcw-card-body{padding:0}
362
+ .dcw-row{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:2px 14px;min-height:48px;padding:10px 16px;border-top:1px solid var(--dcw-border)}
363
+ .dcw-card-body>.dcw-row:first-child{border-top:0}
364
+ .dcw-row-main{display:flex;flex-direction:column;gap:2px;min-width:0}
365
+ .dcw-row-label{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12.5px;color:var(--dcw-fg)}
366
+ .dcw-row-hint{color:var(--dcw-muted);font-size:11.5px;line-height:1.5}
367
+ .dcw-row-value .dcw-input{grid-column:1/-1;margin-top:8px}
368
+ .dcw-fixed-value{color:var(--dcw-muted);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12.5px;white-space:nowrap}
369
+ .dcw-row-flag,.dcw-row-value{cursor:pointer}
370
+ .dcw-style-options{display:grid;grid-template-columns:1fr 1fr;gap:10px;padding:14px 16px}
371
+ .dcw-style-option{display:flex;flex-direction:column;align-items:flex-start;gap:6px;padding:12px 14px;border:1px solid var(--dcw-border);border-radius:10px;background:var(--dcw-bg1);color:var(--dcw-fg);font:inherit;text-align:left;cursor:pointer}
372
+ .dcw-style-option:hover:enabled{border-color:color-mix(in srgb,var(--dcw-fg) 30%,transparent)}
373
+ .dcw-style-option[aria-checked="true"]{border-color:var(--dcw-accent);background:color-mix(in srgb,var(--dcw-accent) 7%,var(--dcw-bg1));box-shadow:inset 0 0 0 1px var(--dcw-accent)}
374
+ .dcw-style-option:focus-visible{outline:2px solid var(--dcw-accent);outline-offset:2px}
375
+ .dcw-style-option:disabled{opacity:.5;cursor:default}
376
+ .dcw-style-symbol{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px;font-weight:620}
377
+ .dcw-style-example{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;line-height:1.55;color:var(--dcw-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
378
+ .dcw-switch{position:relative;display:inline-flex;align-items:center;flex:none;width:40px;height:22px;cursor:pointer}
354
379
  .dcw-switch input{position:absolute;inset:0;margin:0;opacity:0;cursor:pointer}
355
- .dcw-track{position:absolute;inset:0;border-radius:999px;background:color-mix(in srgb,var(--dcw-fg) 18%,transparent);transition:background .16s ease}
356
- .dcw-knob{position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.25);transition:transform .16s ease}
380
+ .dcw-track{position:absolute;inset:0;border-radius:999px;background:color-mix(in srgb,var(--dcw-fg) 20%,transparent);transition:background .16s ease}
381
+ .dcw-knob{position:absolute;top:2px;left:2px;width:18px;height:18px;border-radius:50%;background:var(--dsw-alias-bg-layer-1,#fff);box-shadow:0 1px 2px rgba(0,0,0,.28);transition:transform .16s ease}
357
382
  .dcw-switch input:checked + .dcw-track{background:var(--dcw-accent)}
358
- .dcw-switch input:checked + .dcw-track .dcw-knob{transform:translateX(14px)}
383
+ .dcw-switch input:checked + .dcw-track .dcw-knob{transform:translateX(18px)}
359
384
  .dcw-switch input:focus-visible + .dcw-track{outline:2px solid var(--dcw-accent);outline-offset:2px}
360
385
  .dcw-switch input:disabled + .dcw-track{opacity:.4}
361
386
  .dcw-switch input:disabled{cursor:default}
362
- .dcw-state{color:var(--dcw-faint);font-size:11.5px;min-width:28px}
363
- .dcw-input{flex:1 1 220px;min-width:0;height:28px;padding:0 9px;border:1px solid var(--dcw-border);border-radius:6px;background:transparent;color:var(--dcw-fg);font:inherit;font-size:12.5px}
387
+ .dcw-input{width:100%;min-width:0;height:36px;padding:0 11px;border:1px solid var(--dcw-border);border-radius:8px;background:var(--dcw-bg1);color:var(--dcw-fg);font:inherit;font-size:12.5px}
364
388
  .dcw-input::placeholder{color:var(--dcw-faint)}
365
- .dcw-input:focus{outline:none;border-color:var(--dcw-accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--dcw-accent) 18%,transparent)}
366
- .dcw-input:disabled{background:var(--dcw-bg2);color:var(--dcw-faint)}
367
- .dcw-flags{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:0 16px;padding:4px 14px 8px}
368
- .dcw-flag{display:flex;flex-direction:column;gap:3px;padding:7px 0}
369
- .dcw-flag .dcw-row-label{padding-top:0;display:flex;align-items:center;gap:8px;justify-content:space-between}
370
- .dcw-card>summary{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:11px 14px;cursor:pointer;list-style:none}
371
- .dcw-card details>summary::-webkit-details-marker{display:none}
372
- .dcw-card details>summary::marker{content:''}
373
- .dcw-card>summary:hover{background:var(--dcw-bg2)}
374
- .dcw-chevron{transition:transform .16s ease;color:var(--dcw-muted)}
375
- .dcw-card[open] .dcw-chevron{transform:rotate(90deg)}
376
- .dcw-preview{margin:0;padding:11px 14px 13px;border-top:1px solid var(--dcw-border);overflow-x:auto;background:var(--dcw-bg2);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;line-height:1.7;white-space:pre;color:var(--dcw-fg)}
377
- .dcw-actions{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-top:14px;padding:11px 2px 4px;border-top:1px solid var(--dcw-border)}
378
- .dcw-btn{height:30px;padding:0 13px;border:1px solid var(--dcw-border);border-radius:7px;background:var(--dcw-bg1);color:var(--dcw-fg);font:inherit;font-size:12.5px;cursor:pointer}
389
+ .dcw-input:focus{outline:none;border-color:var(--dcw-accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--dcw-accent) 16%,transparent)}
390
+ .dcw-input:disabled{opacity:.55}
391
+ .dcw-preview{margin:0;border-top:1px solid var(--dcw-border)}
392
+ .dcw-preview-head{display:flex;align-items:center;gap:7px;padding:8px 14px;border-bottom:1px solid var(--dcw-border);background:var(--dcw-bg2);color:var(--dcw-muted);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px}
393
+ .dcw-preview-dot{width:6px;height:6px;border-radius:50%;background:var(--dcw-faint)}
394
+ .dcw-preview-body{margin:0;padding:12px 14px 14px;overflow-x:auto;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;line-height:1.7;white-space:pre;color:var(--dcw-fg)}
395
+ .dcw-actions{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-top:16px;padding:12px 0 4px;border-top:1px solid var(--dcw-border)}
396
+ .dcw-btn{height:32px;padding:0 14px;border:1px solid var(--dcw-border);border-radius:8px;background:var(--dcw-bg1);color:var(--dcw-fg);font:inherit;font-size:12.5px;cursor:pointer}
379
397
  .dcw-btn:hover:enabled{border-color:color-mix(in srgb,var(--dcw-fg) 32%,transparent)}
380
398
  .dcw-btn:focus-visible{outline:2px solid var(--dcw-accent);outline-offset:1px}
381
399
  .dcw-btn:disabled{opacity:.45;cursor:default}
382
400
  .dcw-btn-primary{background:var(--dcw-accent);border-color:transparent;color:var(--dsw-alias-label-primary-foreground,#fff)}
383
401
  .dcw-btn-primary:hover:enabled{filter:brightness(1.06)}
384
- .dcw-status{flex:1 1 200px;margin:0;color:var(--dcw-muted);font-size:12px}
402
+ .dcw-status{flex:1 1 180px;margin:0;min-height:20px;color:var(--dcw-muted);font-size:12px}
385
403
  .dcw-status[data-tone="warn"]{color:var(--dcw-danger)}
386
- .dcw-status[data-tone="ok"]{color:var(--dsw-alias-state-success-primary,#1a7f37)}
387
- .dcw-note-code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;opacity:.8;margin-left:6px}
388
- .dcw-note-action{margin-left:auto;flex:none;padding:3px 10px;border:1px solid currentColor;border-radius:6px;background:transparent;color:inherit;font:inherit;font-size:11.5px;cursor:pointer;white-space:nowrap}
389
- .dcw-note-action:hover{background:var(--dcw-bg1)}
390
- @media (max-width:640px){.dcw-row,.dcw-row-style{grid-template-columns:1fr 40px}.dcw-row-label{grid-column:1/-1}.dcw-row-style .dcw-segmented{grid-column:1/-1}}
404
+ .dcw-status[data-tone="ok"]{color:var(--dcw-success)}
405
+ @media (max-width:520px){.dcw-style-options{grid-template-columns:1fr}.dcw-row{grid-template-columns:minmax(0,1fr) auto}.dcw-row-value .dcw-input{grid-column:1/-1}}
391
406
  `
392
407
 
393
408
  function ensureStyles() {
@@ -401,7 +416,7 @@ function ensureStyles() {
401
416
 
402
417
  function Switch(props) {
403
418
  return h(
404
- 'span',
419
+ 'label',
405
420
  { className: 'dcw-switch' },
406
421
  h('input', {
407
422
  type: 'checkbox',
@@ -414,131 +429,123 @@ function Switch(props) {
414
429
  )
415
430
  }
416
431
 
417
- function LockIcon() {
418
- return h(
419
- 'svg',
420
- { width: 12, height: 12, viewBox: '0 0 16 16', 'aria-hidden': 'true', focusable: 'false' },
421
- h('path', {
422
- fill: 'currentColor',
423
- d: 'M8 1a3 3 0 0 0-3 3v2H4.5A1.5 1.5 0 0 0 3 7.5v5A1.5 1.5 0 0 0 4.5 14h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 11.5 6H11V4a3 3 0 0 0-3-3Zm2 5H6V4a2 2 0 1 1 4 0v2Z',
424
- }),
425
- )
426
- }
427
-
428
- function Chevron() {
429
- return h(
430
- 'svg',
431
- { className: 'dcw-chevron', width: 12, height: 12, viewBox: '0 0 16 16', 'aria-hidden': 'true', focusable: 'false' },
432
- h('path', { fill: 'currentColor', d: 'M6 3.5 10.5 8 6 12.5 4.9 11.4 8.3 8 4.9 4.6Z' }),
433
- )
434
- }
435
-
436
- /** 五个区块统一用默认展开的 details,标题在摘要行。 */
432
+ /** 设置页的分组卡片:标题下一句说明,正文按行排控件。 */
437
433
  function Section(props) {
438
434
  return h(
439
- 'details',
440
- { className: 'dcw-card', open: true },
441
- h('summary', null, h('span', { className: 'dcw-card-title' }, props.title), h(Chevron)),
442
- props.children,
435
+ 'section',
436
+ { className: 'dcw-card' },
437
+ h(
438
+ 'div',
439
+ { className: 'dcw-card-head' },
440
+ h('h4', { className: 'dcw-card-title' }, props.title),
441
+ props.hint ? h('p', { className: 'dcw-card-hint' }, props.hint) : null,
442
+ ),
443
+ h('div', { className: 'dcw-card-body' }, props.children),
443
444
  )
444
445
  }
445
446
 
446
- /** 文件头可填写的一行:标签、开关、文本框。 */
447
+ /** 文件头可填写的一行:标题与开关一行,输入框整行在下;关闭时收起输入框。 */
447
448
  function ValueRow(props) {
448
449
  const entry = props.entry
449
450
  return h(
450
451
  'div',
451
- { className: 'dcw-row' },
452
- h('div', { className: 'dcw-row-label' }, TAG_LABEL[props.tag]),
452
+ {
453
+ className: 'dcw-row dcw-row-value',
454
+ onClick: (event) => {
455
+ if (props.disabled) return
456
+ if (event.target.closest('.dcw-switch, .dcw-input')) return
457
+ props.onChange({ ...entry, enabled: !entry.enabled })
458
+ },
459
+ },
460
+ h(
461
+ 'div',
462
+ { className: 'dcw-row-main' },
463
+ h('span', { className: 'dcw-row-label' }, TAG_LABEL[props.tag]),
464
+ ),
453
465
  h(Switch, {
454
466
  label: TAG_LABEL[props.tag],
455
467
  checked: entry.enabled,
456
468
  disabled: props.disabled,
457
469
  onChange: (enabled) => props.onChange({ ...entry, enabled }),
458
470
  }),
459
- h('input', {
460
- className: 'dcw-input',
461
- type: 'text',
462
- value: entry.value,
463
- placeholder: props.placeholder,
464
- disabled: props.disabled || !entry.enabled,
465
- 'aria-label': `${TAG_LABEL[props.tag]} 的值`,
466
- onChange: (event) => props.onChange({ ...entry, value: event.target.value }),
467
- }),
471
+ entry.enabled
472
+ ? h('input', {
473
+ className: 'dcw-input',
474
+ type: 'text',
475
+ value: entry.value,
476
+ placeholder: props.placeholder,
477
+ disabled: props.disabled,
478
+ 'aria-label': `${TAG_LABEL[props.tag]} 的值`,
479
+ onChange: (event) => props.onChange({ ...entry, value: event.target.value }),
480
+ })
481
+ : null,
468
482
  )
469
483
  }
470
484
 
471
- /** 只有开关的标签行;右侧框显示该标签的作用,不进入配置。 */
485
+ /** 只有开关的标签行;说明放在标题下面,不再放一个不可用的假输入框。 */
472
486
  function FlagRow(props) {
473
487
  return h(
474
488
  'div',
475
- { className: 'dcw-row' },
476
- h('div', { className: 'dcw-row-label' }, TAG_LABEL[props.tag]),
489
+ {
490
+ className: 'dcw-row dcw-row-flag',
491
+ onClick: (event) => {
492
+ if (props.disabled) return
493
+ if (event.target.closest('.dcw-switch, .dcw-input')) return
494
+ props.onChange(!props.checked)
495
+ },
496
+ },
497
+ h(
498
+ 'div',
499
+ { className: 'dcw-row-main' },
500
+ h('span', { className: 'dcw-row-label' }, TAG_LABEL[props.tag]),
501
+ props.hint ? h('span', { className: 'dcw-row-hint' }, props.hint) : null,
502
+ ),
477
503
  h(Switch, {
478
504
  label: TAG_LABEL[props.tag],
479
505
  checked: props.checked,
480
506
  disabled: props.disabled,
481
507
  onChange: props.onChange,
482
508
  }),
483
- h('input', {
484
- className: 'dcw-input',
485
- type: 'text',
486
- value: '',
487
- placeholder: props.placeholder,
488
- disabled: true,
489
- readOnly: true,
490
- tabIndex: -1,
491
- 'aria-label': props.placeholder,
492
- }),
493
509
  )
494
510
  }
495
511
 
496
- /** 值由文件或语法固定的行:中间锁,右侧框给出固定值。 */
512
+ /** 值由文件或语法固定的行:右侧写成普通文本,不放锁图标。 */
497
513
  function LockRow(props) {
498
514
  return h(
499
515
  'div',
500
- { className: 'dcw-row dcw-row-lock' },
501
- h('div', { className: 'dcw-row-label' }, props.label),
502
- h('span', { className: 'dcw-lock', title: props.placeholder }, h(LockIcon)),
503
- h('input', {
504
- className: 'dcw-input',
505
- type: 'text',
506
- value: '',
507
- placeholder: props.placeholder,
508
- disabled: true,
509
- readOnly: true,
510
- tabIndex: -1,
511
- 'aria-label': props.placeholder,
512
- }),
516
+ { className: 'dcw-row dcw-row-fixed' },
517
+ h(
518
+ 'div',
519
+ { className: 'dcw-row-main' },
520
+ h('span', { className: 'dcw-row-label' }, props.label),
521
+ ),
522
+ h('span', { className: 'dcw-fixed-value' }, props.value),
513
523
  )
514
524
  }
515
525
 
516
- /** 两种声明注释符号;按钮本身可用键盘切换。 */
526
+ /** 两种声明注释符号:两张等宽卡片,上面符号、下面示例。 */
517
527
  function StyleRow(props) {
518
528
  const options = [
519
- { value: 'slash', label: '///' },
520
- { value: 'block', label: '/** */' },
529
+ { value: 'slash', symbol: '///', example: props.slashExample },
530
+ { value: 'block', symbol: '/** */', example: props.blockExample },
521
531
  ]
522
532
  return h(
523
533
  'div',
524
- { className: 'dcw-row dcw-row-style' },
525
- h('div', { className: 'dcw-row-label' }, props.label),
526
- h(
527
- 'div',
528
- { className: 'dcw-segmented', role: 'group', 'aria-label': props.label },
529
- options.map((option) =>
530
- h(
531
- 'button',
532
- {
533
- key: option.value,
534
- type: 'button',
535
- className: 'dcw-segment',
536
- 'aria-pressed': props.value === option.value ? 'true' : 'false',
537
- disabled: props.disabled,
538
- onClick: () => props.onChange(option.value),
539
- },
540
- option.label,
541
- ),
534
+ { className: 'dcw-style-options', role: 'radiogroup', 'aria-label': props.label },
535
+ options.map((option) =>
536
+ h(
537
+ 'button',
538
+ {
539
+ key: option.value,
540
+ type: 'button',
541
+ role: 'radio',
542
+ 'aria-checked': props.value === option.value ? 'true' : 'false',
543
+ className: 'dcw-style-option',
544
+ disabled: props.disabled,
545
+ onClick: () => props.onChange(option.value),
546
+ },
547
+ h('span', { className: 'dcw-style-symbol' }, option.symbol),
548
+ h('span', { className: 'dcw-style-example' }, option.example),
542
549
  ),
543
550
  ),
544
551
  )
@@ -548,8 +555,13 @@ function Preview(props) {
548
555
  const s = props.strings
549
556
  return h(
550
557
  Section,
551
- { title: s.preview },
552
- h('pre', { className: 'dcw-preview' }, previewText(props.config, SAMPLE_FILE)),
558
+ { title: s.preview, hint: s.previewHint },
559
+ h(
560
+ 'div',
561
+ { className: 'dcw-preview' },
562
+ h('div', { className: 'dcw-preview-head' }, h('span', { className: 'dcw-preview-dot' }), SAMPLE_FILE),
563
+ h('pre', { className: 'dcw-preview-body' }, previewText(props.config, SAMPLE_FILE)),
564
+ ),
553
565
  )
554
566
  }
555
567
 
@@ -661,9 +673,11 @@ function ConfigPage(props) {
661
673
  return () => clearTimeout(timer)
662
674
  }, [form, attempt])
663
675
 
676
+ // 脏标记按与已保存值的差集算:把开关关了又开回原值时,页面不该再说“有未保存的改动”。
664
677
  const update = (patch) => {
665
- setDraft((current) => ({ ...current, ...patch }))
666
- setDirty(true)
678
+ const next = { ...draft, ...patch }
679
+ setDraft(next)
680
+ setDirty(diffOperations(next, base).length > 0)
667
681
  setMessage('')
668
682
  setTone('')
669
683
  }
@@ -678,8 +692,9 @@ function ConfigPage(props) {
678
692
 
679
693
  // 恢复默认值:只填进草稿,点“保存”后才写盘,避免误触直接覆盖已保存的配置。
680
694
  const restoreDefaults = () => {
681
- setDraft(normalizeConfig(DEFAULT_CONFIG))
682
- setDirty(true)
695
+ const next = normalizeConfig(DEFAULT_CONFIG)
696
+ setDraft(next)
697
+ setDirty(diffOperations(next, base).length > 0)
683
698
  setMessage(s.restored)
684
699
  setTone('')
685
700
  }
@@ -720,21 +735,26 @@ function ConfigPage(props) {
720
735
  const editable = Boolean(form)
721
736
  const disabled = !ready
722
737
 
723
- /** 一个标签一行:中间开关,右侧框给默认值或占位说明。 */
738
+ /** 一个标签一行:标题与说明,右侧开关。 */
724
739
  const flag = (tag, checked) =>
725
740
  h(FlagRow, {
726
741
  key: tag,
727
742
  tag,
728
743
  checked,
729
744
  disabled,
730
- placeholder: s[FLAG_PLACEHOLDER[tag]],
745
+ hint: s[FLAG_PLACEHOLDER[tag]],
731
746
  onChange: (value) => update({ [tag]: value }),
732
747
  })
733
748
 
734
749
  return h(
735
750
  'div',
736
751
  { className: 'dcw-page' },
737
- h('header', { className: 'dcw-head' }, h('h3', { className: 'dcw-title' }, s.heading)),
752
+ h(
753
+ 'header',
754
+ { className: 'dcw-head' },
755
+ h('h3', { className: 'dcw-title' }, s.heading),
756
+ h('p', { className: 'dcw-lede' }, s.lede),
757
+ ),
738
758
  missing
739
759
  ? h(
740
760
  'div',
@@ -759,19 +779,21 @@ function ConfigPage(props) {
759
779
  : null,
760
780
  h(
761
781
  Section,
762
- { title: s.style },
782
+ { title: s.style, hint: s.styleHint },
763
783
  h(StyleRow, {
764
784
  label: s.style,
765
785
  value: draft.commentStyle,
766
786
  disabled,
787
+ slashExample: s.styleSlashExample,
788
+ blockExample: s.styleBlockExample,
767
789
  onChange: (value) => update({ commentStyle: value }),
768
790
  }),
769
- h(LockRow, { label: s.bodyRow, placeholder: s.bodyPlaceholder }),
791
+ h(LockRow, { label: s.bodyRow, value: s.bodyPlaceholder }),
770
792
  ),
771
793
  h(
772
794
  Section,
773
- { title: s.header },
774
- h(LockRow, { label: TAG_LABEL.file, placeholder: s.filePlaceholder }),
795
+ { title: s.header, hint: s.headerHint },
796
+ h(LockRow, { label: TAG_LABEL.file, value: s.filePlaceholder }),
775
797
  ...HEADER_TAGS.map((tag) =>
776
798
  h(ValueRow, {
777
799
  key: tag,
@@ -783,10 +805,10 @@ function ConfigPage(props) {
783
805
  }),
784
806
  ),
785
807
  ),
786
- h(Section, { title: s.type }, ...TYPE_TAGS.map((tag) => flag(tag, draft[tag]))),
808
+ h(Section, { title: s.type, hint: s.typeHint }, ...TYPE_TAGS.map((tag) => flag(tag, draft[tag]))),
787
809
  h(
788
810
  Section,
789
- { title: s.declaration },
811
+ { title: s.declaration, hint: s.declarationHint },
790
812
  ...[...CONTRACT_TAGS, ...FUNCTION_EXTRA_TAGS].map((tag) => flag(tag, draft[tag])),
791
813
  ),
792
814
  h(Preview, { config: draft, strings: s }),
@@ -12,9 +12,9 @@
12
12
  export const HEADER_TAGS = ['brief', 'author', 'date', 'copyright', 'version'];
13
13
  /** 函数声明标签:开关决定是否要求写出。 */
14
14
  export const CONTRACT_TAGS = ['param', 'returns', 'throws'];
15
- /** 函数声明里默认关闭的补充标签。 */
15
+ /** 函数声明里出厂开启的补充标签。 */
16
16
  export const FUNCTION_EXTRA_TAGS = ['pre', 'post', 'retval'];
17
- /** 类型标签:默认关闭,函数模板共用同一组键。 */
17
+ /** 类型标签:出厂开启,函数模板共用同一组键。 */
18
18
  export const TYPE_TAGS = ['tparam', 'note', 'see'];
19
19
  /** 全部可选标签,快照与测试按它排序。 */
20
20
  export const EXTRA_TAGS = ['note', 'see', 'pre', 'post', 'tparam', 'retval'];
@@ -42,23 +42,25 @@ export const TAG_LABEL = {
42
42
  export const SAMPLE_BRIEF = '<一句话说明本文件>';
43
43
  /** 预览里的示例文件名:只是占位符,不对应任何工程的真实文件。 */
44
44
  export const SAMPLE_FILE = '<文件名>';
45
- /** 出厂配置:声明用 `///`,文件头只留 @file 与 @brief,契约标签全开,其余关闭。 */
45
+ /**
46
+ * 出厂配置:声明用 `///`,除 @file 与函数体 // 外全部开启;空值仍不写文件头。
47
+ */
46
48
  export const DEFAULT_CONFIG = {
47
49
  commentStyle: 'slash',
48
50
  brief: { enabled: true, value: '' },
49
- author: { enabled: false, value: '' },
50
- date: { enabled: false, value: '' },
51
- copyright: { enabled: false, value: '' },
52
- version: { enabled: false, value: '' },
51
+ author: { enabled: true, value: '' },
52
+ date: { enabled: true, value: '' },
53
+ copyright: { enabled: true, value: '' },
54
+ version: { enabled: true, value: '' },
53
55
  param: true,
54
56
  returns: true,
55
57
  throws: true,
56
- note: false,
57
- see: false,
58
- pre: false,
59
- post: false,
60
- tparam: false,
61
- retval: false,
58
+ note: true,
59
+ see: true,
60
+ pre: true,
61
+ post: true,
62
+ tparam: true,
63
+ retval: true,
62
64
  };
63
65
  /**
64
66
  * 宿主把 volatile 字段交给插件时给的是可读写单元(`.get()`),设置页快照给的是普通值;
package/lib/index.js CHANGED
@@ -27,10 +27,14 @@ import { normalizeConfig, snapshotText } from './comment-policy.js';
27
27
  export const name = 'dsh-cpp-workflow';
28
28
  export const inject = ['skills'];
29
29
  // 设置页保存的标签开关;字段即时生效,改配置不必重装插件。
30
- const HeaderTag = Schema.object({
31
- enabled: Schema.boolean().default(false).volatile(),
32
- value: Schema.string().default('').volatile(),
33
- });
30
+ // 每个文件头标签各建一个对象:四个标签共用一个 Schema 实例时,volatile 叶子会撞在同一条路径上,
31
+ // 保存 @author 会被宿主拒绝或写到别的标签。
32
+ function headerTag() {
33
+ return Schema.object({
34
+ enabled: Schema.boolean().default(true).volatile(),
35
+ value: Schema.string().default('').volatile(),
36
+ });
37
+ }
34
38
  export const Config = Schema.object({
35
39
  // 声明注释符号:`///` 或 Doxygen 块注释;缺省值由 comment-policy 兜底。
36
40
  commentStyle: Schema.string().default('slash').volatile(),
@@ -38,19 +42,19 @@ export const Config = Schema.object({
38
42
  enabled: Schema.boolean().default(true).volatile(),
39
43
  value: Schema.string().default('').volatile(),
40
44
  }),
41
- author: HeaderTag,
42
- date: HeaderTag,
43
- copyright: HeaderTag,
44
- version: HeaderTag,
45
+ author: headerTag(),
46
+ date: headerTag(),
47
+ copyright: headerTag(),
48
+ version: headerTag(),
45
49
  param: Schema.boolean().default(true).volatile(),
46
50
  returns: Schema.boolean().default(true).volatile(),
47
51
  throws: Schema.boolean().default(true).volatile(),
48
- note: Schema.boolean().default(false).volatile(),
49
- see: Schema.boolean().default(false).volatile(),
50
- pre: Schema.boolean().default(false).volatile(),
51
- post: Schema.boolean().default(false).volatile(),
52
- tparam: Schema.boolean().default(false).volatile(),
53
- retval: Schema.boolean().default(false).volatile(),
52
+ note: Schema.boolean().default(true).volatile(),
53
+ see: Schema.boolean().default(true).volatile(),
54
+ pre: Schema.boolean().default(true).volatile(),
55
+ post: Schema.boolean().default(true).volatile(),
56
+ tparam: Schema.boolean().default(true).volatile(),
57
+ retval: Schema.boolean().default(true).volatile(),
54
58
  });
55
59
  const PROVIDER = 'dsh-cpp-workflow';
56
60
  const RANK = 550;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chengzhao-dev/dsh-cpp-workflow",
3
- "version": "0.6.8",
3
+ "version": "0.6.10",
4
4
  "description": "写 C++ 源码、配 CMake、用 Clang 构建;头文件用 /// Doxygen 文档注释,函数体用 // 分段说明,文件头标签可在设置里改。",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -32,7 +32,7 @@ class Board {
32
32
  - 不写尚未发生的用法、方案对比与取舍论证、广告句。
33
33
  - 纯数据结构不写「只保存…,不拥有…,不含…」这类否定式边界;这些由成员类型和所有权表达。
34
34
  - 领域尺寸不写进类型注释:格子数由 `cells_` 的声明表达,棋盘边长由 `kBoardRows`、`kBoardCols` 表达。
35
- - 枚举与只承载数据的结构体:第一句是名词短语,写这个取值表示什么;需要条件时用 `///<` 补一句可核实的条件。
35
+ - 枚举与只承载数据的结构体:第一句是名词短语,写这个取值表示什么;行尾 `///<` 的两种句式见「枚举项与 `struct` 短字段:两种句式」。
36
36
 
37
37
  ### 构造函数与析构函数:先写标签,再写职责
38
38
 
@@ -62,12 +62,21 @@ GameResult result_ = GameResult::kInProgress;
62
62
 
63
63
  `board_` 这类名字已经说明存放对象的成员不写「对局棋盘」;`cells_` 不写「存储…」这种把声明读一遍的句子,但上一行要先说它是棋盘格子数组,再补轴向、哨兵和改写次序。`clang-format` 不会把行尾注释搬到上一行,位置由本规则和检查器保证。
64
64
 
65
+ ### 枚举项与 `struct` 短字段:两种句式
66
+
67
+ 枚举项与只承载数据的 `struct` 短字段用行尾 `///<`,同一枚举各行的 `///<` 对齐。只写两种句式:
68
+
69
+ - 语义本身已经完整:`///< 空格子。`
70
+ - 语义之外还要补一条能核实的条件或结果:`///< 非法参数:传入空棋盘格子类型。`
71
+
72
+ 冒号用全角 `:`;冒号前写类别,冒号后写说明,两侧都不能为空。禁止空冒号(`///< 落子成功:。`)、禁止用逗号把类别和说明连起来(`///< 传入空棋盘格子类型,非法参数。`)、禁止把说明写在类别前面。同一枚举要么每一项都写 `///<`,要么都不写;都写时两种句式可以混用。
73
+
65
74
  ### 函数:两段式契约
66
75
 
67
76
  有参数或有返回值的函数写两段:第一段写结果、失败方式或检查项;第二段按签名把标签写齐。
68
77
 
69
78
  1. 第一段:普通函数写正常返回什么、失败时返回什么或抛什么;布尔查询与 `operator==` 写「检查…是否…」。
70
- 2. 第二段:每个参数一条 `@param`;有返回值默认写 `@return`,成立条件已由函数名与返回类型说清时可省略;会抛才写 `@throws`;`@pre`、`@post`、`@retval`、`@tparam`、`@note`、`@see` 默认不写,确有需要时再打开。
79
+ 2. 第二段:每个参数一条 `@param`;有返回值默认写 `@return`,成立条件已由函数名与返回类型说清时可省略;会抛才写 `@throws`;`@pre`、`@post`、`@retval`、`@tparam`、`@note`、`@see` 出厂开关打开;只在确有需要时写,不需要可在设置里关掉。
71
80
 
72
81
  ```cpp
73
82
  /// 检查坐标能否落子,不修改棋盘。
@@ -133,7 +142,7 @@ GameResult result_ = GameResult::kInProgress;
133
142
 
134
143
  ### 文件头
135
144
 
136
- 每个手写源文件开头写一段 doxygen 文件头:第一行写 `@file <文件名>`,第二行写 `@brief` 一句话说明这个文件定义或实现什么,用一行空注释隔开后,只写本文件自己的组成部分或边界。`@file` 的参数必须与文件名逐字相同,检查器会核对。`@brief` 默认写出;`@author`、`@date`、`@copyright`、`@version` 默认不写,只有插件配置里打开并填了非空值才写出,作者、年份和变更史不写进源码。删掉把读者指去另一个头文件、另一个目录或另一层的句子,也删掉「这一层不负责什么」。不写作者、版权年份、变更日志和外部资料路径。
145
+ 每个手写源文件开头写一段 doxygen 文件头:第一行写 `@file <文件名>`,第二行写 `@brief` 一句话说明这个文件定义或实现什么,用一行空注释隔开后,只写本文件自己的组成部分或边界。`@file` 的参数必须与文件名逐字相同,检查器会核对。`@brief` 始终写出;`@author`、`@date`、`@copyright`、`@version` 的开关出厂打开,只有填了非空值才写出,空值整条省略;作者、年份和变更史按团队约定填写,不填就不出现在源码里。删掉把读者指去另一个头文件、另一个目录或另一层的句子,也删掉「这一层不负责什么」。不写作者、版权年份、变更日志和外部资料路径。
137
146
 
138
147
  ```cpp
139
148
  /// @file board.h
@@ -41,6 +41,14 @@ class TaskTable {
41
41
  - 用户声明的特殊成员函数集中放第 4 组;`= default` 也写出来,读者才知道是故意默认。
42
42
  - 不要把数据成员插在函数之间;数据成员永远在最后一组。
43
43
 
44
+ ### 其他函数的组内次序
45
+
46
+ Google、LLVM、OpenCV 都没有规定这一组内部的次序;本工作流补上:
47
+
48
+ - 谓词 `Is` / `Has` / `Can`,然后 `Get`,然后普通函数按使用先后,最后是 `friend` 函数。
49
+ - `friend` 不放在这一组开头,也不单放在类的最前面,不强制放进 `private`。
50
+ - `friend` 类型仍跟其他类型放在一起,和 `friend` 函数不是同一组。
51
+
44
52
  ## 命名空间作用域的头文件
45
53
 
46
54
  文件槽位与守卫名以 `references/file-template.md` 为准;本节只看命名空间作用域内的排列顺序。
@@ -54,7 +54,7 @@
54
54
  - 默认用花括号初始化:`int count{};`、`std::vector<int> values{1, 2, 3};`。只有需要对比、`auto` 推导或赋值语义时用 `=`;`name = {{` 只用于多行聚合常量表,单个对象和空初始化仍写 `count{}`;不要用 `()` 初始化普通对象。
55
55
  - 类声明顺序:public → protected → private;类型与别名 → 静态常量 → 工厂 → 构造/赋值/析构 → 其他函数 → 数据成员。
56
56
  - 用户声明的拷贝/移动/析构函数在头文件声明、在 `.cpp` 定义;只有零参数且默认实现足够时才写 `= default`。
57
- - 查询成员函数标尾部 `const`;返回结果对象的 API 加 `[[nodiscard]]`,不要给所有非 `void` 函数机械添加。
57
+ - 查询成员函数标尾部 `const`;返回结果对象的 API 加 `[[nodiscard]]`,不要给所有非 `void` 函数机械添加。标注位置与 `friend` 的例外见 `references/types.md`「`[[nodiscard]]`」。
58
58
  - 所有权优先 `std::unique_ptr`;没有共享所有权的理由时不用 `std::shared_ptr`。禁止裸 `new` / `delete`。
59
59
  - 禁止 C 风格强制转换;用 `static_cast`、`const_cast`、`reinterpret_cast`、`std::bit_cast`,并让 `clang-tidy` 的 `google-readability-casting` 兜底。
60
60
  - `.cpp` 里不需要外部引用的 `constexpr` 直接放具名命名空间;仅一个函数使用时才用函数内 `static constexpr`;函数和类型仍放匿名命名空间。
@@ -77,7 +77,7 @@
77
77
  - 混合优先级算术加显式括号,消除 `readability-math-missing-parentheses`。
78
78
  - 内建整数默认用 `int`;需要确定宽度时用 `<cstdint>` 的 `std::int32_t`、`std::int64_t` 等(可省略 `std::` 前缀),可能到 2^31 的值用 64 位;`std::size_t` / `std::ptrdiff_t` 只用于大小和指针差。不要用无符号类型表达「这个值不会为负」。
79
79
  - 封闭状态码用 `enum class`;底层类型默认 `int`,只有确实需要缩小存储时才写固定底层类型并说明理由。枚举值总是带作用域:`OrderResult::kSuccess`。
80
- - 枚举的尾随注释要么全写要么全不写;语义结果写在最前,`///<` 对齐。
80
+ - 枚举的尾随注释要么全写要么全不写;两种句式与禁止项见 `references/comments.md`「枚举项与 `struct` 短字段:两种句式」。
81
81
  - 浮点不用 `==` 比较计算结果;按量纲给容差,金额用最小货币单位的整数。
82
82
  - 数值宽度、符号、枚举、强类型、`const` 成员和下标访问的完整规则见 `references/types.md`;初始化、RAII、移动和模板见 `references/language.md`。
83
83
 
@@ -18,6 +18,15 @@
18
18
  - 同一底层类型承载不同含义(米/秒、行/列)且容易混用时,用轻量强类型包装。
19
19
  - 不为每个名词造类;只有出现行为、不变量或独立变化原因时才升级为 `class`。
20
20
 
21
+ ### 取值的边界
22
+
23
+ - 封闭取值一次列全。格子的取值是空、X、O;不要把 X 和 O 做成不含空的棋子,再用空值表示空格。
24
+ - 位置是行和列,不是格子的另一种取值。胜负、轮到谁、已下过的序列属于棋盘或对局,不要塞进位置类型。
25
+ - 有不变量的格子用公开构造的类,默认是空,X 或 O 至多写入一次。私有构造函数只留给必须经过工厂才能建立的对象,不用来表达三种取值,也不用来藏起行和列。
26
+ - 一次写入能区分的失败都放进结果枚举。传入空取值是结果项,不抛异常。
27
+ - 只由某一个类型产生的结果枚举嵌在该类型里:格子的写入结果和对局的写入结果取值不同,不合成命名空间里的一个 `PlaceResult`。
28
+ - 尺寸是某个类型的事实时,写成该类型的 `static constexpr`,例如 `Board::kRowCount`;不要再造一个只做 `static_cast<std::size_t>` 的别名常量。
29
+
21
30
  ## 封闭状态与结果
22
31
 
23
32
  - 状态用连续取值的 `enum class`:`kPending`、`kActive`、`kDone`。
@@ -62,14 +62,16 @@ std::array<TaskState, kItemCount> items_{};
62
62
  - 枚举值只在类型作用域内可见:`TaskState::kPending`,不写 `kPending`。
63
63
  - 与整数之间不发生隐式转换;需要编号时写 `static_cast<int>(state)`,只用于输出或既有接口。
64
64
  - 同类型值可以比较和赋值;不能参与算术。
65
- - 需要固定布局时写底层类型:`enum class TaskState : std::uint8_t { kPending, kActive, kDone };`
65
+ - 需要固定布局时写底层类型。编号要在头文件里直接读到,就逐个写出来,不靠第一项默认为 0:`enum class TaskState : std::uint8_t { kPending = 0, kActive = 1, kDone = 2 };`
66
66
 
67
67
  约定:
68
68
 
69
69
  - 新代码用 `enum class`;unscoped `enum` 只用于兼容 C 接口,不作为默认写法。
70
70
  - 封闭状态码默认写 `: std::uint8_t`;普通枚举用默认底层类型。
71
- - 枚举值的行尾注释要么全写要么全不写;语义结果写在最前,`///<` 对齐。
71
+ - 枚举值的行尾注释要么全写要么全不写;两种句式与禁止项见 `references/comments.md`「枚举项与 `struct` 短字段:两种句式」。
72
72
  - 需要“编号”与“名字”两套表示时,写显式映射函数,例如 `TaskStateToName` / `TaskStateFromName`,不要在枚举和整数之间来回隐式转换。
73
+ - 编号按“失败优先”排:空状态或前置条件失败为 0,业务失败随后,成功放在最后。读取端因此可以先判 0,再决定是否继续。
74
+ - 编号只用于输出和既有接口;要从编号找回名字,仍走显式映射函数,不拿编号做算术。
73
75
 
74
76
  ## 强类型包装
75
77
 
@@ -111,6 +113,8 @@ class TaskBoard {
111
113
 
112
114
  - 只给“结果携带信息、忽略会出错”的函数:查询型 `const` 成员与返回结果对象的 API。
113
115
  - 不给所有非 `void` 函数机械添加;标注的价值来自稀缺。
116
+ - 写法位置:`[[nodiscard]]` 在返回类型前面,然后才是 `static` 或 `constexpr`;参数列表之后才是 `const`、`override`、`noexcept`。
117
+ - 类内 `friend` 不加 `[[nodiscard]]`:Clang 拒绝 `[[nodiscard]] friend`、`friend [[nodiscard]]` 和参数列表后的 `[[nodiscard]]`。不要再猜下一种写法。
114
118
  - 有意丢弃时写 `static_cast<void>(result)`,不要为此全局关闭警告。
115
119
  - 测试里验证“必须处理结果”时,同时验证忽略能被编译器发现。
116
120
 
@@ -2,7 +2,7 @@
2
2
  // 只读注释审计:按 references/comments.md 的硬规则检查 C/C++ 源文件。
3
3
  //
4
4
  // 检查项:Doxygen 文件头、注释行宽度、标签、注释与声明的贴合、
5
- // 类型注释存在性、成员功能清单、设计论证(方案对比)、否定式边界、领域尺寸、谓词首句、@param 完整性、枚举成员注释一致性、数据成员主注释位置。
5
+ // 类型注释存在性、成员功能清单、设计论证(方案对比)、否定式边界、领域尺寸、谓词首句、@param 完整性、枚举成员注释一致性、枚举行尾句式、数据成员主注释位置。
6
6
  // 不重写任何文件。
7
7
  //
8
8
  // 用法:
@@ -482,6 +482,22 @@ export function checkText(text, options = {}) {
482
482
  index = body
483
483
  }
484
484
 
485
+ for (let index = 0; index < lines.length; index += 1) {
486
+ const marker = lines[index].indexOf('///<')
487
+ if (marker === -1) continue
488
+ const content = lines[index].slice(marker + 4).trim().replace(/。$/, '').trim()
489
+ const colon = content.indexOf(':')
490
+ if (colon !== -1) {
491
+ if (content.slice(colon + 1).trim() === '') {
492
+ add(index + 1, 'enum-trailing-empty', '行尾注释冒号后为空:写「类别:说明」,冒号两侧都不能为空')
493
+ }
494
+ continue
495
+ }
496
+ if (/[,,]/.test(content)) {
497
+ add(index + 1, 'enum-trailing-comma', '行尾注释用逗号连接类别与说明:改成「类别:说明」,类别在前')
498
+ }
499
+ }
500
+
485
501
  for (let index = 0; index < lines.length; index += 1) {
486
502
  const trimmed = lines[index].trim()
487
503
  if (trimmed.startsWith('//') || trimmed.startsWith('///') || !/\bnoexcept\b/.test(trimmed)) continue
@@ -25,7 +25,7 @@
25
25
 
26
26
  **异常声明**:构造函数、`Get` 类查询、解析函数最容易抛;在声明注释里写「当……时抛出 `std::out_of_range`」这样的具体类型,不要只写「可能抛异常」,也不要给每个参数错误都新建类型。类型选择见 `references/exceptions.md`。
27
27
 
28
- **注释**:声明注释用 `///` 写中文散文,只写调用方需要知道的;实现细节留给 `.cpp`。`@param` 每个参数都写,`@return` 每个非 `void` 函数都写,`@throws` 会抛才写;构造、析构与 `void` 无参数函数不写空标签。标识符和标签保持英文。文件头默认写 `@file` 与 `@brief` 两行,`@file` 必须与文件名相同;作者、日期、版权、版本默认不写。标签开关在插件设置页,技能正文末尾附带当前配置的快照。写完运行 `node "<skill-dir>/scripts/checking-comments.mjs" --root <项目根>`;关闭 `@brief` 时加 `--no-brief`,配置里打开了隐私标签并填了值时加 `--allow-tag <标签>`。文件头、标签、参数完整性、成员清单与设计论证由脚本兜底。文件头与 TODO 的格式见 `references/comments.md`。
28
+ **注释**:声明注释用 `///` 写中文散文,只写调用方需要知道的;实现细节留给 `.cpp`。`@param` 每个参数都写,`@return` 每个非 `void` 函数都写,`@throws` 会抛才写;构造、析构与 `void` 无参数函数不写空标签。标识符和标签保持英文。文件头默认写 `@file` 与 `@brief` 两行,`@file` 必须与文件名相同;作者、日期、版权、版本开关出厂打开,填了非空值才写出。标签开关在插件设置页,技能正文末尾附带当前配置的快照。写完运行 `node "<skill-dir>/scripts/checking-comments.mjs" --root <项目根>`;关闭 `@brief` 时加 `--no-brief`,配置里打开了隐私标签并填了值时加 `--allow-tag <标签>`。文件头、标签、参数完整性、成员清单与设计论证由脚本兜底。文件头与 TODO 的格式见 `references/comments.md`。
29
29
 
30
30
  ## 正反例指针
31
31