@chengzhao-dev/dsh-cpp-workflow 0.2.0 → 0.3.1

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.
Files changed (49) hide show
  1. package/README.en.md +7 -5
  2. package/README.md +7 -5
  3. package/lib/workspace.js +1 -56
  4. package/package.json +3 -2
  5. package/skills/building-cpp/SKILL.md +4 -2
  6. package/skills/building-cpp/assets/config/.clang-format +2 -0
  7. package/skills/building-cpp/assets/config/.clang-tidy +1 -0
  8. package/skills/building-cpp/assets/templates/projects/multi/CMakeLists.txt +2 -2
  9. package/skills/building-cpp/assets/templates/projects/shared-library/CMakeLists.txt +1 -1
  10. package/skills/building-cpp/assets/templates/projects/shared-library/greeting/CMakeLists.txt +1 -1
  11. package/skills/building-cpp/assets/templates/projects/single/CMakeLists.txt +1 -1
  12. package/skills/building-cpp/assets/templates/projects/static-library/CMakeLists.txt +1 -1
  13. package/skills/building-cpp/assets/templates/projects/static-library/greeting/CMakeLists.txt +1 -1
  14. package/skills/building-cpp/examples/cmake.md +15 -15
  15. package/skills/building-cpp/examples/tooling.md +3 -3
  16. package/skills/building-cpp/references/cmake.md +6 -6
  17. package/skills/building-cpp/references/encoding.md +49 -0
  18. package/skills/building-cpp/references/linking.md +6 -6
  19. package/skills/building-cpp/references/standards/c++23/README.md +1 -1
  20. package/skills/building-cpp/references/static-analysis.md +3 -3
  21. package/skills/building-cpp/scripts/checking-encoding.mjs +241 -0
  22. package/skills/building-cpp/steps/01-build.md +1 -1
  23. package/skills/building-cpp/steps/02-tooling.md +2 -2
  24. package/skills/building-cpp/steps/03-check.md +8 -6
  25. package/skills/developing-cpp/SKILL.md +19 -3
  26. package/skills/developing-cpp/examples/comments.md +161 -119
  27. package/skills/developing-cpp/examples/structure.md +4 -4
  28. package/skills/developing-cpp/references/comments.md +158 -69
  29. package/skills/developing-cpp/references/declaration-order.md +4 -2
  30. package/skills/developing-cpp/references/file-template.md +131 -0
  31. package/skills/developing-cpp/references/glossary.md +3 -3
  32. package/skills/developing-cpp/references/google-cpp-style.md +8 -8
  33. package/skills/developing-cpp/references/language.md +3 -3
  34. package/skills/developing-cpp/references/layout.md +21 -12
  35. package/skills/developing-cpp/references/naming.md +11 -2
  36. package/skills/developing-cpp/references/project-design.md +10 -1
  37. package/skills/developing-cpp/references/standard-library.md +1 -1
  38. package/skills/developing-cpp/scripts/checking-comments.mjs +316 -0
  39. package/skills/developing-cpp/steps/01-behavior.md +1 -1
  40. package/skills/developing-cpp/steps/02-boundary.md +5 -4
  41. package/skills/developing-cpp/steps/03-declaration.md +6 -6
  42. package/skills/developing-cpp/steps/04-implementation.md +3 -3
  43. /package/skills/building-cpp/assets/templates/projects/multi/src/{greeting.cc → greeting.cpp} +0 -0
  44. /package/skills/building-cpp/assets/templates/projects/multi/src/{main.cc → main.cpp} +0 -0
  45. /package/skills/building-cpp/assets/templates/projects/shared-library/greeting/src/{greeting.cc → greeting.cpp} +0 -0
  46. /package/skills/building-cpp/assets/templates/projects/shared-library/src/{main.cc → main.cpp} +0 -0
  47. /package/skills/building-cpp/assets/templates/projects/single/src/{main.cc → main.cpp} +0 -0
  48. /package/skills/building-cpp/assets/templates/projects/static-library/greeting/src/{greeting.cc → greeting.cpp} +0 -0
  49. /package/skills/building-cpp/assets/templates/projects/static-library/src/{main.cc → main.cpp} +0 -0
@@ -0,0 +1,241 @@
1
+ #!/usr/bin/env node
2
+ // Read-only encoding audit for a Git work tree.
3
+ //
4
+ // Default profile "portable" is the cross-platform baseline: UTF-8 without BOM,
5
+ // LF line endings, one final newline. Profile "msvc" allows a UTF-8 BOM and CRLF
6
+ // only for MSVC-compiled C/C++ sources; Markdown, CMake, scripts and configs stay
7
+ // portable. It never rewrites files.
8
+ //
9
+ // Usage:
10
+ // node checking-encoding.mjs [--root DIR] [--profile portable|msvc] [--json] [--quiet]
11
+ //
12
+ // Exit codes: 0 clean, 1 issues found, 2 usage or Git error.
13
+ import { execFileSync } from 'node:child_process'
14
+ import { existsSync, readFileSync, statSync } from 'node:fs'
15
+ import path from 'node:path'
16
+ import { pathToFileURL } from 'node:url'
17
+
18
+ export const EXIT = { ok: 0, issues: 1, usage: 2 }
19
+
20
+ const UTF8_BOM = [0xef, 0xbb, 0xbf]
21
+ const UTF16_LE = [0xff, 0xfe]
22
+ const UTF16_BE = [0xfe, 0xff]
23
+ const UTF32_LE = [0xff, 0xfe, 0x00, 0x00]
24
+ const UTF32_BE = [0x00, 0x00, 0xfe, 0xff]
25
+
26
+ const CRLF_REQUIRED = new Set(['.bat', '.cmd'])
27
+ const MSVC_SOURCE = new Set([
28
+ '.c', '.cc', '.cpp', '.cxx', '.h', '.hh', '.hpp', '.hxx', '.inl', '.ixx', '.cppm', '.rc',
29
+ ])
30
+ const ALWAYS_LF = new Set([
31
+ '.md', '.qmd', '.txt', '.rst', '.yml', '.yaml', '.json', '.jsonc', '.json5', '.toml',
32
+ '.ini', '.cfg', '.conf', '.sh', '.bash', '.zsh', '.fish', '.ps1', '.py', '.mjs', '.cjs',
33
+ '.js', '.jsx', '.ts', '.tsx', '.css', '.scss', '.sass', '.less', '.html', '.htm', '.xml',
34
+ '.svg', '.cmake', '.lua', '.sql', '.gitignore', '.gitattributes', '.editorconfig',
35
+ ])
36
+ const NO_EXTENSION_LF = new Set(['CMakeLists.txt', 'Makefile', 'Justfile', 'Dockerfile', 'LICENSE'])
37
+
38
+ function usage(message) {
39
+ if (message) process.stderr.write(`错误:${message}\n`)
40
+ process.stderr.write('用法:node checking-encoding.mjs [--root DIR] [--profile portable|msvc] [--json] [--quiet]\n')
41
+ return EXIT.usage
42
+ }
43
+
44
+ function parseArgs(argv) {
45
+ const options = { root: undefined, profile: 'portable', json: false, quiet: false }
46
+ for (let index = 0; index < argv.length; index += 1) {
47
+ const arg = argv[index]
48
+ if (arg === '--root') {
49
+ options.root = argv[index + 1]
50
+ index += 1
51
+ } else if (arg === '--profile') {
52
+ options.profile = argv[index + 1]
53
+ index += 1
54
+ } else if (arg === '--json') {
55
+ options.json = true
56
+ } else if (arg === '--quiet') {
57
+ options.quiet = true
58
+ } else if (arg === '--help' || arg === '-h') {
59
+ options.help = true
60
+ } else {
61
+ throw new Error(`未知参数 ${arg}`)
62
+ }
63
+ }
64
+ if (options.profile !== 'portable' && options.profile !== 'msvc') {
65
+ throw new Error(`--profile 只能是 portable 或 msvc,收到 ${options.profile}`)
66
+ }
67
+ return options
68
+ }
69
+
70
+ function gitRoot(from) {
71
+ try {
72
+ return execFileSync('git', ['-C', from, 'rev-parse', '--show-toplevel'], {
73
+ encoding: 'utf8',
74
+ stdio: ['ignore', 'pipe', 'ignore'],
75
+ }).trim()
76
+ } catch {
77
+ return undefined
78
+ }
79
+ }
80
+
81
+ function listFiles(root) {
82
+ const output = execFileSync('git', ['-C', root, 'ls-files', '-z', '--cached', '--others', '--exclude-standard'], {
83
+ encoding: 'utf8',
84
+ maxBuffer: 64 * 1024 * 1024,
85
+ })
86
+ return output
87
+ .split('\0')
88
+ .filter(Boolean)
89
+ .map((item) => path.join(root, item))
90
+ .filter((file) => {
91
+ try {
92
+ return statSync(file).isFile()
93
+ } catch {
94
+ return false
95
+ }
96
+ })
97
+ }
98
+
99
+ function startsWith(buffer, bytes) {
100
+ return bytes.every((byte, index) => buffer[index] === byte)
101
+ }
102
+
103
+ function bomOf(buffer) {
104
+ if (startsWith(buffer, UTF32_LE)) return 'UTF-32 LE'
105
+ if (startsWith(buffer, UTF32_BE)) return 'UTF-32 BE'
106
+ if (startsWith(buffer, UTF8_BOM)) return 'UTF-8'
107
+ if (startsWith(buffer, UTF16_LE)) return 'UTF-16 LE'
108
+ if (startsWith(buffer, UTF16_BE)) return 'UTF-16 BE'
109
+ return undefined
110
+ }
111
+
112
+ function isBinary(buffer) {
113
+ const limit = Math.min(buffer.length, 8192)
114
+ for (let index = 0; index < limit; index += 1) {
115
+ if (buffer[index] === 0) return true
116
+ }
117
+ return false
118
+ }
119
+
120
+ function inspect(file, root, profile) {
121
+ const rel = path.relative(root, file).split(path.sep).join('/')
122
+ const extension = path.extname(file).toLowerCase()
123
+ const source = MSVC_SOURCE.has(extension)
124
+ const alwaysLf = ALWAYS_LF.has(extension) || NO_EXTENSION_LF.has(path.basename(file))
125
+ const issues = []
126
+ const add = (kind, message) => issues.push({ path: rel, kind, message })
127
+
128
+ let buffer
129
+ try {
130
+ buffer = readFileSync(file)
131
+ } catch (error) {
132
+ add('read', `无法读取(${error.message})`)
133
+ return { rel, issues }
134
+ }
135
+
136
+ const bom = bomOf(buffer)
137
+ if (bom === 'UTF-8') {
138
+ if (profile !== 'msvc' || !source) {
139
+ add('bom', profile === 'msvc' && !source ? '只有 MSVC 编译的 C/C++ 源码可以带 BOM' : '不应带 BOM')
140
+ }
141
+ } else if (bom) {
142
+ add('bom', `发现 ${bom} BOM;只接受 UTF-8`)
143
+ }
144
+
145
+ if (bom && bom !== 'UTF-8') {
146
+ return { rel, issues }
147
+ }
148
+ if (isBinary(buffer)) {
149
+ return { rel, issues }
150
+ }
151
+
152
+ let text
153
+ try {
154
+ text = new TextDecoder('utf-8', { fatal: true }).decode(buffer)
155
+ } catch {
156
+ add('utf8', '不是合法 UTF-8')
157
+ return { rel, issues }
158
+ }
159
+
160
+ const body = text.startsWith('\uFEFF') ? text.slice(1) : text
161
+ if (body.includes('\uFEFF')) {
162
+ add('bom', '文件中间含 U+FEFF')
163
+ }
164
+
165
+ if (buffer.length > 0 && !body.endsWith('\n')) {
166
+ add('newline', '缺少末尾换行')
167
+ }
168
+
169
+ const crlf = (body.match(/\r\n/g) ?? []).length
170
+ const lf = (body.match(/(?<!\r)\n/g) ?? []).length
171
+ const cr = (body.match(/\r(?!\n)/g) ?? []).length
172
+
173
+ if (CRLF_REQUIRED.has(extension)) {
174
+ if (crlf === 0 || lf > 0 || cr > 0) add('eol', '应为 CRLF')
175
+ } else if (profile === 'msvc' && source && !alwaysLf) {
176
+ if ((crlf > 0 && lf > 0) || cr > 0) add('eol', '不要混用 CRLF 与 LF')
177
+ } else if (crlf > 0 || cr > 0) {
178
+ add('eol', '应为 LF')
179
+ }
180
+
181
+ return { rel, issues }
182
+ }
183
+
184
+ export function checkEncoding(root, profile = 'portable') {
185
+ const files = listFiles(root)
186
+ const results = []
187
+ for (const file of files) {
188
+ results.push(inspect(file, root, profile))
189
+ }
190
+ const issues = results.flatMap((result) => result.issues)
191
+ const counts = { files: results.length, issues: issues.length, bom: 0, eol: 0, utf8: 0, newline: 0, read: 0 }
192
+ for (const issue of issues) counts[issue.kind] = (counts[issue.kind] ?? 0) + 1
193
+ return { root, profile, files: results.length, issues, counts }
194
+ }
195
+
196
+ function main() {
197
+ let options
198
+ try {
199
+ options = parseArgs(process.argv.slice(2))
200
+ } catch (error) {
201
+ return usage(error.message)
202
+ }
203
+ if (options.help) {
204
+ process.stdout.write('检查 Git 工作树的 UTF-8/BOM、行尾与末尾换行;默认 portable。\n')
205
+ return EXIT.ok
206
+ }
207
+
208
+ const base = options.root ? path.resolve(options.root) : process.cwd()
209
+ if (!existsSync(base)) return usage(`根目录不存在:${base}`)
210
+ const root = gitRoot(base)
211
+ if (!root) return usage(`不是 Git 工作树:${base}`)
212
+
213
+ let report
214
+ try {
215
+ report = checkEncoding(root, options.profile)
216
+ } catch (error) {
217
+ process.stderr.write(`错误:${error.message}\n`)
218
+ return EXIT.usage
219
+ }
220
+
221
+ if (options.json) {
222
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`)
223
+ } else if (!options.quiet || report.issues.length > 0) {
224
+ if (report.issues.length === 0) {
225
+ process.stdout.write(`编码检查:${report.files} 个文件,未发现问题(profile ${report.profile})。\n`)
226
+ } else {
227
+ process.stdout.write(`编码检查:${report.files} 个文件,发现 ${report.issues.length} 个问题(profile ${report.profile})。\n`)
228
+ for (const issue of report.issues) {
229
+ process.stdout.write(` ${issue.path}: [${issue.kind}] ${issue.message}\n`)
230
+ }
231
+ process.stdout.write(
232
+ `汇总:BOM ${report.counts.bom},行尾 ${report.counts.eol},UTF-8 ${report.counts.utf8},末尾换行 ${report.counts.newline}\n`,
233
+ )
234
+ }
235
+ }
236
+ return report.issues.length === 0 ? EXIT.ok : EXIT.issues
237
+ }
238
+
239
+ if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
240
+ process.exitCode = main()
241
+ }
@@ -60,7 +60,7 @@ GCC 模式把目录和编译器换成 `build/gcc`、`g++`。工程确有 C 源
60
60
 
61
61
  ## 情况
62
62
 
63
- **新工程**:从 `assets/templates/projects/` 选形态:`single` 单文件、`multi` 多文件单程序、`static-library` 静态库 + 程序、`shared-library` 动态库 + 程序。只复制目标工程缺的文件;已有的 `CMakeLists.txt`、`.clang-*`、源码不覆盖。模板里的 `{{PROJECT_NAME}}` 换成真实工程名,默认标准 C++23;四套模板已按 `__cplusplus` 条件使用 `std::format`,把 `standard`、`cxx_std_*`、`CXX_STANDARD` 和 `.clang-format` 的解析档位一起降到 C++11–C++20 后仍应能构建,降完跑 `references/standards/README.md` 的五档矩阵。模板源码是 `.cc`,头文件用路径式 `#define` 守卫;新增文件沿用 `.cc` / `.h`,已有工程保持原有扩展名。
63
+ **新工程**:从 `assets/templates/projects/` 选形态:`single` 单文件、`multi` 多文件单程序、`static-library` 静态库 + 程序、`shared-library` 动态库 + 程序。只复制目标工程缺的文件;已有的 `CMakeLists.txt`、`.clang-*`、源码不覆盖。模板里的 `{{PROJECT_NAME}}` 换成真实工程名,默认标准 C++23;四套模板已按 `__cplusplus` 条件使用 `std::format`,把 `standard`、`cxx_std_*`、`CXX_STANDARD` 和 `.clang-format` 的解析档位一起降到 C++11–C++20 后仍应能构建,降完跑 `references/standards/README.md` 的五档矩阵。模板源码是 `.cpp`,头文件用路径式 `#define` 守卫;新增文件沿用 `.cpp` / `.h`,已有工程保持原有扩展名。
64
64
 
65
65
  **已有工程**:先读现有 `CMakeLists.txt`,找出全局设置、GLOB、输出目录和 target 命名;只改与当前任务相关的一处。大改前先让通过检查的项目入口跑通,保留可回退点。
66
66
 
@@ -13,8 +13,8 @@
13
13
  - `.clangd` 的 `CompilationDatabase` 指向 `build/<compiler>`;切换编译器时同步改,不能留旧目录。**不要写 `-std`**:它会覆盖 `compile_commands.json`,把 C++23 工程降成旧标准;标准只由构建声明和编译数据库决定。
14
14
  - `--query-driver` 是 clangd 的命令行参数,写在 `.vscode/settings.json` 的 `clangd.arguments` 里,不是 `.clangd` 的 YAML 键。它告诉 clangd 允许执行哪个编译器驱动来取系统头路径;路径写绝对路径,并与 `environment.json` 的编译器一致。
15
15
  - 首次复制时按轴替换三处:`compiler=clang` 用 `CompilationDatabase: build/clang`、`--query-driver=/usr/bin/clang++` 和 `cmake.debugConfig` 的 `MIMode: lldb` / `miDebuggerPath: /usr/bin/lldb`;`compiler=gcc` 用 `build/gcc`、`/usr/bin/g++` 和 `MIMode: gdb` / `/usr/bin/gdb`。只复制模板而不改这三处,GCC 模式会指向错误的数据库、驱动或调试器。原工程已有的 `.clangd` 不覆盖;要改先征得同意。
16
- - `.clang-format` 只保留 `Language: Cpp`、`BasedOnStyle: Google`、`Standard: Latest`(clang-format 21/22 不接受 `c++23`)。列宽 80、每级 2 空格、不用 tab、包含顺序都取 Google 预设;不要覆盖 `ColumnLimit`、`TabWidth`、`IncludeCategories` 或指针对齐。需要新规则时先改 `references/google-cpp-style.md` 并说明理由。
17
- - `.clang-tidy` 用 `Checks: '-*, google-*, readability-identifier-naming'`,保留 `WarningsAsErrors: '*'` 和 `FormatStyle: file`。`google-*` 覆盖 Google 风格能静态检查的部分;`readability-identifier-naming` 只配项目确实统一的命名项:类型/枚举/别名/模板参数 PascalCase,变量/参数/成员 snake_case,类成员尾随 `_`,结构体成员不尾随,常量 `k` + PascalCase。不要设 `FunctionCase`,Google 允许 `count()` / `set_count()` 这类访问器;不要重新打开 `cppcoreguidelines-*` 整包或 `portability-avoid-pragma-once`。
16
+ - `.clang-format` 保留 `Language: Cpp`、`BasedOnStyle: Google` 和 `Standard`:模板资产写 `Latest`;工程按 `environment.json` 声明 C++11–C++20 时,把 `Standard` 改成 `c++NN`(映射见 `references/standards/README.md`)。clang-format 21/22 的 `Standard` 不接受 `c++23`,所以 C++23 工程仍写 `Latest`。列宽 80、每级 2 空格、不用 tab、包含顺序都取 Google 预设;不要覆盖 `ColumnLimit`、`TabWidth`、`IncludeCategories` 或指针对齐。需要新规则时先改 `references/google-cpp-style.md` 并说明理由。
17
+ - `.clang-tidy` 用 `Checks: '-*, google-*, portability-avoid-pragma-once, readability-identifier-naming'`,保留 `WarningsAsErrors: '*'` 和 `FormatStyle: file`。`google-*` 覆盖 Google 风格能静态检查的部分;`readability-identifier-naming` 只配项目确实统一的命名项:类型/枚举/别名/模板参数 PascalCase,变量/参数/成员 snake_case,类成员尾随 `_`,结构体成员不尾随,常量 `k` + PascalCase。不要设 `FunctionCase`,Google 允许 `count()` / `set_count()` 这类访问器;`portability-avoid-pragma-once` 要打开,因为工程统一用路径式守卫;不要重新打开 `cppcoreguidelines-*` 整包。
18
18
  - `.clang-tidy` 关闭项和例外必须写理由;不要借 `-*` 之后再打开 `cppcoreguidelines-*` 这类非 Google 整族检查。
19
19
  - `.vscode/extensions.json` 推荐 clangd、CMake Tools 与 C/C++ 扩展;`.vscode/settings.json` 固定 clangd 参数、CMake 构建目录和 `cmake.debugConfig`。配置、构建、运行和调试只从 CMake Tools 进入,不写 `tasks.json` 或 `launch.json`;C/C++ 扩展只作为 CMake 快速调试的后端,IntelliSense 关闭,避免和 clangd 并行。配置注释用 JSONC 的 `//`,贴在被解释的键上方。
20
20
  - 配置注释写「为什么这样设」,不写「这一项是什么」;不写策略头、不指向文档路径。
@@ -8,15 +8,17 @@
8
8
 
9
9
  按固定顺序跑,前一步失败就停,不要带着失败继续:
10
10
 
11
- 1. **校验环境声明(不带 `--live`)**:读 `environment.json`,检查三个环境轴、`standard` 和组合是否合法。环境轴为空、`standard` 非法或组合非法时,停在这里,不找可执行文件。
12
- 2. **活动校验**:在声明的 build host 上跑 `--live`,核对 PATH 中的角色和版本。`windows` + `native` 在声明校验阶段就返回失败,不会进入这一步。
13
- 3. **跑项目入口命令**:项目技能里写的入口命令(如 `./running-build.sh clang`)。入口命令仍是 `<entry-command>` 或空白时,停在这里,不要猜命令;是真实命令才跑。入口内部依次 configure → build → format → tidy → ctest → run;每一步失败都保留原始输出。
14
- 4. **核对产物**:`build/<compiler>/compile_commands.json` 存在;`build/compile_commands.json` 指向当前编译器;`app` 可执行文件在 `build/<compiler>/bin`。
15
- 5. **记录结果**:通过时记录编译器版本、构建目录、测试数量;失败时记录命令、退出码、第一条根因诊断。
11
+ 1. **校验编码与行尾**:在仓库根跑编码检查。默认 UTF-8 无 BOM、LF;MSVC 原生工程加 `--profile msvc`。有问题先按 `references/encoding.md` 修正,不带着 BOM 或混用行尾继续。
12
+ 2. **校验环境声明(不带 `--live`)**:读 `environment.json`,检查三个环境轴、`standard` 和组合是否合法。环境轴为空、`standard` 非法或组合非法时,停在这里,不找可执行文件。
13
+ 3. **活动校验**:在声明的 build host 上跑 `--live`,核对 PATH 中的角色和版本。`windows` + `native` 在声明校验阶段就返回失败,不会进入这一步。
14
+ 4. **跑项目入口命令**:项目技能里写的入口命令(如 `./running-build.sh clang`)。入口命令仍是 `<entry-command>` 或空白时,停在这里,不要猜命令;是真实命令才跑。入口内部依次 configure → build → format → tidy → ctest → run;每一步失败都保留原始输出。
15
+ 5. **核对产物**:`build/<compiler>/compile_commands.json` 存在;`build/compile_commands.json` 指向当前编译器;`app` 可执行文件在 `build/<compiler>/bin`。
16
+ 6. **记录结果**:通过时记录编译器版本、构建目录、测试数量;失败时记录命令、退出码、第一条根因诊断。
16
17
 
17
- 环境校验脚本:
18
+ 编码检查与环境校验脚本:
18
19
 
19
20
  ```bash
21
+ node "<skill-dir>/scripts/checking-encoding.mjs" --profile portable # UTF-8 无 BOM、LF
20
22
  node "<skill-dir>/scripts/checking-environment.mjs" --project . # 声明校验
21
23
  node "<skill-dir>/scripts/checking-environment.mjs" --project . --live # 活动校验
22
24
  ```
@@ -9,10 +9,12 @@ metadata:
9
9
 
10
10
  ## 适用场景
11
11
 
12
- 管 `.h` / `.cc` 里换一台机器也不会改的内容:可观察行为、库与程序的边界、声明、命名、异常和源码注释。排版、命名、头文件与包含顺序以 `references/google-cpp-style.md` 的 Google 基线为准。异常条文采用 C++ Core Guidelines 的 Error handling,作为开发阶段的源码指导;它不进入插件运行时。`CMakeLists.txt`、`cmake/`、`running-build.sh`、`.clang-*`、`.vscode/` 和 `environment.json` 只开 `building-cpp`。只改函数体、命名、异常或注释且入口命令可运行时,定实现后只开 `building-cpp` 的「通过检查」;新增或移动源文件、改库与程序的边界、补测试 target 时,声明和实现可以先写,但完成前必须开 `building-cpp` 的定构建,把文件写进 CMake,再决定是否定工具配置,最后通过检查。`.qmd`、仓库 README、Python 和非 C++ 文档不打开本技能。
12
+ 管 `.h` / `.hpp` / `.cpp` / `.cc` 里换一台机器也不会改的内容:可观察行为、库与程序的边界、声明、命名、异常和源码注释。文件槽位与守卫以 `references/file-template.md` 为准;排版、命名、头文件与包含顺序以 `references/google-cpp-style.md` 的 Google 基线为准。异常条文采用 C++ Core Guidelines 的 Error handling,作为开发阶段的源码指导;它不进入插件运行时。`CMakeLists.txt`、`cmake/`、`running-build.sh`、`.clang-*`、`.vscode/` 和 `environment.json` 只开 `building-cpp`。只改函数体、命名、异常或注释且入口命令可运行时,定实现后只开 `building-cpp` 的「通过检查」;新增或移动源文件、改库与程序的边界、补测试 target 时,声明和实现可以先写,但完成前必须开 `building-cpp` 的定构建,把文件写进 CMake,再决定是否定工具配置,最后通过检查。`.qmd`、仓库 README、Python 和非 C++ 文档不打开本技能。
13
13
 
14
14
  存在项目技能 `cpp-workflow-project` 时先读它的 `SKILL.md`:工程名、库与程序的边界、阶段表、术语增补、入口命令。没有项目技能时门禁不会放出本技能,不要猜项目约定。
15
15
 
16
+ 源码和构建文件的编码以 `building-cpp` 的 `references/encoding.md` 为准;行尾与 `.clang-format` 的 `LineEnding` 跟仓库根 `.gitattributes` 和 `.editorconfig`。
17
+
16
18
  ## 两种走法
17
19
 
18
20
  | 走法 | 什么时候用 | 怎么做 |
@@ -29,9 +31,22 @@ metadata:
29
31
  | 1 | 定行为 | `steps/01-behavior.md` | 把这一处要能观察到的结果写成验收句 |
30
32
  | 2 | 定边界 | `steps/02-boundary.md` | 决定哪些放库、哪些放程序,依赖朝哪个方向 |
31
33
  | 3 | 定声明 | `steps/03-declaration.md` | 写命名、声明顺序、头文件契约和声明注释 |
32
- | 4 | 定实现 | `steps/04-implementation.md` | 写 `.cc` 的操作顺序、分组和实现注释 |
34
+ | 4 | 定实现 | `steps/04-implementation.md` | 写 `.cpp` 函数体的操作顺序、分组和实现注释 |
35
+
36
+ 每步只打开同号的一个步骤文件。判不准才打开 `examples/` 里与该步对应的案例;`references/` 保存完整条文,只在排错、整目录重构,或步骤里明确指向某一条文时按需打开。性能问题读 `references/performance.md`,标准库设施选型读 `references/standard-library.md`,链接与产物的细节读 `building-cpp` 的 `references/linking.md`。
37
+
38
+ ### 四类改动
39
+
40
+ 先认这次改的是哪一类,再决定从哪一步进;一次改动混了两类时,按「写作 → 布局 → 排版 → 润色」的次序拆开做,前一类落定并经检查再进下一类。
41
+
42
+ | 类型 | 典型请求 | 从哪进 | 先读哪份条文 |
43
+ | --- | --- | --- | --- |
44
+ | 写作 | 新增类型、函数或文件;改可观察行为;改库与程序的边界 | 第 1 步 | `steps/01-behavior.md`、`02-boundary.md`、`references/project-design.md` |
45
+ | 布局 | 换文件位置、拆头文件、调声明顺序、补测试 target | 第 3 步 | `references/file-template.md`、`declaration-order.md` |
46
+ | 排版 | 只改缩进、行宽、包含顺序或格式 | 第 3 步的声明写法,或 `building-cpp` 的「定工具配置」 | `references/google-cpp-style.md`、`layout.md` |
47
+ | 润色 | 只改命名、只改注释措辞 | 第 3、4 步 | `references/naming.md`、`comments.md` |
33
48
 
34
- 每步只打开同号的一个步骤文件。判不准才打开 `examples/` 里与该步对应的案例;`references/` 保存完整条文,只在排错、整目录重构,或步骤里明确指向某一条文时按需打开。
49
+ 只改格式时不动行为,只改注释时不动签名;类型注释的职责、跨文件边界与线程规则,只按 `references/comments.md` 的条文取内容。
35
50
 
36
51
  ## 完成判据
37
52
 
@@ -39,4 +54,5 @@ metadata:
39
54
  - 第 2 步的库 target 不依赖终端、GUI 或脚本;程序 target 只做装配和输入输出;共享抽象有第二个真实使用方。
40
55
  - 第 3 步的公共头文件自包含,命名、声明顺序和异常声明符合 `references/` 的条文。
41
56
  - 第 4 步的实现只兑现声明;资源由 RAII 管理;失败是正常结果时用返回值,函数无法完成任务时抛异常,不拿异常做正常分支。
57
+ - 源码注释符合 `references/comments.md` 的四层规范,`node "<skill-dir>/scripts/checking-comments.mjs" --root <项目根>` 无输出。
42
58
  - 没有改 `CMakeLists.txt`、`.clang-*`、构建脚本,也没有把外部资料路径或项目专属类名写进通用条文。