@chengzhao-dev/dsh-cpp-workflow 0.2.0 → 0.3.0
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 +2 -2
- package/README.md +2 -2
- package/package.json +3 -2
- package/skills/building-cpp/SKILL.md +2 -0
- package/skills/building-cpp/assets/config/.clang-format +2 -0
- package/skills/building-cpp/references/encoding.md +49 -0
- package/skills/building-cpp/references/standards/c++23/README.md +1 -1
- package/skills/building-cpp/scripts/checking-encoding.mjs +241 -0
- package/skills/building-cpp/steps/03-check.md +8 -6
- package/skills/developing-cpp/SKILL.md +2 -0
- package/skills/developing-cpp/examples/comments.md +29 -0
- package/skills/developing-cpp/references/comments.md +18 -0
- package/skills/developing-cpp/references/naming.md +8 -0
- package/skills/developing-cpp/references/project-design.md +9 -0
- package/skills/developing-cpp/steps/02-boundary.md +1 -0
- package/skills/developing-cpp/steps/03-declaration.md +1 -1
package/README.en.md
CHANGED
|
@@ -18,13 +18,13 @@ The exception material follows the C++ Core Guidelines' Error handling section a
|
|
|
18
18
|
Pinned npm install:
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.
|
|
21
|
+
dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.3.0
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
The matching Git tag:
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.
|
|
27
|
+
dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.3.0
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
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.
|
package/README.md
CHANGED
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
npm 安装钉死版本:
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.
|
|
21
|
+
dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.3.0
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
同版本的 Git 标签:
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.
|
|
27
|
+
dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.3.0
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
桌面应用独占 `desktop` profile。命令行 `add` 被拒绝时,在应用设置里添加插件,地址填同一个 npm 版本。Git 标签安装使用仓库内已构建的 `lib/`,不在目标机器上重新构建;包内也不含需要授权执行的安装脚本。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chengzhao-dev/dsh-cpp-workflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "C++11–C++23 工程流程技能包:源码四步与构建三步,按项目声明的环境组合校验 · C++11–C++23 workflow skills for DeepSeek Harness",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"icon": "./icon.svg",
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsc -p tsconfig.json",
|
|
45
|
-
"test": "npm run build && node scripts/checking-skill-links.mjs && node --test tests/*.test.mjs",
|
|
45
|
+
"test": "npm run build && node scripts/checking-skill-links.mjs && node skills/building-cpp/scripts/checking-encoding.mjs --quiet && node --test tests/*.test.mjs",
|
|
46
|
+
"check:encoding": "node skills/building-cpp/scripts/checking-encoding.mjs",
|
|
46
47
|
"test:matrix": "bash scripts/checking-standard-matrix.sh",
|
|
47
48
|
"pack:check": "npm pack --dry-run"
|
|
48
49
|
},
|
|
@@ -13,6 +13,8 @@ metadata:
|
|
|
13
13
|
|
|
14
14
|
编译失败先分因:诊断指向函数体,回 `developing-cpp` 的定实现;诊断指向声明或类型,回定声明;诊断是找不到头、链接失败、没有编译数据库或工具不在 PATH,留在本技能的定构建或定工具配置。
|
|
15
15
|
|
|
16
|
+
源码和构建文件用 UTF-8 无 BOM、检出 LF;`.bat` / `.cmd` 用 CRLF;`.clang-format` 写 `LineEnding: LF`。MSVC 原生构建优先加 `/utf-8`,不要靠 BOM 过关。完整规则见 `references/encoding.md`,检查脚本是 `scripts/checking-encoding.mjs`,机器规则以仓库根 `.gitattributes` 与 `.editorconfig` 为准。
|
|
17
|
+
|
|
16
18
|
存在项目技能 `cpp-workflow-project` 时先读它的 `SKILL.md`、`environment.json` 和 `stages/stage-table.md`。三个环境轴为空、`standard` 非法或组合非法时停在定构建,不猜 WSL、Clang 或构建目录;`windows` + `native` 直接拒绝,提示「本版本不能在 Windows 原生壳里构建」,不要给出补 PowerShell 的路线。环境组合空着时源码四步仍可做,只是不能构建。
|
|
17
19
|
|
|
18
20
|
## 两种走法
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# 编码与行尾
|
|
2
|
+
|
|
3
|
+
默认组合是 UTF-8 无 BOM、LF。机器规则以仓库根 `.gitattributes` 与 `.editorconfig` 为准,本文件只记决定、MSVC 例外与检查方法。
|
|
4
|
+
|
|
5
|
+
## 规则
|
|
6
|
+
|
|
7
|
+
- 文本文件用 UTF-8,不带 BOM。JSON 按 RFC 8259;YAML 按 YAML 1.2.2;不要 GBK、UTF-16 或 Latin-1。
|
|
8
|
+
- 所有文本文件用 LF,包括 `.sh`、Python、C++、Markdown、YAML、CMake 和配置文件。
|
|
9
|
+
- `.sh` 必须 LF:内核会把 shebang 末尾的 CR 当作解释器路径的一部分,导致 `bad interpreter`。
|
|
10
|
+
- `.bat` / `.cmd` 必须 CRLF:`cmd.exe` 对纯 LF 批处理不稳定。
|
|
11
|
+
- 每个文本文件末尾一个换行;文件中间不混用 LF 和 CRLF。
|
|
12
|
+
- 二进制文件(字体、图片)不转码,按 `binary` 处理。
|
|
13
|
+
|
|
14
|
+
## MSVC 原生
|
|
15
|
+
|
|
16
|
+
MSVC 默认按 BOM 判断源码编码。源码没有 BOM 时,它按当前用户代码页解释,除非显式传入 `/utf-8` 或 `/source-charset:utf-8`。因此 BOM 不是 MSVC 的硬要求。
|
|
17
|
+
|
|
18
|
+
- 首选做法是加编译选项:在目标上写 `target_compile_options(<target> PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/utf-8>)`,源码继续 UTF-8 无 BOM、LF。
|
|
19
|
+
- 只有在无法改编译选项时,才把 MSVC 编译的 C/C++ 源文件保存为 UTF-8 BOM。BOM 只允许出现在这类源文件里。
|
|
20
|
+
- `.sh`、`.bat`、`.cmd`、`.json`、`.yml`、`.gitattributes`、`.editorconfig` 和 CMake 文件不要加 BOM。
|
|
21
|
+
- MSVC 接受 LF 源码;CRLF 不是必需项。只有 `.bat` / `.cmd` 固定 CRLF。
|
|
22
|
+
- WSL 与 Linux 构建按默认组合,不要把源码转成 CRLF 来迁就 Windows。
|
|
23
|
+
|
|
24
|
+
## 工具
|
|
25
|
+
|
|
26
|
+
- `.gitattributes` 决定 Git 入库和检出;它覆盖贡献者自己的 `core.autocrlf`。
|
|
27
|
+
- `.editorconfig` 决定编辑器保存;`charset` 与 `end_of_line` 必须和 `.gitattributes` 同一档。
|
|
28
|
+
- `.clang-format` 写 `LineEnding: LF`,避免格式化把行尾改回 CRLF。
|
|
29
|
+
|
|
30
|
+
## 检查
|
|
31
|
+
|
|
32
|
+
在仓库根运行:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
node "<skill-dir>/scripts/checking-encoding.mjs" --profile portable
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- `portable`(默认)要求所有文本文件 UTF-8 无 BOM、LF、末尾换行。
|
|
39
|
+
- `msvc` 只放宽 MSVC 编译的 C/C++ 源文件:允许 UTF-8 BOM 与 CRLF;Markdown、配置与脚本仍按 `portable` 检查。
|
|
40
|
+
- 退出码:0 无问题,1 有问题,2 参数或 Git 错误。加 `--json` 输出机器可读结果。
|
|
41
|
+
|
|
42
|
+
再跑 `git diff --check`,确认没有空白错误。不要用 `git add --renormalize` 把行尾改动混进功能改动;确需规范化时单独提交并说明。
|
|
43
|
+
|
|
44
|
+
## 依据
|
|
45
|
+
|
|
46
|
+
- MSVC `/utf-8`:<https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170>
|
|
47
|
+
- MSVC `/source-charset`:<https://learn.microsoft.com/en-us/cpp/build/reference/source-charset-set-source-character-set?view=msvc-170>
|
|
48
|
+
- Unicode UTF-8 BOM FAQ:<https://www.unicode.org/faq/utf_bom.html>
|
|
49
|
+
- Git `.gitattributes`:<https://git-scm.com/docs/gitattributes>
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
|
|
44
44
|
## 降档/升级
|
|
45
45
|
|
|
46
|
-
从 C++23 降到 C++20 见 [C++20 档案](../c++20/README.md)。升级到 C++26 前先确认 CMake 3.25+、编译器/标准库支持、clang-format 的 `c++26` 枚举;新增目录流程见[标准索引](../README.md#新增标准)。
|
|
46
|
+
从 C++23 降到 C++20 见 [C++20 档案](../c++20/README.md)。升级到 C++26 前先确认 CMake 3.25+、编译器/标准库支持、clang-format 的 `c++26` 枚举;新增目录流程见 [标准索引](../README.md#新增标准)。
|
|
47
47
|
|
|
48
48
|
## 验证
|
|
49
49
|
|
|
@@ -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
|
+
}
|
|
@@ -8,15 +8,17 @@
|
|
|
8
8
|
|
|
9
9
|
按固定顺序跑,前一步失败就停,不要带着失败继续:
|
|
10
10
|
|
|
11
|
-
1.
|
|
12
|
-
2.
|
|
13
|
-
3.
|
|
14
|
-
4.
|
|
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
|
```
|
|
@@ -13,6 +13,8 @@ metadata:
|
|
|
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
|
| 走法 | 什么时候用 | 怎么做 |
|
|
@@ -231,3 +231,32 @@ return true;
|
|
|
231
231
|
```
|
|
232
232
|
|
|
233
233
|
一段连续的实现注释只讲一个判断或一个取舍;不是逐行字幕。
|
|
234
|
+
|
|
235
|
+
## 成员还是同一命名空间的函数
|
|
236
|
+
|
|
237
|
+
**差**:
|
|
238
|
+
|
|
239
|
+
```cpp
|
|
240
|
+
class TaskTable {
|
|
241
|
+
public:
|
|
242
|
+
std::size_t CountOverdue() const;
|
|
243
|
+
bool HasOverdue() const { return CountOverdue() > 0; }
|
|
244
|
+
};
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
`HasOverdue` 只组合公开查询,不需要直接访问表示,却做成了成员。
|
|
248
|
+
|
|
249
|
+
**好**:
|
|
250
|
+
|
|
251
|
+
```cpp
|
|
252
|
+
class TaskTable {
|
|
253
|
+
public:
|
|
254
|
+
std::size_t CountOverdue() const;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
bool HasOverdue(const TaskTable& table) {
|
|
258
|
+
return table.CountOverdue() > 0;
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
函数和类放在同一命名空间;调用方只看到 `HasOverdue(table)`,类不增加只为转发而存在的成员。
|
|
@@ -117,6 +117,24 @@ Standard: Latest
|
|
|
117
117
|
- 同一个理由只写一次,不逐项复制。
|
|
118
118
|
- 源码注释不写团队政策或流程;那些属于文档,不属于被阅读的代码。
|
|
119
119
|
|
|
120
|
+
## 润色次序
|
|
121
|
+
|
|
122
|
+
只改注释时,仍走第 3 步和第 4 步,然后按这个次序回读:
|
|
123
|
+
|
|
124
|
+
1. 文件头:这个文件负责什么、不负责什么,6 行以内。
|
|
125
|
+
2. 章节横幅:文件超过约 200 行才分组。
|
|
126
|
+
3. 声明契约:`///` 写参数、返回值、失败方式;与 `references/exceptions.md` 一致。
|
|
127
|
+
4. 成员行尾:先写保存的对象,再写约束。
|
|
128
|
+
5. 实现注释:只写为什么和边界,不翻译下一行。
|
|
129
|
+
6. 配置注释:按 `building-cpp` 的定工具写;只写为什么这样设。
|
|
130
|
+
7. 回读:同一概念一种译法;不把读者章节的句子抄进注释。
|
|
131
|
+
|
|
132
|
+
## 与读者正文的边界
|
|
133
|
+
|
|
134
|
+
- 中文、术语稳定、不复述下一行;两边各自写自己的文件。
|
|
135
|
+
- 句长、段落、标点、书根 README 不在本插件。
|
|
136
|
+
- `///`、横幅、`@param` 不进入 qmd、YAML、CSS。
|
|
137
|
+
|
|
120
138
|
## 禁止
|
|
121
139
|
|
|
122
140
|
- 注释里写外部资料路径、项目专属类名或跨仓库引用。
|
|
@@ -44,6 +44,14 @@
|
|
|
44
44
|
- 修改器用 `Set` + 属性名(`SetCount()` / `set_count()`)或领域动作(`CompleteTask()`);同一 API 只选一种,不要用 `SetItem` 这类暴露存储细节的名字。
|
|
45
45
|
- `current_` 前缀只在有多个候选、且「当前」可能混淆时使用:`current_order_`。只有一个当前对象时不要加。
|
|
46
46
|
|
|
47
|
+
## 谓词与查询命名
|
|
48
|
+
|
|
49
|
+
- 返回 `bool` 的查询用肯定式,读起来像一句断言:`IsEmpty()`、`HasStock()`、`CanReserve()`、`CompletesLine()`。
|
|
50
|
+
- `Get` 只用于取值,`Set` 只用于设值;不要用 `CheckLine()` 当布尔 getter。
|
|
51
|
+
- `Check` / `Validate` 留给会报告错误、产生诊断或可能失败的操作;它们不返回纯 `bool` 查询。
|
|
52
|
+
- 假设性查询在名字里写明条件:`CompletesLine(position, cell)` 读作「这一手会成线吗」;需要更强语气时用 `WouldCompleteLine()`。同一 API 只选一种,改名时同步声明、定义、调用点、测试和注释。
|
|
53
|
+
- 私有谓词同样用肯定式;不要用 `flag`、`status` 或 `check` 这类不说明判据的名字。
|
|
54
|
+
|
|
47
55
|
## 类型命名的判断顺序
|
|
48
56
|
|
|
49
57
|
1. 纯数据、无 invariant、可以直接聚合初始化 → `struct`,名字是名词:`Order`, `TaskRecord`。
|
|
@@ -44,6 +44,15 @@
|
|
|
44
44
|
|
|
45
45
|
依赖方向从外向内;核心不知道适配层存在。
|
|
46
46
|
|
|
47
|
+
## 谁回答这个问题
|
|
48
|
+
|
|
49
|
+
判断一个操作该落在哪个类型,先看回答它需要哪些数据:
|
|
50
|
+
|
|
51
|
+
- 需要的数据在哪个类型里,就由那个类型回答(Information Expert)。
|
|
52
|
+
- 只有直接碰到内部表示的函数才做成成员;只组合公开查询的函数留在同一命名空间(C.4、C.5)。调用方不另写一份。
|
|
53
|
+
- 例外是虚函数、`=`、`()`、`[]`、`->` 这类必须为成员的运算符,以及同一组重载或链式调用里为了接口一致而留下的函数。使用例外时在声明旁写一句原因。
|
|
54
|
+
- 示例不指向任何仓库的类:一次落子仍由对局类型接收,因为它掌握当前参与者、历史和结果;它不重新计算棋盘已经能回答的占格、成线和终局。棋盘类型不包含参与者类型和结果类型。
|
|
55
|
+
|
|
47
56
|
## 边界的划分理由
|
|
48
57
|
|
|
49
58
|
按“变化原因”划分边界,不按文件数量:
|
|
@@ -21,6 +21,7 @@ app 只做参数、输入输出和装配
|
|
|
21
21
|
- 只有一个可执行文件、没有复用需求时,可以不分库;一旦出现第二个调用方或测试需要直接调用,再拆库。
|
|
22
22
|
- 抽共享库前先确认第二个真实使用方和相同语义;只是“看起来会复用”不构成理由。抽象理由写进项目技能或设计注释。
|
|
23
23
|
- 第 2 步不写 `CMakeLists.txt`。只决定边界,构建 target 留给 `building-cpp` 的定构建。
|
|
24
|
+
- 判断一个查询该成为成员还是同一命名空间的函数时,按 `references/project-design.md` 的「谁回答这个问题」:掌握数据的一方回答,只组合公开查询的函数不升级为成员。
|
|
24
25
|
|
|
25
26
|
## 情况
|
|
26
27
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
## 通用情况
|
|
8
8
|
|
|
9
9
|
- 语言标准由项目声明决定,新工程默认 C++23,支持 C++11–C++23;头文件用 `#define` 守卫,名字由源码树全路径生成。
|
|
10
|
-
- 命名按 `references/naming.md`:类型与函数 PascalCase,变量与参数 snake_case,类成员尾随 `_`、结构体成员不尾随,常量与枚举子 `k`
|
|
10
|
+
- 命名按 `references/naming.md`:类型与函数 PascalCase,变量与参数 snake_case,类成员尾随 `_`、结构体成员不尾随,常量与枚举子 `k` 前缀。布尔查询与 `Check` 的分工见同文件「谓词与查询命名」。
|
|
11
11
|
- 声明顺序按 `references/declaration-order.md`:类型 → 静态常量 → 工厂 → 构造/赋值/析构 → 其他函数 → 数据成员。
|
|
12
12
|
- 公共头文件自包含:自身需要的头都直接包含,不依赖调用方的包含顺序。
|
|
13
13
|
- 单参数构造函数写 `explicit`;可查询的成员函数加 `[[nodiscard]]`;默认成员初始化写在声明处,不在构造函数里重复。
|