@chengzhao-dev/dsh-cpp-workflow 0.3.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 (44) 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 +1 -1
  5. package/skills/building-cpp/SKILL.md +2 -2
  6. package/skills/building-cpp/assets/config/.clang-tidy +1 -0
  7. package/skills/building-cpp/assets/templates/projects/multi/CMakeLists.txt +2 -2
  8. package/skills/building-cpp/assets/templates/projects/shared-library/CMakeLists.txt +1 -1
  9. package/skills/building-cpp/assets/templates/projects/shared-library/greeting/CMakeLists.txt +1 -1
  10. package/skills/building-cpp/assets/templates/projects/single/CMakeLists.txt +1 -1
  11. package/skills/building-cpp/assets/templates/projects/static-library/CMakeLists.txt +1 -1
  12. package/skills/building-cpp/assets/templates/projects/static-library/greeting/CMakeLists.txt +1 -1
  13. package/skills/building-cpp/examples/cmake.md +15 -15
  14. package/skills/building-cpp/examples/tooling.md +3 -3
  15. package/skills/building-cpp/references/cmake.md +6 -6
  16. package/skills/building-cpp/references/linking.md +6 -6
  17. package/skills/building-cpp/references/static-analysis.md +3 -3
  18. package/skills/building-cpp/steps/01-build.md +1 -1
  19. package/skills/building-cpp/steps/02-tooling.md +2 -2
  20. package/skills/developing-cpp/SKILL.md +17 -3
  21. package/skills/developing-cpp/examples/comments.md +151 -138
  22. package/skills/developing-cpp/examples/structure.md +4 -4
  23. package/skills/developing-cpp/references/comments.md +152 -81
  24. package/skills/developing-cpp/references/declaration-order.md +4 -2
  25. package/skills/developing-cpp/references/file-template.md +131 -0
  26. package/skills/developing-cpp/references/glossary.md +3 -3
  27. package/skills/developing-cpp/references/google-cpp-style.md +8 -8
  28. package/skills/developing-cpp/references/language.md +3 -3
  29. package/skills/developing-cpp/references/layout.md +21 -12
  30. package/skills/developing-cpp/references/naming.md +3 -2
  31. package/skills/developing-cpp/references/project-design.md +1 -1
  32. package/skills/developing-cpp/references/standard-library.md +1 -1
  33. package/skills/developing-cpp/scripts/checking-comments.mjs +316 -0
  34. package/skills/developing-cpp/steps/01-behavior.md +1 -1
  35. package/skills/developing-cpp/steps/02-boundary.md +4 -4
  36. package/skills/developing-cpp/steps/03-declaration.md +5 -5
  37. package/skills/developing-cpp/steps/04-implementation.md +3 -3
  38. /package/skills/building-cpp/assets/templates/projects/multi/src/{greeting.cc → greeting.cpp} +0 -0
  39. /package/skills/building-cpp/assets/templates/projects/multi/src/{main.cc → main.cpp} +0 -0
  40. /package/skills/building-cpp/assets/templates/projects/shared-library/greeting/src/{greeting.cc → greeting.cpp} +0 -0
  41. /package/skills/building-cpp/assets/templates/projects/shared-library/src/{main.cc → main.cpp} +0 -0
  42. /package/skills/building-cpp/assets/templates/projects/single/src/{main.cc → main.cpp} +0 -0
  43. /package/skills/building-cpp/assets/templates/projects/static-library/greeting/src/{greeting.cc → greeting.cpp} +0 -0
  44. /package/skills/building-cpp/assets/templates/projects/static-library/src/{main.cc → main.cpp} +0 -0
@@ -15,7 +15,7 @@
15
15
  | 命名空间 | snake_case | `taskflow`, `warehouse` | `Taskflow`, `Warehouse` |
16
16
  | 宏 | 全大写 + 项目前缀,尽量不用 | `CPP_WORKFLOW_ASSERT` | `ASSERT` |
17
17
 
18
- - 文件名用 snake_case:`order_result.h`、`task_table.cc`;测试文件 `task_table_test.cc`。
18
+ - 文件名用 snake_case:`order_result.h`、`task_table.cpp`;测试文件 `task_table_test.cpp`。
19
19
  - 布尔变量用 `is_` / `has_` / `can_` 开头:`is_valid`、`has_stock`;不要用 `flag`、`status`。
20
20
  - 容器用复数:`tasks`、`orders`;单个元素用单数。
21
21
  - 缩写按单词处理:`url`、`id`、`http`,不要写 `URL` 混进 snake_case;类型名里可以 `HttpClient`。
@@ -26,7 +26,7 @@
26
26
  | 角色 | 前缀 / 动词 | 例子 | 说明 |
27
27
  | --- | --- | --- | --- |
28
28
  | 查询 | `Get` 或访问器风格 | `GetCurrentOrder()`、`CurrentOrder()` | 只读、不改变状态;同一 API 选一种 |
29
- | 判断 | `Is` / `Has` / `Can` | `Contains()`, `HasStock()` | 返回 `bool`,不抛异常 |
29
+ | 判断 | `Is` / `Has` / `Can`;成对规则用 `Completes` 等限定式 | `IsEmpty()`、`HasStock()`、`CompletesLine()` | 返回 `bool`,不抛异常 |
30
30
  | 查找 | `Find` | `FindTask()` | 找不到是正常结果,返回 `optional` 或迭代器,不抛 |
31
31
  | 计数 | `Count` | `CountPendingTasks()` | 返回数量,不修改状态 |
32
32
  | 尝试 | `Try` | `TryReserve()` | 可能失败但不抛异常,返回结果类型 |
@@ -47,6 +47,7 @@
47
47
  ## 谓词与查询命名
48
48
 
49
49
  - 返回 `bool` 的查询用肯定式,读起来像一句断言:`IsEmpty()`、`HasStock()`、`CanReserve()`、`CompletesLine()`。
50
+ - 前缀集就是这四类:`Is` / `Has` / `Can`,外加与成对规则一一对应的 `Completes`、`Would` 这类限定式;`Contains` 只保留给容器成员(`std::vector::empty` 一类已有标准名),不拿它当一般谓词。
50
51
  - `Get` 只用于取值,`Set` 只用于设值;不要用 `CheckLine()` 当布尔 getter。
51
52
  - `Check` / `Validate` 留给会报告错误、产生诊断或可能失败的操作;它们不返回纯 `bool` 查询。
52
53
  - 假设性查询在名字里写明条件:`CompletesLine(position, cell)` 读作「这一手会成线吗」;需要更强语气时用 `WouldCompleteLine()`。同一 API 只选一种,改名时同步声明、定义、调用点、测试和注释。
@@ -62,7 +62,7 @@
62
62
  - 平台变化(Windows/WSL/macOS 路径、动态库查找)放构建脚本或适配层,不进核心。
63
63
  - 测试需要替换的外部依赖(时钟、随机源、输入输出)通过接口注入,不在核心内部直接调用。
64
64
 
65
- 公共头文件只暴露稳定契约;实现细节放 `.cc` 或私有头。一个组件一个 `CMakeLists.txt`,组件的 `include/` 是对外接口,`src/` 是实现。
65
+ 公共头文件只暴露稳定契约;实现细节放 `.cpp` 或私有头。一个组件一个 `CMakeLists.txt`,组件的 `include/` 是对外接口,`src/` 是实现。
66
66
 
67
67
  ## 什么时候抽共享库
68
68
 
@@ -97,4 +97,4 @@ std::erase_if(values, [](int value) { return value % 2 == 0; });
97
97
  - [ ] 没有保存 `string_view` / `span` 指向的临时对象。
98
98
  - [ ] 修改容器大小前处理了迭代器失效。
99
99
  - [ ] 能用算法表达时没有手写下标循环。
100
- - [ ] 输出用 `std::format` 组合、流输出,没有 `std::print`。
100
+ - [ ] 组合消息用 `std::format` 或流输出;`std::print` 只出现在 C++23 的程序边界,库代码没有。
@@ -0,0 +1,316 @@
1
+ #!/usr/bin/env node
2
+ // 只读注释审计:按 references/comments.md 的硬规则检查 C/C++ 源文件。
3
+ //
4
+ // 检查项:文件头横幅、横幅宽度、注释行宽度、Doxygen 标签、注释与声明的贴合、
5
+ // 类型注释存在性、成员功能清单、设计论证(方案对比)、@param 完整性、枚举成员注释一致性。
6
+ // 不重写任何文件。
7
+ //
8
+ // 用法:
9
+ // node checking-comments.mjs [--root DIR] [--width N] [--banner-width N] [--no-header] [--json] [--quiet]
10
+ //
11
+ // 退出码:0 无问题,1 发现问题,2 用法或读取错误。
12
+ import { readdirSync, readFileSync, statSync } from 'node:fs'
13
+ import path from 'node:path'
14
+ import { pathToFileURL } from 'node:url'
15
+
16
+ export const EXIT = { ok: 0, issues: 1, usage: 2 }
17
+
18
+ const SOURCE_EXT = new Set([
19
+ '.h', '.hh', '.hpp', '.hxx', '.h++', '.inl', '.ipp', '.tpp',
20
+ '.cpp', '.cc', '.cxx', '.c++', '.c', '.m', '.mm',
21
+ ])
22
+ const HEADER_EXT = new Set(['.h', '.hh', '.hpp', '.hxx', '.h++', '.inl', '.ipp', '.tpp'])
23
+ const SKIP_DIR = new Set([
24
+ '.git', '.hg', '.svn', 'build', 'out', 'node_modules', '_book', '.quarto', '.cache',
25
+ '.tmp', 'temp', 'third_party', 'third-party', 'external', 'vendor', 'deps', 'cmake-build-debug',
26
+ 'cmake-build-release', '.vs', '.idea',
27
+ ])
28
+ const FORBIDDEN_TAGS = ['@returns', '@brief', '@details', '@author', '@since', '@todo', '@date', '@file', '@copyright', '@version']
29
+ const BANNER = /^\/\/=+-+=+\/\/$/
30
+ const MEMBER_LIST = /提供[^。]{0,40}、[^。]{0,40}(的功能|的能力|的接口)|功能[::]/
31
+ const RATIONALE = /而不是|而非|代价.{0,4}忽略|只留一份|与其|比.{0,12}更|更(?:安全|快|清楚|直接|好写|难写)/
32
+ const FORWARD_REF = /(?:留在|归|交给|由)\s*`?[A-Z][A-Za-z0-9_]*`?/
33
+ const TYPE_SYSTEM = /值语义|自由拷贝/
34
+ const DECL_KEYWORDS = /^(class|struct|union|enum|namespace|using|typedef|template|#|if|else|for|while|switch|return|case|default)\b/
35
+
36
+ function usage(message) {
37
+ if (message) process.stderr.write(`错误:${message}\n`)
38
+ process.stderr.write('用法:node checking-comments.mjs [--root DIR] [--width N] [--banner-width N] [--no-header] [--json] [--quiet]\n')
39
+ return EXIT.usage
40
+ }
41
+
42
+ function parseArgs(argv) {
43
+ const options = { root: undefined, width: 80, bannerWidth: 80, requireHeader: true, json: false, quiet: false }
44
+ for (let index = 0; index < argv.length; index += 1) {
45
+ const arg = argv[index]
46
+ if (arg === '--root') {
47
+ options.root = argv[index + 1]
48
+ index += 1
49
+ } else if (arg === '--width') {
50
+ options.width = Number(argv[index + 1])
51
+ index += 1
52
+ } else if (arg === '--banner-width') {
53
+ options.bannerWidth = Number(argv[index + 1])
54
+ index += 1
55
+ } else if (arg === '--no-header') {
56
+ options.requireHeader = false
57
+ } else if (arg === '--json') {
58
+ options.json = true
59
+ } else if (arg === '--quiet') {
60
+ options.quiet = true
61
+ } else if (arg === '--help' || arg === '-h') {
62
+ options.help = true
63
+ } else {
64
+ throw new Error(`未知参数 ${arg}`)
65
+ }
66
+ }
67
+ for (const key of ['width', 'bannerWidth']) {
68
+ if (!Number.isInteger(options[key]) || options[key] < 40) {
69
+ throw new Error(`--${key} 需要不小于 40 的整数`)
70
+ }
71
+ }
72
+ return options
73
+ }
74
+
75
+ /** 中文按两列计宽,用于判断渲染后的行宽。 */
76
+ export function displayWidth(text) {
77
+ let width = 0
78
+ for (const char of text) {
79
+ const code = char.codePointAt(0)
80
+ const wide =
81
+ (code >= 0x1100 && code <= 0x115f) ||
82
+ (code >= 0x2e80 && code <= 0xa4cf) ||
83
+ (code >= 0xac00 && code <= 0xd7a3) ||
84
+ (code >= 0xf900 && code <= 0xfaff) ||
85
+ (code >= 0xfe30 && code <= 0xfe6f) ||
86
+ (code >= 0xff00 && code <= 0xff60) ||
87
+ (code >= 0xffe0 && code <= 0xffe6) ||
88
+ (code >= 0x1f300 && code <= 0x1f64f) ||
89
+ (code >= 0x20000 && code <= 0x3fffd)
90
+ width += wide ? 2 : 1
91
+ }
92
+ return width
93
+ }
94
+
95
+ /** 取出顶层逗号分隔的参数段,忽略模板与括号内的逗号。 */
96
+ function splitParams(text) {
97
+ const parts = []
98
+ let depth = 0
99
+ let current = ''
100
+ for (const char of text) {
101
+ if (char === '<' || char === '(' || char === '[' || char === '{') depth += 1
102
+ if (char === '>' || char === ')' || char === ']' || char === '}') depth = Math.max(0, depth - 1)
103
+ if (char === ',' && depth === 0) {
104
+ parts.push(current)
105
+ current = ''
106
+ continue
107
+ }
108
+ current += char
109
+ }
110
+ if (current.trim() !== '') parts.push(current)
111
+ return parts.map((part) => part.trim()).filter((part) => part !== '' && part !== 'void')
112
+ }
113
+
114
+ /** 取参数名:去掉默认值、数组后缀、引用与指针,保留最后一个标识符。 */
115
+ function paramName(part) {
116
+ let text = part
117
+ if (text.includes('=')) text = text.slice(0, text.indexOf('='))
118
+ text = text.replace(/\[[^\]]*\]/g, ' ').trim()
119
+ if (text === '...') return undefined
120
+ const tokens = text.match(/[A-Za-z_]\w*/g) ?? []
121
+ const last = tokens.at(-1)
122
+ if (!last) return undefined
123
+ if (['const', 'volatile', 'signed', 'unsigned', 'void'].includes(last)) return undefined
124
+ return last
125
+ }
126
+
127
+ /** 提取函数声明的参数名列表;不是函数声明时返回 undefined。 */
128
+ export function functionParams(declaration) {
129
+ const decl = declaration.trim().replace(/^template\s*<[^>]*>\s*/, '')
130
+ if (DECL_KEYWORDS.test(decl)) return undefined
131
+ const open = decl.indexOf('(')
132
+ if (open === -1) return undefined
133
+ const namePart = decl.slice(0, open).trim()
134
+ if (!/[~A-Za-z_]\w*$|operator\W/.test(namePart)) return undefined
135
+ let depth = 0
136
+ let close = -1
137
+ for (let index = open; index < decl.length; index += 1) {
138
+ const char = decl[index]
139
+ if (char === '(') depth += 1
140
+ if (char === ')') {
141
+ depth -= 1
142
+ if (depth === 0) {
143
+ close = index
144
+ break
145
+ }
146
+ }
147
+ }
148
+ if (close === -1) return undefined
149
+ const tail = decl.slice(close + 1).trim()
150
+ if (tail !== '' && !/^[;{:]/.test(tail) && !/^(const|noexcept|override|final|->|=\s*(0|default|delete))/.test(tail)) {
151
+ return undefined
152
+ }
153
+ return splitParams(decl.slice(open + 1, close)).map(paramName).filter(Boolean)
154
+ }
155
+
156
+ /** 检查一段文本,返回问题列表;每个问题带 1 起始的行号。 */
157
+ export function checkText(text, options = {}) {
158
+ const width = options.width ?? 80
159
+ const bannerWidth = options.bannerWidth ?? 80
160
+ const requireHeader = options.requireHeader ?? true
161
+ const lines = text.split(/\r?\n/)
162
+ const issues = []
163
+ const add = (line, rule, message) => issues.push({ line, rule, message })
164
+
165
+ const firstContent = lines.findIndex((line) => line.trim() !== '')
166
+ if (requireHeader && firstContent !== -1 && !BANNER.test(lines[firstContent].trim())) {
167
+ add(firstContent + 1, 'header-missing', '文件开头缺少文件头横幅(//===...===//)')
168
+ }
169
+
170
+ for (let index = 0; index < lines.length; index += 1) {
171
+ const line = lines[index]
172
+ const trimmed = line.trim()
173
+ if (BANNER.test(trimmed)) {
174
+ const actual = displayWidth(trimmed)
175
+ if (actual !== bannerWidth) add(index + 1, 'banner-width', `横幅宽度 ${actual} 列,应为 ${bannerWidth} 列`)
176
+ continue
177
+ }
178
+ if (trimmed.startsWith('///') || (trimmed.startsWith('//') && !trimmed.startsWith('//!'))) {
179
+ if (displayWidth(trimmed) > width) add(index + 1, 'comment-width', `注释行 ${displayWidth(trimmed)} 列,超过 ${width} 列`)
180
+ for (const tag of FORBIDDEN_TAGS) {
181
+ if (trimmed.includes(tag)) add(index + 1, 'forbidden-tag', `禁止使用 ${tag},按 references/comments.md 的标签表改写`)
182
+ }
183
+ }
184
+ }
185
+
186
+ for (let index = 0; index < lines.length; index += 1) {
187
+ if (!lines[index].trim().startsWith('///')) continue
188
+ if (lines[index].trim().startsWith('///<')) continue
189
+ const start = index
190
+ let end = index
191
+ while (end + 1 < lines.length && lines[end + 1].trim().startsWith('///') && !lines[end + 1].trim().startsWith('///<')) {
192
+ end += 1
193
+ }
194
+ index = end
195
+ let next = end + 1
196
+ if (next < lines.length && lines[next].trim() === '') {
197
+ add(end + 1, 'doc-gap', '注释与声明之间有空行:注释会挂到下一个声明,请删掉空行')
198
+ while (next < lines.length && lines[next].trim() === '') next += 1
199
+ }
200
+ if (next >= lines.length) continue
201
+ const declaration = lines[next].trim()
202
+ const block = lines.slice(start, end + 1).join('\n')
203
+ const isType = /^(template\s*<[^>]*>\s*)?(class|struct|union|enum)\b/.test(declaration)
204
+ if (RATIONALE.test(block)) {
205
+ add(start + 1, 'rationale', '注释在写设计取舍:方案对比与理由移到正文或实现注释,接口注释只留契约')
206
+ }
207
+ if (isType && MEMBER_LIST.test(block)) {
208
+ add(start + 1, 'member-list', '类型注释在罗列成员功能,应改写为概念与使用约定')
209
+ }
210
+ if (isType && FORWARD_REF.test(block)) {
211
+ add(start + 1, 'forward-ref', '类型注释在交代别的类型负责什么:只写本类型的契约,跨文件边界留给文件头')
212
+ }
213
+ if (isType && TYPE_SYSTEM.test(block)) {
214
+ add(start + 1, 'type-system', '类型注释在复述类型系统已表达的性质:可拷贝与 const 查询由声明给出,删掉这句')
215
+ }
216
+ const params = functionParams(declaration)
217
+ if (!params) continue
218
+ const documented = [...block.matchAll(/@param(?:\[[^\]]*\])?\s+(\w+)/g)].map((match) => match[1])
219
+ if (documented.length === 0) continue
220
+ for (const name of params) {
221
+ if (!documented.includes(name)) add(start + 1, 'param-missing', `参数 ${name} 没有 @param 说明`)
222
+ }
223
+ for (const name of documented) {
224
+ if (!params.includes(name)) add(start + 1, 'param-extra', `@param ${name} 在声明里不存在`)
225
+ }
226
+ }
227
+
228
+ for (let index = 0; index < lines.length; index += 1) {
229
+ const trimmed = lines[index].trim()
230
+ if (!/^(template\s*<[^>]*>\s*)?enum\b/.test(trimmed)) continue
231
+ let body = index
232
+ while (body < lines.length && !lines[body].includes('{')) body += 1
233
+ if (body >= lines.length) continue
234
+ let commented = 0
235
+ let bare = 0
236
+ for (let cursor = body + 1; cursor < lines.length; cursor += 1) {
237
+ const line = lines[cursor].trim()
238
+ if (line.startsWith('}')) break
239
+ if (line === '' || line.startsWith('//') || line.startsWith('#')) continue
240
+ const code = line.replace(/\/\/.*$/, '').trim()
241
+ if (/^[A-Za-z_]\w*\s*(=[^,]+)?,?$/.test(code)) {
242
+ if (line.includes('///<')) commented += 1
243
+ else bare += 1
244
+ }
245
+ }
246
+ if (commented > 0 && bare > 0) {
247
+ add(index + 1, 'enum-partial', '枚举成员注释需全写或全不写,当前只有部分成员有 ///<')
248
+ }
249
+ index = body
250
+ }
251
+
252
+ return issues.sort((left, right) => left.line - right.line)
253
+ }
254
+
255
+ /** 递归收集源文件,跳过生成目录与隐藏目录。 */
256
+ export function collectFiles(root) {
257
+ const files = []
258
+ const walk = (dir) => {
259
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
260
+ const full = path.join(dir, entry.name)
261
+ if (entry.isDirectory()) {
262
+ if (SKIP_DIR.has(entry.name.toLowerCase()) || entry.name.startsWith('.')) continue
263
+ walk(full)
264
+ continue
265
+ }
266
+ if (entry.isFile() && SOURCE_EXT.has(path.extname(entry.name).toLowerCase())) files.push(full)
267
+ }
268
+ }
269
+ if (statSync(root).isFile()) return [root]
270
+ walk(root)
271
+ return files.sort()
272
+ }
273
+
274
+ function main(argv) {
275
+ let options
276
+ try {
277
+ options = parseArgs(argv)
278
+ } catch (error) {
279
+ return usage(error.message)
280
+ }
281
+ if (options.help) return usage()
282
+ const root = options.root ? path.resolve(options.root) : process.cwd()
283
+ let files
284
+ try {
285
+ files = collectFiles(root)
286
+ } catch (error) {
287
+ return usage(`读取 ${root} 失败:${error.message}`)
288
+ }
289
+ const results = []
290
+ for (const file of files) {
291
+ let text
292
+ try {
293
+ text = readFileSync(file, 'utf8')
294
+ } catch (error) {
295
+ process.stderr.write(`错误:读取 ${file} 失败:${error.message}\n`)
296
+ return EXIT.usage
297
+ }
298
+ for (const issue of checkText(text, options)) results.push({ file: path.relative(root, file) || file, ...issue })
299
+ }
300
+ if (options.json) {
301
+ process.stdout.write(`${JSON.stringify({ root, files: files.length, issues: results }, null, 2)}\n`)
302
+ } else if (results.length > 0) {
303
+ for (const issue of results) process.stdout.write(`${issue.file}:${issue.line}: [${issue.rule}] ${issue.message}\n`)
304
+ const touched = new Set(results.map((issue) => issue.file)).size
305
+ process.stdout.write(`发现 ${results.length} 处注释问题,涉及 ${touched} 个文件。\n`)
306
+ } else if (!options.quiet) {
307
+ process.stdout.write(`注释检查通过:${files.length} 个文件。\n`)
308
+ }
309
+ return results.length === 0 ? EXIT.ok : EXIT.issues
310
+ }
311
+
312
+ if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
313
+ process.exitCode = main(process.argv.slice(2))
314
+ }
315
+
316
+ export { HEADER_EXT }
@@ -16,7 +16,7 @@
16
16
  - 一次只定一个可验收的切片。切片太大就拆成两个行为句,分别走完四步。
17
17
  - 失败方式也要写:非法参数、空输入、越界、文件不存在时,是返回错误值、打印提示,还是抛异常。
18
18
  - 行为句不绑定实现。「用 `std::vector` 保存」是声明或实现的决定,不是行为。
19
- - 需要新增输出时,组合消息用 `std::format`;不要为了打印引入 C++23 的 `std::print`。
19
+ - 需要新增输出时,组合消息用 `std::format`;`std::print` 只在 C++23 且是程序边界(`apps/`、`main`)时可用,库代码不直接打印。
20
20
 
21
21
  ## 情况
22
22
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  ```text
8
8
  <component>/include/<name>.h 公共接口
9
- <component>/src/<name>.cc 实现
9
+ <component>/src/<name>.cpp 实现
10
10
  tests/ 测试
11
11
  app 只做参数、输入输出和装配
12
12
  ```
@@ -16,7 +16,7 @@ app 只做参数、输入输出和装配
16
16
  - 库 target 不依赖终端、GUI、网络脚本或全局单例;它只接收值、返回值、在无法完成任务时抛异常。
17
17
  - 程序 target 链接库,负责读参数、读标准输入、打印结果、把退出码翻译成进程退出码。
18
18
  - 公共头文件直接放在组件的 `include/` 下,从仓库根写完整路径包含,例如 `#include "task_list/include/task_list.h"`;不要写裸文件名或 `../` 相对路径。
19
- - 依赖方向从程序指向库。库不反向包含程序的 `cli.h`,也不引用 `main.cc`。
19
+ - 依赖方向从程序指向库。库不反向包含程序的 `cli.h`,也不引用 `main.cpp`。
20
20
  - 测试放 `tests/`,测试 target 链接被测库;测试不复制库的实现。
21
21
  - 只有一个可执行文件、没有复用需求时,可以不分库;一旦出现第二个调用方或测试需要直接调用,再拆库。
22
22
  - 抽共享库前先确认第二个真实使用方和相同语义;只是“看起来会复用”不构成理由。抽象理由写进项目技能或设计注释。
@@ -25,11 +25,11 @@ app 只做参数、输入输出和装配
25
25
 
26
26
  ## 情况
27
27
 
28
- **单文件程序**:`src/main.cc` 可以有 `main()` 和私有辅助函数;一旦辅助逻辑需要测试,把它移到库组件。
28
+ **单文件程序**:`src/main.cpp` 可以有 `main()` 和私有辅助函数;一旦辅助逻辑需要测试,把它移到库组件。
29
29
 
30
30
  **领域核心 + 外围**:规则核心(状态、合法性、结果)只依赖标准和领域类型;终端、GUI、Web、脚本都在适配层。依赖方向从外向内,核心不知道适配层存在。
31
31
 
32
- **库 + 程序**:库放在 `<component>/`,程序放在 `app/` 或根 `src/main.cc`;程序只包含库的公共头。公共头只暴露稳定契约,实现细节放 `.cc` 或私有头。
32
+ **库 + 程序**:库放在 `<component>/`,程序放在 `app/` 或根 `src/main.cpp`;程序只包含库的公共头。公共头只暴露稳定契约,实现细节放 `.cpp` 或私有头。
33
33
 
34
34
  **静态库 / 动态库**:接口相同,差别在链接方式和运行时库路径;都遵守「库不依赖终端」。选哪种由 `building-cpp` 的定构建决定。
35
35
 
@@ -2,18 +2,18 @@
2
2
 
3
3
  ## 产出
4
4
 
5
- 公共头文件、类型与函数声明、命名、声明顺序和声明注释。声明要能让调用方在不读 `.cc` 的情况下知道怎么用、什么时候失败。
5
+ 公共头文件、类型与函数声明、命名、声明顺序和声明注释。声明要能让调用方在不读 `.cpp` 的情况下知道怎么用、什么时候失败。
6
6
 
7
7
  ## 通用情况
8
8
 
9
- - 语言标准由项目声明决定,新工程默认 C++23,支持 C++11–C++23;头文件用 `#define` 守卫,名字由源码树全路径生成。
9
+ - 语言标准由项目声明决定,新工程默认 C++23,支持 C++11–C++23;头文件用 `#define` 守卫,名字由源码树全路径生成,不要用 `#pragma once`。槽位与守卫名按 `references/file-template.md`。
10
10
  - 命名按 `references/naming.md`:类型与函数 PascalCase,变量与参数 snake_case,类成员尾随 `_`、结构体成员不尾随,常量与枚举子 `k` 前缀。布尔查询与 `Check` 的分工见同文件「谓词与查询命名」。
11
11
  - 声明顺序按 `references/declaration-order.md`:类型 → 静态常量 → 工厂 → 构造/赋值/析构 → 其他函数 → 数据成员。
12
12
  - 公共头文件自包含:自身需要的头都直接包含,不依赖调用方的包含顺序。
13
13
  - 单参数构造函数写 `explicit`;可查询的成员函数加 `[[nodiscard]]`;默认成员初始化写在声明处,不在构造函数里重复。
14
- - 头文件只就地定义大约 10 行以内的短函数;更长或需要完整定义的普通函数只在 `.cc` 定义,模板和 `constexpr` 例外。
14
+ - 头文件只就地定义大约 10 行以内的短函数;更长或需要完整定义的普通函数只在 `.cpp` 定义,模板和 `constexpr` 例外。
15
15
  - 失败是调用方的正常结果时,用结果类型、`bool` + 输出参数或 `std::optional` 表达;函数无法完成自己的任务、或构造函数无法建立 invariant 时,才在声明注释里写清抛出的异常类型。判据见 `references/exceptions.md`。
16
- - 声明注释写契约:参数范围、返回值、失败方式、所有权。不要写实现步骤。
16
+ - 声明注释按 `references/comments.md` 分四层写:文件头的范围与边界、类型注释的概念与使用约定、函数注释的承诺与契约、数据成员的行尾说明。类型注释不罗列成员功能、不写方案对比与设计论证;全项目的风格理由写进项目文档,不写进注释。
17
17
  - `std::format` 用于组合消息;声明里不要承诺 `std::print`(C++23)。
18
18
 
19
19
  ## 情况
@@ -24,7 +24,7 @@
24
24
 
25
25
  **异常声明**:构造函数、`Get` 类查询、解析函数最容易抛;在声明注释里写「当……时抛出 `TaskIndexError`」这样的具体类型,不要只写「可能抛异常」,也不要机械地写成 `std::invalid_argument` / `std::out_of_range`。类型选择见 `references/exceptions.md`。
26
26
 
27
- **注释**:声明注释用 `///`,只写调用方需要知道的;实现细节留给 `.cc`。文件头、章节横幅和 TODO 的格式见 `references/comments.md`。
27
+ **注释**:声明注释用 `///` 写中文散文,只写调用方需要知道的;实现细节留给 `.cpp`。`@param`、`@return`、`@throws` 要么全写要么全不写,标识符和标签保持英文。写完运行 `node "<skill-dir>/scripts/checking-comments.mjs" --root <项目根>`,横幅、标签、参数完整性、成员清单与设计论证由脚本兜底。文件头、章节横幅和 TODO 的格式见 `references/comments.md`。
28
28
 
29
29
  ## 正反例指针
30
30
 
@@ -2,14 +2,14 @@
2
2
 
3
3
  ## 产出
4
4
 
5
- `.cc` 中的函数体、操作顺序、分组空白和实现注释。实现只兑现第 1 步的行为和第 3 步的声明,不新增公共接口。
5
+ `.cpp` 实现文件中的函数体、操作顺序、分组空白和实现注释。实现只兑现第 1 步的行为和第 3 步的声明,不新增公共接口。
6
6
 
7
7
  ## 通用情况
8
8
 
9
9
  - 函数保持短小;一个函数只做一件事。超过约 40 行时先找可以命名的中间步骤,不要用注释把长函数切成几段。
10
10
  - 按「准备 → 操作 → 收尾」排列语句,用空行分组;组与组之间的空行表达阶段,不用大段叙述。
11
- - `.cc` 第一个 include 是自己的配对头,然后直接包含本文件用到的标准库头;不要依赖公共头替本文件转发声明。包含顺序见 `references/google-cpp-style.md`。
12
- - 实现注释只写「为什么这样做」「这里的边界条件」「这样排序的代价」,不复述代码。
11
+ - `.cpp` 的第一个 include 是自己的配对头,然后直接包含本文件用到的标准库头;不要依赖公共头替本文件转发声明。包含顺序见 `references/google-cpp-style.md`;文件槽位见 `references/file-template.md`。
12
+ - 实现注释只写「为什么这样做」「这里的边界条件」「这样排序的代价」,1–3 行,放语句上方;不复述代码,不在 `if` 与 `else` 之间挂注释,记录变更史的内容留给提交说明。
13
13
  - 错误处理:失败是调用方正常结果时返回结果类型、`bool` 或 `std::optional`;函数无法完成自己的任务时抛异常,不拿异常做正常分支。捕获时按 `const` 引用,只处理能处理的类型,其余继续抛。
14
14
  - 组合输出用 `std::format`,再写入 `std::cout` / `std::cerr`;不要在库实现里直接打印。C++23 且库支持时程序边界可用 `std::print`,C++20 及更早没有。
15
15
  - 不写 C 风格强制转换;用 `static_cast` / `std::bit_cast` 等具名转换。