code-yangzz 1.0.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.md +102 -0
- package/agents/meta-artisan.md +164 -0
- package/agents/meta-conductor.md +482 -0
- package/agents/meta-genesis.md +165 -0
- package/agents/meta-librarian.md +213 -0
- package/agents/meta-prism.md +268 -0
- package/agents/meta-scout.md +173 -0
- package/agents/meta-sentinel.md +161 -0
- package/agents/meta-warden.md +304 -0
- package/bin/install.js +390 -0
- package/bin/lib/utils.js +72 -0
- package/bin/lib/watermark.js +176 -0
- package/config/CLAUDE.md +363 -0
- package/config/settings.json +120 -0
- package/hooks/block-dangerous-bash.mjs +36 -0
- package/hooks/post-console-log-warn.mjs +27 -0
- package/hooks/post-format.mjs +24 -0
- package/hooks/post-typecheck.mjs +27 -0
- package/hooks/pre-git-push-confirm.mjs +19 -0
- package/hooks/stop-completion-guard.mjs +159 -0
- package/hooks/stop-console-log-audit.mjs +44 -0
- package/hooks/subagent-context.mjs +27 -0
- package/hooks/user-prompt-submit.js +233 -0
- package/package.json +36 -0
- package/prompt-optimizer/prompt-optimizer-meta.md +159 -0
- package/skills/agent-teams/SKILL.md +215 -0
- package/skills/domains/ai/SKILL.md +34 -0
- package/skills/domains/ai/agent-dev.md +242 -0
- package/skills/domains/ai/llm-security.md +288 -0
- package/skills/domains/ai/prompt-and-eval.md +279 -0
- package/skills/domains/ai/rag-system.md +542 -0
- package/skills/domains/architecture/SKILL.md +42 -0
- package/skills/domains/architecture/api-design.md +225 -0
- package/skills/domains/architecture/caching.md +298 -0
- package/skills/domains/architecture/cloud-native.md +285 -0
- package/skills/domains/architecture/message-queue.md +328 -0
- package/skills/domains/architecture/security-arch.md +297 -0
- package/skills/domains/data-engineering/SKILL.md +207 -0
- package/skills/domains/development/SKILL.md +46 -0
- package/skills/domains/development/cpp.md +246 -0
- package/skills/domains/development/go.md +323 -0
- package/skills/domains/development/java.md +277 -0
- package/skills/domains/development/python.md +288 -0
- package/skills/domains/development/rust.md +313 -0
- package/skills/domains/development/shell.md +313 -0
- package/skills/domains/development/typescript.md +277 -0
- package/skills/domains/devops/SKILL.md +39 -0
- package/skills/domains/devops/cost-optimization.md +271 -0
- package/skills/domains/devops/database.md +217 -0
- package/skills/domains/devops/devsecops.md +198 -0
- package/skills/domains/devops/git-workflow.md +181 -0
- package/skills/domains/devops/observability.md +279 -0
- package/skills/domains/devops/performance.md +335 -0
- package/skills/domains/devops/testing.md +283 -0
- package/skills/domains/frontend-design/SKILL.md +38 -0
- package/skills/domains/frontend-design/agents/openai.yaml +4 -0
- package/skills/domains/frontend-design/claymorphism/SKILL.md +119 -0
- package/skills/domains/frontend-design/claymorphism/references/tokens.css +52 -0
- package/skills/domains/frontend-design/component-patterns.md +202 -0
- package/skills/domains/frontend-design/engineering.md +287 -0
- package/skills/domains/frontend-design/glassmorphism/SKILL.md +140 -0
- package/skills/domains/frontend-design/glassmorphism/references/tokens.css +32 -0
- package/skills/domains/frontend-design/liquid-glass/SKILL.md +137 -0
- package/skills/domains/frontend-design/liquid-glass/references/tokens.css +81 -0
- package/skills/domains/frontend-design/neubrutalism/SKILL.md +143 -0
- package/skills/domains/frontend-design/neubrutalism/references/tokens.css +44 -0
- package/skills/domains/frontend-design/state-management.md +680 -0
- package/skills/domains/frontend-design/ui-aesthetics.md +110 -0
- package/skills/domains/frontend-design/ux-principles.md +156 -0
- package/skills/domains/infrastructure/SKILL.md +200 -0
- package/skills/domains/mobile/SKILL.md +224 -0
- package/skills/domains/orchestration/SKILL.md +29 -0
- package/skills/domains/orchestration/multi-agent.md +263 -0
- package/skills/domains/security/SKILL.md +72 -0
- package/skills/domains/security/blue-team.md +436 -0
- package/skills/domains/security/code-audit.md +265 -0
- package/skills/domains/security/pentest.md +226 -0
- package/skills/domains/security/red-team.md +374 -0
- package/skills/domains/security/threat-intel.md +372 -0
- package/skills/domains/security/vuln-research.md +369 -0
- package/skills/meta-theory/SKILL.md +878 -0
- package/skills/meta-theory/references/create-agent.md +248 -0
- package/skills/meta-theory/references/dev-governance.md +1108 -0
- package/skills/meta-theory/references/intent-amplification.md +190 -0
- package/skills/meta-theory/references/meta-theory.md +346 -0
- package/skills/meta-theory/references/rhythm-orchestration.md +257 -0
- package/skills/meta-theory/references/ten-step-governance.md +285 -0
- package/skills/orchestration/multi-agent/SKILL.md +493 -0
- package/skills/run_skill.js +128 -0
- package/skills/tools/gen-docs/SKILL.md +115 -0
- package/skills/tools/gen-docs/agents/openai.yaml +4 -0
- package/skills/tools/gen-docs/scripts/doc_generator.js +435 -0
- package/skills/tools/lib/shared.js +98 -0
- package/skills/tools/override-refusal/SKILL.md +55 -0
- package/skills/tools/override-refusal/agents/openai.yaml +4 -0
- package/skills/tools/override-refusal/scripts/refusal_rewriter.js +229 -0
- package/skills/tools/verify-change/SKILL.md +139 -0
- package/skills/tools/verify-change/agents/openai.yaml +4 -0
- package/skills/tools/verify-change/scripts/change_analyzer.js +289 -0
- package/skills/tools/verify-module/SKILL.md +126 -0
- package/skills/tools/verify-module/agents/openai.yaml +4 -0
- package/skills/tools/verify-module/scripts/module_scanner.js +171 -0
- package/skills/tools/verify-quality/SKILL.md +159 -0
- package/skills/tools/verify-quality/agents/openai.yaml +4 -0
- package/skills/tools/verify-quality/scripts/quality_checker.js +337 -0
- package/skills/tools/verify-security/SKILL.md +142 -0
- package/skills/tools/verify-security/agents/openai.yaml +4 -0
- package/skills/tools/verify-security/scripts/security_scanner.js +283 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { execFileSync } = require("child_process");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const { parseCliArgs, buildReport, hasFatal, DASH } = require(path.join(__dirname, '..', '..', 'lib', 'shared.js'));
|
|
8
|
+
|
|
9
|
+
const CODE_EXT = new Set([".py",".go",".rs",".ts",".js",".jsx",".tsx",".java",".c",".cpp",".h",".hpp"]);
|
|
10
|
+
const DOC_EXT = new Set([".md",".rst",".txt",".adoc"]);
|
|
11
|
+
const TEST_PATTERNS = ["test_","_test.",".test.","spec_","_spec.","/tests/","/test/","/__tests__/"];
|
|
12
|
+
const CONFIG_FILES = new Set(["package.json","pyproject.toml","go.mod","cargo.toml","pom.xml","makefile","dockerfile"]);
|
|
13
|
+
const CONFIG_EXT = new Set([".yaml",".yml",".json",".toml",".ini"]);
|
|
14
|
+
|
|
15
|
+
function normalizePath(p) {
|
|
16
|
+
let s = p.trim();
|
|
17
|
+
if (s.startsWith('"') && s.endsWith('"') && s.length >= 2) {
|
|
18
|
+
s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
19
|
+
}
|
|
20
|
+
if (s.startsWith("./")) s = s.slice(2);
|
|
21
|
+
return s;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function classifyFile(filePath) {
|
|
25
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
26
|
+
const name = path.basename(filePath).toLowerCase();
|
|
27
|
+
const lower = filePath.toLowerCase();
|
|
28
|
+
return {
|
|
29
|
+
path: filePath, type: "modified", additions: 0, deletions: 0,
|
|
30
|
+
is_code: CODE_EXT.has(ext), is_doc: DOC_EXT.has(ext),
|
|
31
|
+
is_test: TEST_PATTERNS.some(p => lower.includes(p)),
|
|
32
|
+
is_config: CONFIG_FILES.has(name) || CONFIG_EXT.has(ext),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function parseNameStatusLine(line) {
|
|
37
|
+
const parts = line.split("\t");
|
|
38
|
+
if (parts.length < 2) return null;
|
|
39
|
+
const status = parts[0][0];
|
|
40
|
+
const p = normalizePath(parts[parts.length - 1]);
|
|
41
|
+
if (!p) return null;
|
|
42
|
+
const c = classifyFile(p);
|
|
43
|
+
const map = { A: "added", M: "modified", D: "deleted", R: "renamed" };
|
|
44
|
+
if (map[status]) c.type = map[status];
|
|
45
|
+
return c;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parsePorcelainLine(line) {
|
|
49
|
+
if (line.length < 3) return null;
|
|
50
|
+
const status = line.slice(0, 2);
|
|
51
|
+
let raw = line.slice(3);
|
|
52
|
+
if (!raw) return null;
|
|
53
|
+
if (raw.includes(" -> ")) raw = raw.split(" -> ")[1];
|
|
54
|
+
const p = normalizePath(raw);
|
|
55
|
+
if (!p) return null;
|
|
56
|
+
const c = classifyFile(p);
|
|
57
|
+
if (status.includes("?") || status.includes("A")) c.type = "added";
|
|
58
|
+
else if (status.includes("R")) c.type = "renamed";
|
|
59
|
+
else if (status.includes("M")) c.type = "modified";
|
|
60
|
+
else if (status.includes("D")) c.type = "deleted";
|
|
61
|
+
return c;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function git(...args) {
|
|
65
|
+
try { return execFileSync('git', args, { encoding: "utf8", stdio: ["pipe","pipe","pipe"] }); }
|
|
66
|
+
catch { return ""; }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getGitChanges(base = "HEAD~1", target = "HEAD") {
|
|
70
|
+
const changes = [];
|
|
71
|
+
for (const line of git('diff', '--name-status', base, target).split("\n")) {
|
|
72
|
+
if (!line) continue;
|
|
73
|
+
const c = parseNameStatusLine(line);
|
|
74
|
+
if (c) changes.push(c);
|
|
75
|
+
}
|
|
76
|
+
const statMap = {};
|
|
77
|
+
for (const line of git('diff', '--numstat', base, target).split("\n")) {
|
|
78
|
+
if (!line) continue;
|
|
79
|
+
const parts = line.split("\t");
|
|
80
|
+
if (parts.length >= 3) {
|
|
81
|
+
statMap[normalizePath(parts[2])] = [
|
|
82
|
+
parts[0] === "-" ? 0 : parseInt(parts[0], 10),
|
|
83
|
+
parts[1] === "-" ? 0 : parseInt(parts[1], 10),
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (const c of changes) {
|
|
88
|
+
if (statMap[c.path]) { [c.additions, c.deletions] = statMap[c.path]; }
|
|
89
|
+
}
|
|
90
|
+
return changes;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getStagedChanges() {
|
|
94
|
+
const changes = [];
|
|
95
|
+
for (const line of git('diff', '--cached', '--name-status').split("\n")) {
|
|
96
|
+
if (!line) continue;
|
|
97
|
+
const c = parseNameStatusLine(line);
|
|
98
|
+
if (c) changes.push(c);
|
|
99
|
+
}
|
|
100
|
+
return changes;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getWorkingChanges() {
|
|
104
|
+
const changes = [];
|
|
105
|
+
for (const line of git('status', '--porcelain').split("\n")) {
|
|
106
|
+
if (!line) continue;
|
|
107
|
+
const c = parsePorcelainLine(line);
|
|
108
|
+
if (c) changes.push(c);
|
|
109
|
+
}
|
|
110
|
+
return changes;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function isPathInModule(filePath, mod) {
|
|
114
|
+
const np = normalizePath(filePath);
|
|
115
|
+
if (mod === ".") return !np.includes("/");
|
|
116
|
+
return np === mod || np.startsWith(mod + "/");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function identifyModules(changes) {
|
|
120
|
+
const modules = new Set();
|
|
121
|
+
for (const c of changes) {
|
|
122
|
+
const np = normalizePath(c.path);
|
|
123
|
+
const parts = np.split("/").filter(Boolean);
|
|
124
|
+
if (parts.length === 1) { modules.add("."); continue; }
|
|
125
|
+
let found = false;
|
|
126
|
+
for (let i = 0; i < parts.length; i++) {
|
|
127
|
+
const mp = parts.slice(0, i + 1).join("/");
|
|
128
|
+
if (fs.existsSync(path.join(mp, "README.md")) || fs.existsSync(path.join(mp, "DESIGN.md"))) {
|
|
129
|
+
modules.add(mp); found = true; break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (!found && parts.length > 1) modules.add(parts[0]);
|
|
133
|
+
}
|
|
134
|
+
return modules;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function checkDocSync(changes, modules) {
|
|
138
|
+
const docStatus = {};
|
|
139
|
+
const issues = [];
|
|
140
|
+
const codeChanges = changes.filter(c => c.is_code && c.type !== "deleted");
|
|
141
|
+
const docPaths = new Set(changes.filter(c => c.is_doc).map(c => normalizePath(c.path)));
|
|
142
|
+
|
|
143
|
+
for (const mod of modules) {
|
|
144
|
+
const readme = normalizePath(mod === "." ? "README.md" : `${mod}/README.md`);
|
|
145
|
+
const design = normalizePath(mod === "." ? "DESIGN.md" : `${mod}/DESIGN.md`);
|
|
146
|
+
const modCode = codeChanges.filter(c => isPathInModule(c.path, mod));
|
|
147
|
+
if (!modCode.length) continue;
|
|
148
|
+
const total = modCode.reduce((s, c) => s + c.additions + c.deletions, 0);
|
|
149
|
+
if (total > 50 && !docPaths.has(design)) {
|
|
150
|
+
issues.push({
|
|
151
|
+
severity: "warning",
|
|
152
|
+
message: `模块 ${mod} 有较大代码变更 (${total} 行),但 DESIGN.md 未更新`,
|
|
153
|
+
related_files: modCode.map(c => c.path)
|
|
154
|
+
});
|
|
155
|
+
docStatus[`${mod}/DESIGN.md`] = false;
|
|
156
|
+
} else {
|
|
157
|
+
docStatus[`${mod}/DESIGN.md`] = true;
|
|
158
|
+
}
|
|
159
|
+
const newFiles = modCode.filter(c => c.type === "added");
|
|
160
|
+
if (newFiles.length && !docPaths.has(readme)) {
|
|
161
|
+
issues.push({
|
|
162
|
+
severity: "info",
|
|
163
|
+
message: `模块 ${mod} 新增了文件,建议更新 README.md`,
|
|
164
|
+
related_files: newFiles.map(c => c.path)
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { docStatus, issues };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function analyzeImpact(changes) {
|
|
172
|
+
const issues = [];
|
|
173
|
+
const code = changes.filter(c => c.is_code && !c.is_test);
|
|
174
|
+
const tests = changes.filter(c => c.is_test);
|
|
175
|
+
if (code.length && !tests.length) {
|
|
176
|
+
const total = code.reduce((s, c) => s + c.additions + c.deletions, 0);
|
|
177
|
+
if (total > 30) {
|
|
178
|
+
issues.push({
|
|
179
|
+
severity: "warning",
|
|
180
|
+
message: `代码变更 ${total} 行,但没有对应的测试更新`,
|
|
181
|
+
related_files: code.map(c => c.path)
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const configs = changes.filter(c => c.is_config);
|
|
186
|
+
if (configs.length) {
|
|
187
|
+
issues.push({
|
|
188
|
+
severity: "info",
|
|
189
|
+
message: "配置文件有变更,请确认是否需要更新文档",
|
|
190
|
+
related_files: configs.map(c => c.path)
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
const deleted = changes.filter(c => c.type === "deleted");
|
|
194
|
+
if (deleted.length) {
|
|
195
|
+
issues.push({
|
|
196
|
+
severity: "info",
|
|
197
|
+
message: `删除了 ${deleted.length} 个文件,请确认相关引用已清理`,
|
|
198
|
+
related_files: deleted.map(c => c.path)
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return issues;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function analyzeChanges(mode = "working") {
|
|
205
|
+
let changes;
|
|
206
|
+
if (mode === "staged") changes = getStagedChanges();
|
|
207
|
+
else if (mode === "committed") changes = getGitChanges();
|
|
208
|
+
else changes = getWorkingChanges();
|
|
209
|
+
|
|
210
|
+
const issues = [];
|
|
211
|
+
let modules = new Set(), docStatus = {};
|
|
212
|
+
if (changes.length) {
|
|
213
|
+
modules = identifyModules(changes);
|
|
214
|
+
const ds = checkDocSync(changes, modules);
|
|
215
|
+
docStatus = ds.docStatus;
|
|
216
|
+
issues.push(...ds.issues, ...analyzeImpact(changes));
|
|
217
|
+
}
|
|
218
|
+
const passed = !issues.some(i => i.severity === "error");
|
|
219
|
+
const totalAdd = changes.reduce((s, c) => s + c.additions, 0);
|
|
220
|
+
const totalDel = changes.reduce((s, c) => s + c.deletions, 0);
|
|
221
|
+
return { changes, issues, modules, docStatus, passed, totalAdd, totalDel };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function formatReport(r, verbose) {
|
|
225
|
+
const fields = {
|
|
226
|
+
'变更文件': r.changes.length,
|
|
227
|
+
'新增行数': `+${r.totalAdd}`,
|
|
228
|
+
'删除行数': `-${r.totalDel}`,
|
|
229
|
+
'受影响模块': [...r.modules].join(", ") || "无",
|
|
230
|
+
'分析结果': r.passed ? "✓ 通过" : "✗ 需要关注",
|
|
231
|
+
};
|
|
232
|
+
let report = buildReport('变更分析报告', fields, r.issues, verbose);
|
|
233
|
+
|
|
234
|
+
if (r.changes.length && verbose) {
|
|
235
|
+
const lines = ["\n" + DASH, "变更文件列表:", DASH];
|
|
236
|
+
const icons = { added: "➕", modified: "📝", deleted: "➖", renamed: "📋" };
|
|
237
|
+
for (const c of r.changes) {
|
|
238
|
+
const tags = [];
|
|
239
|
+
if (c.is_code) tags.push("代码");
|
|
240
|
+
if (c.is_doc) tags.push("文档");
|
|
241
|
+
if (c.is_test) tags.push("测试");
|
|
242
|
+
if (c.is_config) tags.push("配置");
|
|
243
|
+
const t = tags.length ? ` [${tags.join(", ")}]` : "";
|
|
244
|
+
lines.push(` ${icons[c.type] || "📝"} ${c.path}${t} (+${c.additions}/-${c.deletions})`);
|
|
245
|
+
}
|
|
246
|
+
report += '\n' + lines.join('\n');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (Object.keys(r.docStatus).length) {
|
|
250
|
+
const lines = ["\n" + DASH, "文档同步状态:", DASH];
|
|
251
|
+
for (const [doc, synced] of Object.entries(r.docStatus)) {
|
|
252
|
+
lines.push(` ${synced ? "✓" : "✗"} ${doc}`);
|
|
253
|
+
}
|
|
254
|
+
report += '\n' + lines.join('\n');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return report;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// CLI
|
|
261
|
+
if (require.main === module) {
|
|
262
|
+
const opts = parseCliArgs(process.argv);
|
|
263
|
+
const result = analyzeChanges(opts.mode || "working");
|
|
264
|
+
|
|
265
|
+
if (opts.json) {
|
|
266
|
+
console.log(JSON.stringify({
|
|
267
|
+
passed: result.passed,
|
|
268
|
+
total_additions: result.totalAdd,
|
|
269
|
+
total_deletions: result.totalDel,
|
|
270
|
+
affected_modules: [...result.modules],
|
|
271
|
+
changes: result.changes.map(c => ({
|
|
272
|
+
path: c.path, type: c.type, additions: c.additions,
|
|
273
|
+
deletions: c.deletions, is_code: c.is_code,
|
|
274
|
+
is_doc: c.is_doc, is_test: c.is_test
|
|
275
|
+
})),
|
|
276
|
+
issues: result.issues.map(i => ({
|
|
277
|
+
severity: i.severity, message: i.message,
|
|
278
|
+
related_files: i.related_files,
|
|
279
|
+
})),
|
|
280
|
+
doc_sync_status: result.docStatus,
|
|
281
|
+
}, null, 2));
|
|
282
|
+
} else {
|
|
283
|
+
console.log(formatReport(result, opts.verbose));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
process.exit(result.passed ? 0 : 1);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
module.exports = { normalizePath, classifyFile, parsePorcelainLine, parseNameStatusLine, identifyModules };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: verify-module
|
|
3
|
+
description: 模块完整性校验关卡。扫描目录结构、检测缺失文档、验证代码与文档同步。当用户提到模块校验、文档检查、结构完整性、README检查、DESIGN检查时使用。在新建模块完成时自动触发。
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: node>=18
|
|
6
|
+
user-invocable: true
|
|
7
|
+
disable-model-invocation: false
|
|
8
|
+
allowed-tools: Bash, Read, Glob
|
|
9
|
+
argument-hint: <模块路径>
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# ⚖ 校验关卡 · 模块完整性
|
|
13
|
+
|
|
14
|
+
## 核心原则
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
模块 = 代码 + README.md + DESIGN.md
|
|
18
|
+
缺一不可,残缺即异端
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 自动扫描
|
|
22
|
+
|
|
23
|
+
运行扫描脚本(跨平台):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# 在 verify-module 目录下运行(推荐)
|
|
27
|
+
node scripts/module_scanner.js <模块路径>
|
|
28
|
+
node scripts/module_scanner.js <模块路径> -v # 详细模式
|
|
29
|
+
node scripts/module_scanner.js <模块路径> --json # JSON 输出
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 校验标准
|
|
33
|
+
|
|
34
|
+
一个完整的模块必须包含:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
module/
|
|
38
|
+
├── README.md # 必须 - 模块是什么、为什么存在
|
|
39
|
+
├── DESIGN.md # 必须 - 设计决策、权衡取舍
|
|
40
|
+
├── src/ # 代码实现
|
|
41
|
+
└── tests/ # 测试用例(如适用)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 检测项
|
|
45
|
+
|
|
46
|
+
### 必须存在
|
|
47
|
+
|
|
48
|
+
| 文件 | 说明 | 缺失后果 |
|
|
49
|
+
|------|------|----------|
|
|
50
|
+
| `README.md` | 模块说明文档 | 🔴 阻断交付 |
|
|
51
|
+
| `DESIGN.md` | 设计决策文档 | 🔴 阻断交付 |
|
|
52
|
+
|
|
53
|
+
### 推荐存在
|
|
54
|
+
|
|
55
|
+
| 文件/目录 | 说明 | 缺失后果 |
|
|
56
|
+
|-----------|------|----------|
|
|
57
|
+
| `tests/` | 测试目录 | 🟠 警告 |
|
|
58
|
+
| `__init__.py` | Python 包标识 | 🟡 提示 |
|
|
59
|
+
| `.gitignore` | Git 忽略配置 | 🔵 信息 |
|
|
60
|
+
|
|
61
|
+
### README.md 必须包含
|
|
62
|
+
|
|
63
|
+
- [ ] **模块名称与定位** — 一句话说明是什么
|
|
64
|
+
- [ ] **存在理由** — 为什么需要这个模块
|
|
65
|
+
- [ ] **核心职责** — 做什么、不做什么
|
|
66
|
+
- [ ] **依赖关系** — 依赖谁、被谁依赖
|
|
67
|
+
- [ ] **快速使用** — 最简示例
|
|
68
|
+
|
|
69
|
+
### DESIGN.md 必须包含
|
|
70
|
+
|
|
71
|
+
- [ ] **设计目标** — 要解决什么问题
|
|
72
|
+
- [ ] **方案选择** — 考虑过哪些方案、为何选当前方案
|
|
73
|
+
- [ ] **关键决策** — 重要的技术决策及理由
|
|
74
|
+
- [ ] **已知限制** — 当前方案的局限性
|
|
75
|
+
- [ ] **变更历史** — 重大变更记录
|
|
76
|
+
|
|
77
|
+
## 自动触发时机
|
|
78
|
+
|
|
79
|
+
| 场景 | 触发条件 |
|
|
80
|
+
|------|----------|
|
|
81
|
+
| 新建模块 | 模块创建完成时 |
|
|
82
|
+
| 模块重构 | 重构完成时 |
|
|
83
|
+
| 提交前 | 代码提交前检查 |
|
|
84
|
+
|
|
85
|
+
## 校验流程
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
1. 运行 module_scanner.js 自动扫描
|
|
89
|
+
2. 检查文件结构是否完整
|
|
90
|
+
3. 检查 README.md 各项是否齐全
|
|
91
|
+
4. 检查 DESIGN.md 各项是否齐全
|
|
92
|
+
5. 检查代码与文档描述是否一致
|
|
93
|
+
6. 输出校验报告
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 校验报告格式
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
## 模块校验报告
|
|
100
|
+
|
|
101
|
+
### 模块: <模块名>
|
|
102
|
+
|
|
103
|
+
✓ 通过 | ✗ 未通过
|
|
104
|
+
|
|
105
|
+
### 文件检查
|
|
106
|
+
- README.md: ✓ 存在 / ✗ 缺失
|
|
107
|
+
- DESIGN.md: ✓ 存在 / ✗ 缺失
|
|
108
|
+
- tests/: ✓ 存在 / ⚠️ 缺失
|
|
109
|
+
|
|
110
|
+
### 内容检查
|
|
111
|
+
- README 完整性: ✓ 完整 / ⚠️ 缺少 [X, Y, Z]
|
|
112
|
+
- DESIGN 完整性: ✓ 完整 / ⚠️ 缺少 [X, Y, Z]
|
|
113
|
+
|
|
114
|
+
### 结论
|
|
115
|
+
可交付 / 需补充后交付
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 快速修复
|
|
119
|
+
|
|
120
|
+
如果缺少文档,可使用文档生成器:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
/gen-docs <模块路径>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { parseCliArgs, buildReport, hasFatal } = require(path.join(__dirname, '..', '..', 'lib', 'shared.js'));
|
|
7
|
+
|
|
8
|
+
const REQUIRED_FILES = { 'README.md': '模块说明文档', 'DESIGN.md': '设计决策文档' };
|
|
9
|
+
const ALT_SRC_DIRS = ['src', 'lib', 'pkg', 'internal', 'cmd', 'app'];
|
|
10
|
+
const ALT_TEST_DIRS = ['tests', 'test', '__tests__', 'spec'];
|
|
11
|
+
const ROOT_SCRIPT_FILES = new Set([
|
|
12
|
+
'install.sh', 'uninstall.sh', 'install.ps1',
|
|
13
|
+
'uninstall.ps1', 'Dockerfile', 'Makefile'
|
|
14
|
+
]);
|
|
15
|
+
const CODE_EXTS = new Set(['.py', '.go', '.rs', '.ts', '.js', '.java', '.sh', '.ps1']);
|
|
16
|
+
const TEST_PATTERNS = ['test_', '_test.', '.test.', 'spec_', '_spec.'];
|
|
17
|
+
|
|
18
|
+
function scanStructure(p, depth = 3) {
|
|
19
|
+
const s = { name: path.basename(p), type: 'dir', children: [] };
|
|
20
|
+
if (depth <= 0) return s;
|
|
21
|
+
try {
|
|
22
|
+
for (const name of fs.readdirSync(p).sort()) {
|
|
23
|
+
if (name.startsWith('.')) continue;
|
|
24
|
+
const full = path.join(p, name);
|
|
25
|
+
const stat = fs.statSync(full);
|
|
26
|
+
if (stat.isFile()) s.children.push({ name, type: 'file', size: stat.size });
|
|
27
|
+
else if (stat.isDirectory()) s.children.push(scanStructure(full, depth - 1));
|
|
28
|
+
}
|
|
29
|
+
} catch {}
|
|
30
|
+
return s;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function rglob(dir, test) {
|
|
34
|
+
try {
|
|
35
|
+
for (const name of fs.readdirSync(dir)) {
|
|
36
|
+
const full = path.join(dir, name);
|
|
37
|
+
try {
|
|
38
|
+
const stat = fs.statSync(full);
|
|
39
|
+
if (stat.isFile() && test(name)) return true;
|
|
40
|
+
if (stat.isDirectory()) { if (rglob(full, test)) return true; }
|
|
41
|
+
} catch {}
|
|
42
|
+
}
|
|
43
|
+
} catch {}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function scanModule(target) {
|
|
48
|
+
const modulePath = path.resolve(target);
|
|
49
|
+
const issues = [];
|
|
50
|
+
const add = (severity, message, p) => issues.push({ severity, message, path: p || null });
|
|
51
|
+
|
|
52
|
+
if (!fs.existsSync(modulePath)) {
|
|
53
|
+
add('error', `路径不存在: ${modulePath}`);
|
|
54
|
+
return { modulePath, issues, structure: {} };
|
|
55
|
+
}
|
|
56
|
+
if (!fs.statSync(modulePath).isDirectory()) {
|
|
57
|
+
add('error', `不是目录: ${modulePath}`);
|
|
58
|
+
return { modulePath, issues, structure: {} };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const structure = scanStructure(modulePath);
|
|
62
|
+
|
|
63
|
+
// required files
|
|
64
|
+
for (const [file, desc] of Object.entries(REQUIRED_FILES)) {
|
|
65
|
+
const fp = path.join(modulePath, file);
|
|
66
|
+
if (!fs.existsSync(fp)) add('error', `缺少必需文档: ${file} (${desc})`, fp);
|
|
67
|
+
else if (fs.statSync(fp).size < 50) add('warning', `文档内容过少: ${file} (< 50 bytes)`, fp);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// source dirs
|
|
71
|
+
let srcFound = ALT_SRC_DIRS.some(d => {
|
|
72
|
+
try { return fs.statSync(path.join(modulePath, d)).isDirectory(); }
|
|
73
|
+
catch { return false; }
|
|
74
|
+
});
|
|
75
|
+
const entries = fs.readdirSync(modulePath);
|
|
76
|
+
const rootCode = entries.filter(n => {
|
|
77
|
+
try {
|
|
78
|
+
const s = fs.statSync(path.join(modulePath, n));
|
|
79
|
+
return s.isFile() && CODE_EXTS.has(path.extname(n));
|
|
80
|
+
} catch { return false; }
|
|
81
|
+
});
|
|
82
|
+
const rootScript = entries.filter(n => {
|
|
83
|
+
try {
|
|
84
|
+
return fs.statSync(path.join(modulePath, n)).isFile()
|
|
85
|
+
&& ROOT_SCRIPT_FILES.has(n);
|
|
86
|
+
} catch { return false; }
|
|
87
|
+
});
|
|
88
|
+
if (rootCode.length || rootScript.length) {
|
|
89
|
+
srcFound = true;
|
|
90
|
+
if (rootCode.length > 5) {
|
|
91
|
+
add('warning', `根目录代码文件过多 (${rootCode.length}个),建议整理到 src/ 目录`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (!srcFound) add('warning', '未找到源码目录或代码文件');
|
|
95
|
+
|
|
96
|
+
// test dirs
|
|
97
|
+
let testFound = ALT_TEST_DIRS.some(d => {
|
|
98
|
+
try { return fs.statSync(path.join(modulePath, d)).isDirectory(); }
|
|
99
|
+
catch { return false; }
|
|
100
|
+
});
|
|
101
|
+
if (!testFound) testFound = rglob(modulePath, n => TEST_PATTERNS.some(p => n.includes(p)));
|
|
102
|
+
if (!testFound) add('warning', '未找到测试目录或测试文件');
|
|
103
|
+
|
|
104
|
+
// doc quality
|
|
105
|
+
const readme = path.join(modulePath, 'README.md');
|
|
106
|
+
if (fs.existsSync(readme)) {
|
|
107
|
+
const c = fs.readFileSync(readme, 'utf-8');
|
|
108
|
+
if (!c.includes('#')) add('warning', 'README.md 缺少标题', readme);
|
|
109
|
+
const docKeys = ['usage', 'install', '使用', '安装', 'example', '示例'];
|
|
110
|
+
if (!docKeys.some(k => c.toLowerCase().includes(k)))
|
|
111
|
+
add('info', 'README.md 建议添加使用说明或示例', readme);
|
|
112
|
+
}
|
|
113
|
+
const design = path.join(modulePath, 'DESIGN.md');
|
|
114
|
+
if (fs.existsSync(design)) {
|
|
115
|
+
const c = fs.readFileSync(design, 'utf-8');
|
|
116
|
+
const designKeys = ['决策', 'decision', '选择', 'choice', '权衡', 'trade'];
|
|
117
|
+
if (!designKeys.some(k => c.toLowerCase().includes(k)))
|
|
118
|
+
add('info', 'DESIGN.md 建议记录设计决策和权衡', design);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return { modulePath, issues, structure };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function formatStructure(s, indent = 0) {
|
|
125
|
+
const pre = ' '.repeat(indent);
|
|
126
|
+
if (s.type === 'dir') {
|
|
127
|
+
const lines = [`${pre}\u{1F4C1} ${s.name}/`];
|
|
128
|
+
for (const ch of (s.children || [])) lines.push(formatStructure(ch, indent + 1));
|
|
129
|
+
return lines.join('\n');
|
|
130
|
+
}
|
|
131
|
+
const sz = (s.size || 0) < 1024 ? `(${s.size} B)` : `(${Math.floor(s.size / 1024)} KB)`;
|
|
132
|
+
return `${pre}\u{1F4C4} ${s.name} ${sz}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function formatReport(r, verbose) {
|
|
136
|
+
const errs = r.issues.filter(i => i.severity === 'error').length;
|
|
137
|
+
const warns = r.issues.filter(i => i.severity === 'warning').length;
|
|
138
|
+
const passed = !hasFatal(r.issues);
|
|
139
|
+
const fields = {
|
|
140
|
+
'模块路径': r.modulePath,
|
|
141
|
+
'扫描结果': passed ? '\u2713 通过' : '\u2717 未通过',
|
|
142
|
+
'统计': `错误: ${errs} | 警告: ${warns}`,
|
|
143
|
+
};
|
|
144
|
+
const issues = r.issues.map(i => ({
|
|
145
|
+
severity: i.severity, message: i.message, path: i.path,
|
|
146
|
+
file_path: i.path || '', line_number: null,
|
|
147
|
+
}));
|
|
148
|
+
let report = buildReport('模块完整性扫描报告', fields, issues, verbose);
|
|
149
|
+
if (verbose && r.structure.name) {
|
|
150
|
+
report += '\n' + '-'.repeat(40) + '\n目录结构:\n' + '-'.repeat(40) + '\n' + formatStructure(r.structure);
|
|
151
|
+
}
|
|
152
|
+
return report;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// CLI
|
|
156
|
+
const opts = parseCliArgs(process.argv);
|
|
157
|
+
const result = scanModule(opts.target);
|
|
158
|
+
const passed = !hasFatal(result.issues);
|
|
159
|
+
|
|
160
|
+
if (opts.json) {
|
|
161
|
+
console.log(JSON.stringify({
|
|
162
|
+
module_path: result.modulePath, passed,
|
|
163
|
+
error_count: result.issues.filter(i => i.severity === 'error').length,
|
|
164
|
+
warning_count: result.issues.filter(i => i.severity === 'warning').length,
|
|
165
|
+
issues: result.issues
|
|
166
|
+
}, null, 2));
|
|
167
|
+
} else {
|
|
168
|
+
console.log(formatReport(result, opts.verbose));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
process.exit(passed ? 0 : 1);
|