@chengzhao-dev/dsh-cpp-workflow 0.6.5 → 0.6.6

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.5
22
+ dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.6
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.5
28
+ dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.6
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`, `@return`, `@throws` are required by default. 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, `@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.
36
36
 
37
37
  ## Workspace gate
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.5
22
+ dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.6
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.5
28
+ dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.6
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`。预览随开关即时更新,保存后下一次读取技能即生效,已有源码不会被改写。设置导航不再重复放一份。
35
+ 插件页的「C++ 工程流程」是唯一入口,只管文件头标签:`@file` 固定为文件名,`@brief` 默认写出,`@author`、`@date`、`@copyright`、`@version` 默认关闭且只在填了非空值时才写出;有参数就要求 `@param`,有返回值默认要求 `@return`(成立条件自明时可省),会抛才要求 `@throws`。预览随开关即时更新,保存后下一次读取技能即生效,已有源码不会被改写。设置导航不再重复放一份。
36
36
 
37
37
  ## 工作区门禁
38
38
 
package/lib/client.js CHANGED
@@ -79,11 +79,25 @@ const DEFAULT_CONFIG = {
79
79
  tparam: false,
80
80
  retval: false,
81
81
  };
82
+ /**
83
+ * 宿主把 volatile 字段交给插件时给的是可读写单元(`.get()`),设置页快照给的是普通值;
84
+ * 两种形状都要按同一个值读取。
85
+ */
86
+ function unwrap(value) {
87
+ if (value !== null &&
88
+ typeof value === 'object' &&
89
+ typeof value.get === 'function') {
90
+ return value.get();
91
+ }
92
+ return value;
93
+ }
82
94
  function text(value) {
83
- return typeof value === 'string' ? value.trim() : '';
95
+ const plain = unwrap(value);
96
+ return typeof plain === 'string' ? plain.trim() : '';
84
97
  }
85
98
  function flag(value, fallback) {
86
- return typeof value === 'boolean' ? value : fallback;
99
+ const plain = unwrap(value);
100
+ return typeof plain === 'boolean' ? plain : fallback;
87
101
  }
88
102
  /**
89
103
  * 把宿主给的值补齐成完整配置;缺字段、旧版本或手工改坏的配置都回到出厂值。
@@ -468,36 +482,69 @@ function Preview(props) {
468
482
  function diffOperations(draft, base) {
469
483
  const operations = []
470
484
  for (const key of Object.keys(DEFAULT_CONFIG)) {
471
- const next = JSON.stringify(draft[key])
472
- const previous = JSON.stringify(base[key])
473
- if (next !== previous) operations.push({ op: 'set', path: [key], value: draft[key] })
485
+ const next = draft[key]
486
+ const previous = base[key]
487
+ if (next !== null && typeof next === 'object') {
488
+ // 文件头字段整体不是 volatile,宿主只接受叶子路径(brief.enabled / brief.value)。
489
+ const before = previous !== null && typeof previous === 'object' ? previous : {}
490
+ for (const field of Object.keys(next)) {
491
+ if (JSON.stringify(next[field]) !== JSON.stringify(before[field])) {
492
+ operations.push({ op: 'set', path: [key, field], value: next[field] })
493
+ }
494
+ }
495
+ continue
496
+ }
497
+ if (JSON.stringify(next) !== JSON.stringify(previous)) {
498
+ operations.push({ op: 'set', path: [key], value: next })
499
+ }
474
500
  }
475
501
  return operations
476
502
  }
477
503
 
504
+ function snapshotOf(form) {
505
+ if (!form) return { status: 'missing', writable: false }
506
+ if (typeof form.getSnapshot === 'function') return form.getSnapshot()
507
+ return form.state || { status: 'missing', writable: false }
508
+ }
509
+
510
+ // 宿主的 ConfigFormController 是可订阅的外部存储;旧调用方仍可能直接给 form.state。
511
+ function useFormSnapshot(form) {
512
+ const [snapshot, setSnapshot] = React.useState(() => snapshotOf(form))
513
+ React.useEffect(() => {
514
+ if (!form || typeof form.subscribe !== 'function' || typeof form.getSnapshot !== 'function') {
515
+ setSnapshot(snapshotOf(form))
516
+ return undefined
517
+ }
518
+ setSnapshot(form.getSnapshot())
519
+ return form.subscribe(() => setSnapshot(form.getSnapshot()))
520
+ }, [form])
521
+ return snapshot
522
+ }
523
+
478
524
  function ConfigPage(props) {
479
525
  const s = t()
480
526
  ensureStyles()
481
527
  const form = props.form
482
- const initial = React.useMemo(() => normalizeConfig(form && form.state ? form.state.value : undefined), [])
528
+ const state = useFormSnapshot(form)
529
+ const initial = React.useMemo(() => normalizeConfig(state.value), [])
483
530
  const [draft, setDraft] = React.useState(initial)
484
531
  const [base, setBase] = React.useState(initial)
485
532
  const [dirty, setDirty] = React.useState(false)
486
533
  const [busy, setBusy] = React.useState(false)
487
534
  const [message, setMessage] = React.useState('')
488
535
  const [tone, setTone] = React.useState('')
489
- const revision = form && form.state ? form.state.revision : undefined
536
+ const revision = state.revision
490
537
  // 快照只有 status / writable / revision;旧版本多出来的 mode 字段已不存在,
491
538
  // 再按它判断会把所有可写表单误判成只读。
492
- const ready = Boolean(form && form.state && form.state.status === 'ready' && form.state.writable)
493
- const status = form && form.state ? form.state.status : 'missing'
539
+ const ready = Boolean(state.status === 'ready' && state.writable)
540
+ const status = state.status
494
541
 
495
542
  React.useEffect(() => {
496
- if (dirty || !form || !form.state || form.state.status !== 'ready') return
497
- const next = normalizeConfig(form.state.value)
543
+ if (dirty || state.status !== 'ready') return
544
+ const next = normalizeConfig(state.value)
498
545
  setBase(next)
499
546
  setDraft(next)
500
- }, [revision, status, dirty, form])
547
+ }, [revision, status, dirty, form, state.value])
501
548
 
502
549
  const update = (patch) => {
503
550
  setDraft((current) => ({ ...current, ...patch }))
@@ -666,26 +713,36 @@ function ConfigPage(props) {
666
713
  )
667
714
  }
668
715
 
669
- function page(ownerProps) {
670
- return h(ConfigPage, ownerProps || {})
716
+ // 组合包 cordis.patch.yml 插入的 profile 条目 id;设置服务按它定位表单。
717
+ const ENTRY_ID = 'cpp-workflow'
718
+
719
+ function page(ownerProps, form) {
720
+ return h(ConfigPage, { ...(ownerProps || {}), form })
671
721
  }
672
722
 
673
723
  function apply(ctx) {
674
- ctx.slots.inject('plugins.bundle.config', () =>
675
- ctx.slots.register(
676
- {
677
- name: 'plugins.bundle.config',
678
- key: PACKAGE_NAME,
679
- label: () => t().nav,
680
- },
681
- (ownerProps) => (ownerProps && ownerProps.view === 'summary' ? null : page(ownerProps)),
682
- ),
683
- )
724
+ const form = ctx.configForms ? ctx.configForms.get(ENTRY_ID) : undefined
725
+ const mount = () =>
726
+ ctx.slots.inject('plugins.bundle.config', () =>
727
+ ctx.slots.register(
728
+ {
729
+ name: 'plugins.bundle.config',
730
+ key: PACKAGE_NAME,
731
+ label: () => t().nav,
732
+ },
733
+ (ownerProps) => (ownerProps && ownerProps.view === 'summary' ? null : page(ownerProps, form)),
734
+ ),
735
+ )
736
+ if (ctx.configForms && typeof ctx.configForms.whileServed === 'function') {
737
+ ctx.effect(() => ctx.configForms.whileServed([ENTRY_ID], mount), 'dsh-cpp-workflow: 配置挂点')
738
+ } else {
739
+ mount()
740
+ }
684
741
  }
685
742
 
686
743
  module.exports = {
687
744
  name: PACKAGE_NAME,
688
- inject: ['slots'],
745
+ inject: ['slots', 'configForms'],
689
746
  apply,
690
747
  ConfigPage,
691
748
  diffOperations,
@@ -72,11 +72,25 @@ export const DEFAULT_CONFIG = {
72
72
  tparam: false,
73
73
  retval: false,
74
74
  };
75
+ /**
76
+ * 宿主把 volatile 字段交给插件时给的是可读写单元(`.get()`),设置页快照给的是普通值;
77
+ * 两种形状都要按同一个值读取。
78
+ */
79
+ function unwrap(value) {
80
+ if (value !== null &&
81
+ typeof value === 'object' &&
82
+ typeof value.get === 'function') {
83
+ return value.get();
84
+ }
85
+ return value;
86
+ }
75
87
  function text(value) {
76
- return typeof value === 'string' ? value.trim() : '';
88
+ const plain = unwrap(value);
89
+ return typeof plain === 'string' ? plain.trim() : '';
77
90
  }
78
91
  function flag(value, fallback) {
79
- return typeof value === 'boolean' ? value : fallback;
92
+ const plain = unwrap(value);
93
+ return typeof plain === 'boolean' ? plain : fallback;
80
94
  }
81
95
  /**
82
96
  * 把宿主给的值补齐成完整配置;缺字段、旧版本或手工改坏的配置都回到出厂值。
package/lib/index.js CHANGED
@@ -223,11 +223,11 @@ async function getSkill(candidate, options = {}, commentConfig = undefined) {
223
223
  };
224
224
  }
225
225
  export function apply(ctx, config = undefined) {
226
- // 配置决定注释标签的默认集合;快照随每次 get() 读出,改配置立即生效。
227
- const resolved = normalizeConfig(config);
226
+ // 配置对象由宿主在 volatile 保存时原地更新,所以每次 get() 都重新读取,
227
+ // 不能在这里缓存成一份快照,否则设置页改了标签开关,技能正文仍是旧值。
228
228
  ctx.skills.registerProvider(() => ({
229
229
  name: PROVIDER,
230
230
  list: listSkills,
231
- get: (candidate, options) => getSkill(candidate, options, resolved),
231
+ get: (candidate, options) => getSkill(candidate, options, normalizeConfig(config)),
232
232
  }));
233
233
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chengzhao-dev/dsh-cpp-workflow",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
4
4
  "description": "写 C++ 源码、配 CMake、用 Clang 构建;头文件用 /// Doxygen 文档注释,函数体用 // 分段说明,文件头标签可在设置里改。 · Write C++ sources, configure CMake, and build with Clang; /// Doxygen on declarations, // section notes in bodies, and configurable file-header tags.",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -32,7 +32,9 @@
32
32
  "client": {
33
33
  "platform": "web",
34
34
  "inject": [
35
- "@deepseek-ai/dsh-client-ui-slots"
35
+ "@deepseek-ai/dsh-client-ui-slots",
36
+ "@deepseek-ai/dsh-client-ui-settings",
37
+ "@deepseek-ai/dsh-client-ui-plugin-manager"
36
38
  ]
37
39
  },
38
40
  "catalog": {
@@ -67,7 +67,7 @@ GameResult result_ = GameResult::kInProgress;
67
67
  有参数或有返回值的函数写两段:第一段写结果、失败方式或检查项;第二段按签名把标签写齐。
68
68
 
69
69
  1. 第一段:普通函数写正常返回什么、失败时返回什么或抛什么;布尔查询与 `operator==` 写「检查…是否…」。
70
- 2. 第二段:每个参数一条 `@param`;有返回值就写 `@return`;会抛才写 `@throws`。
70
+ 2. 第二段:每个参数一条 `@param`;有返回值默认写 `@return`,成立条件已由函数名与返回类型说清时可省略;会抛才写 `@throws`。
71
71
 
72
72
  ```cpp
73
73
  /// 检查坐标能否落子,不修改棋盘。
@@ -78,9 +78,9 @@ GameResult result_ = GameResult::kInProgress;
78
78
  [[nodiscard]] PlaceCellResult CheckPlaceable(CellPosition position, CellType cell_type) const;
79
79
  ```
80
80
 
81
- - 布尔查询与 `operator==`:第一句写「检查…是否…」;`@return` 只写「什么情况下为 true」,再补一句「不检查什么」。条件简单也要写,「名字已经说明」不构成省略理由。
81
+ - 布尔查询与 `operator==`:第一句写「检查…是否…」;`@return` 只写「什么情况下为 true」,再补一句「不检查什么」。`Is`、`Has` 开头的名字与 `bool` 返回类型已经把成立条件说清时可省略 `@return`,不为了形式补一句同义反复。
82
82
  - `@param` 每个参数都写,写范围、单位或所有权,不写「索引」这类同义反复;没有参数就不写空的 `@param`。
83
- - `@return` 每个非 `void`、非构造、非析构的函数都写,写返回值怎么读或失败时返回什么;不要用「见上」或重复第一段。
83
+ - `@return` 默认写,写返回值怎么读或失败时返回什么;返回类型与函数名已把条件说清时可省略,不要用「见上」或重复第一段。
84
84
  - `@throws` 必须与 `references/exceptions.md` 一致;不抛异常的函数不写。
85
85
  - 私有函数同样写契约,不能默认调用者知道前置条件;输出参数标 `[out]`,既读又写标 `[in,out]`。
86
86
 
@@ -14,7 +14,7 @@
14
14
  - 头文件只就地定义大约 10 行以内的短函数;更长或需要完整定义的普通函数只在 `.cpp` 定义,模板和 `constexpr` 例外。
15
15
  - 失败是调用方的正常结果时,用结果类型、`bool` + 输出参数或 `std::optional` 表达;函数无法完成自己的任务、或构造函数无法建立 invariant 时,才在声明注释里写清抛出的异常类型;越界用 `std::out_of_range`,前置条件被破坏用 `std::logic_error`,只有调用方必须区分时才新增派生类型;Debug/Release 的抛出条件相同。消息句式见 `references/exceptions.md` 的「消息」节。
16
16
  - 一次操作有多种必须区分的正常结果时,用 `enum class` 返回(如落子:成功、越界、类型非法、已占),不用 `bool` 把原因压掉;成功还需要附带数据时再考虑 `std::expected` 或结果结构,成功无数据时不用。纯查询谓词仍用 `bool`,不要把 `IsEmpty` 改成三态。
17
- - 声明注释按 `references/comments.md` 的四节写:写作只写声明里看不出的事实——类型注释的名词短语(纯数据与枚举一句,类只在不变量不重复领域尺寸时加第二段)、特殊成员的标签与后置条件(例如 `默认构造函数:得到空棋盘、每个格子都是 kEmpty`)、函数注释的第一句结果或失败方式或检查项与契约。有参数就每个参数一条 `@param`,有返回值就写 `@return`,会抛才写 `@throws`;构造、析构和 `void` 无参数函数写两段后置条件,不写空标签。数据成员的主注释放在声明上一行并用 `///`,先说明这个成员是什么,再用全角冒号补名字和类型看不出的轴向、哨兵、所有权与改写时机,行尾不写补充;布局定文件头、位置与行尾;排版定符号、宽度与标签;润色删复述。类型注释不罗列成员功能、不写领域尺寸、不写方案对比与设计论证;全项目的风格理由写进项目文档,不写进注释。
17
+ - 声明注释按 `references/comments.md` 的四节写:写作只写声明里看不出的事实——类型注释的名词短语(纯数据与枚举一句,类只在不变量不重复领域尺寸时加第二段)、特殊成员的标签与后置条件(例如 `默认构造函数:得到空棋盘、每个格子都是 kEmpty`)、函数注释的第一句结果或失败方式或检查项与契约。有参数就每个参数一条 `@param`,有返回值默认写 `@return`(成立条件自明时可省),会抛才写 `@throws`;构造、析构和 `void` 无参数函数写两段后置条件,不写空标签。数据成员的主注释放在声明上一行并用 `///`,先说明这个成员是什么,再用全角冒号补名字和类型看不出的轴向、哨兵、所有权与改写时机,行尾不写补充;布局定文件头、位置与行尾;排版定符号、宽度与标签;润色删复述。类型注释不罗列成员功能、不写领域尺寸、不写方案对比与设计论证;全项目的风格理由写进项目文档,不写进注释。
18
18
  - `std::format` 用于组合消息;声明里不要承诺 `std::print`(C++23)。
19
19
 
20
20
  ## 情况