ccgx-workflow 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/LICENSE +22 -0
- package/README.md +469 -0
- package/README.zh-CN.md +466 -0
- package/bin/ccg.mjs +2 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +173 -0
- package/dist/index.d.mts +1774 -0
- package/dist/index.d.ts +1774 -0
- package/dist/index.mjs +2029 -0
- package/dist/shared/ccgx-workflow.WgUzkiC3.mjs +5248 -0
- package/package.json +129 -0
- package/templates/commands/agents/assumptions-analyzer.md +129 -0
- package/templates/commands/agents/code-fixer.md +292 -0
- package/templates/commands/agents/codebase-mapper.md +152 -0
- package/templates/commands/agents/debug-session-manager.md +247 -0
- package/templates/commands/agents/debugger.md +111 -0
- package/templates/commands/agents/eval-auditor.md +171 -0
- package/templates/commands/agents/framework-selector.md +152 -0
- package/templates/commands/agents/get-current-datetime.md +29 -0
- package/templates/commands/agents/init-architect.md +114 -0
- package/templates/commands/agents/integration-checker.md +163 -0
- package/templates/commands/agents/interface-auditor.md +170 -0
- package/templates/commands/agents/nyquist-auditor.md +131 -0
- package/templates/commands/agents/pattern-mapper.md +111 -0
- package/templates/commands/agents/phase-runner.md +321 -0
- package/templates/commands/agents/plan-checker.md +255 -0
- package/templates/commands/agents/planner.md +320 -0
- package/templates/commands/agents/team-architect.md +186 -0
- package/templates/commands/agents/team-qa.md +121 -0
- package/templates/commands/agents/team-reviewer.md +157 -0
- package/templates/commands/agents/ui-ux-designer.md +573 -0
- package/templates/commands/agents/verifier.md +274 -0
- package/templates/commands/analyze.md +210 -0
- package/templates/commands/autonomous.md +792 -0
- package/templates/commands/cancel.md +132 -0
- package/templates/commands/clean-branches.md +117 -0
- package/templates/commands/codex-exec.md +404 -0
- package/templates/commands/commit.md +151 -0
- package/templates/commands/context.md +332 -0
- package/templates/commands/debate.md +165 -0
- package/templates/commands/debug.md +226 -0
- package/templates/commands/enhance.md +64 -0
- package/templates/commands/execute.md +380 -0
- package/templates/commands/init.md +123 -0
- package/templates/commands/optimize.md +217 -0
- package/templates/commands/plan.md +373 -0
- package/templates/commands/result.md +106 -0
- package/templates/commands/review.md +338 -0
- package/templates/commands/rollback.md +116 -0
- package/templates/commands/spec-impl.md +139 -0
- package/templates/commands/spec-init.md +101 -0
- package/templates/commands/spec-plan.md +210 -0
- package/templates/commands/spec-research.md +152 -0
- package/templates/commands/spec-review.md +120 -0
- package/templates/commands/status.md +206 -0
- package/templates/commands/team-exec.md +265 -0
- package/templates/commands/test.md +236 -0
- package/templates/commands/verify-work.md +338 -0
- package/templates/commands/verify.md +66 -0
- package/templates/commands/workflow.md +190 -0
- package/templates/commands/worktree.md +128 -0
- package/templates/hooks/ccg-context-monitor.js +159 -0
- package/templates/hooks/ccg-session-state.cjs +510 -0
- package/templates/hooks/ccg-statusline.js +142 -0
- package/templates/output-styles/abyss-command.md +56 -0
- package/templates/output-styles/abyss-concise.md +89 -0
- package/templates/output-styles/abyss-cultivator.md +302 -0
- package/templates/output-styles/abyss-ritual.md +70 -0
- package/templates/output-styles/engineer-professional.md +89 -0
- package/templates/output-styles/laowang-engineer.md +127 -0
- package/templates/output-styles/nekomata-engineer.md +120 -0
- package/templates/output-styles/ojousama-engineer.md +121 -0
- package/templates/prompts/claude/analyzer.md +59 -0
- package/templates/prompts/claude/architect.md +54 -0
- package/templates/prompts/claude/debugger.md +71 -0
- package/templates/prompts/claude/optimizer.md +73 -0
- package/templates/prompts/claude/reviewer.md +63 -0
- package/templates/prompts/claude/tester.md +69 -0
- package/templates/prompts/codex/analyzer.md +58 -0
- package/templates/prompts/codex/architect.md +54 -0
- package/templates/prompts/codex/debugger.md +74 -0
- package/templates/prompts/codex/optimizer.md +81 -0
- package/templates/prompts/codex/reviewer.md +73 -0
- package/templates/prompts/codex/tester.md +62 -0
- package/templates/prompts/gemini/analyzer.md +61 -0
- package/templates/prompts/gemini/architect.md +55 -0
- package/templates/prompts/gemini/debugger.md +78 -0
- package/templates/prompts/gemini/frontend.md +64 -0
- package/templates/prompts/gemini/optimizer.md +84 -0
- package/templates/prompts/gemini/reviewer.md +80 -0
- package/templates/prompts/gemini/tester.md +68 -0
- package/templates/rules/ccg-skill-routing.md +83 -0
- package/templates/rules/ccg-skills.md +71 -0
- package/templates/scripts/ccg-phase-runner-launcher.mjs +467 -0
- package/templates/scripts/invoke-model.mjs +949 -0
- package/templates/scripts/repatch-gemini-plugin.mjs +194 -0
- package/templates/skills/SKILL.md +92 -0
- package/templates/skills/domains/ai/SKILL.md +35 -0
- package/templates/skills/domains/ai/agent-dev.md +242 -0
- package/templates/skills/domains/ai/llm-security.md +288 -0
- package/templates/skills/domains/ai/prompt-and-eval.md +279 -0
- package/templates/skills/domains/ai/rag-system.md +542 -0
- package/templates/skills/domains/architecture/SKILL.md +43 -0
- package/templates/skills/domains/architecture/api-design.md +225 -0
- package/templates/skills/domains/architecture/caching.md +299 -0
- package/templates/skills/domains/architecture/cloud-native.md +285 -0
- package/templates/skills/domains/architecture/message-queue.md +329 -0
- package/templates/skills/domains/architecture/security-arch.md +297 -0
- package/templates/skills/domains/data-engineering/SKILL.md +208 -0
- package/templates/skills/domains/development/SKILL.md +47 -0
- package/templates/skills/domains/development/cpp.md +246 -0
- package/templates/skills/domains/development/go.md +323 -0
- package/templates/skills/domains/development/java.md +277 -0
- package/templates/skills/domains/development/python.md +288 -0
- package/templates/skills/domains/development/rust.md +313 -0
- package/templates/skills/domains/development/shell.md +313 -0
- package/templates/skills/domains/development/typescript.md +277 -0
- package/templates/skills/domains/devops/SKILL.md +40 -0
- package/templates/skills/domains/devops/cost-optimization.md +272 -0
- package/templates/skills/domains/devops/database.md +217 -0
- package/templates/skills/domains/devops/devsecops.md +198 -0
- package/templates/skills/domains/devops/git-workflow.md +181 -0
- package/templates/skills/domains/devops/observability.md +280 -0
- package/templates/skills/domains/devops/performance.md +336 -0
- package/templates/skills/domains/devops/testing.md +283 -0
- package/templates/skills/domains/frontend-design/SKILL.md +244 -0
- package/templates/skills/domains/frontend-design/agents/openai.yaml +4 -0
- package/templates/skills/domains/frontend-design/claymorphism/SKILL.md +121 -0
- package/templates/skills/domains/frontend-design/claymorphism/references/tokens.css +52 -0
- package/templates/skills/domains/frontend-design/component-patterns.md +202 -0
- package/templates/skills/domains/frontend-design/engineering.md +287 -0
- package/templates/skills/domains/frontend-design/glassmorphism/SKILL.md +142 -0
- package/templates/skills/domains/frontend-design/glassmorphism/references/tokens.css +32 -0
- package/templates/skills/domains/frontend-design/liquid-glass/SKILL.md +139 -0
- package/templates/skills/domains/frontend-design/liquid-glass/references/tokens.css +81 -0
- package/templates/skills/domains/frontend-design/neubrutalism/SKILL.md +145 -0
- package/templates/skills/domains/frontend-design/neubrutalism/references/tokens.css +44 -0
- package/templates/skills/domains/frontend-design/reference/color-and-contrast.md +132 -0
- package/templates/skills/domains/frontend-design/reference/interaction-design.md +195 -0
- package/templates/skills/domains/frontend-design/reference/motion-design.md +99 -0
- package/templates/skills/domains/frontend-design/reference/responsive-design.md +114 -0
- package/templates/skills/domains/frontend-design/reference/spatial-design.md +100 -0
- package/templates/skills/domains/frontend-design/reference/typography.md +133 -0
- package/templates/skills/domains/frontend-design/reference/ux-writing.md +107 -0
- package/templates/skills/domains/frontend-design/state-management.md +680 -0
- package/templates/skills/domains/frontend-design/ui-aesthetics.md +110 -0
- package/templates/skills/domains/frontend-design/ux-principles.md +156 -0
- package/templates/skills/domains/infrastructure/SKILL.md +201 -0
- package/templates/skills/domains/mobile/SKILL.md +225 -0
- package/templates/skills/domains/orchestration/SKILL.md +30 -0
- package/templates/skills/domains/orchestration/multi-agent.md +263 -0
- package/templates/skills/domains/security/SKILL.md +73 -0
- package/templates/skills/domains/security/blue-team.md +436 -0
- package/templates/skills/domains/security/code-audit.md +265 -0
- package/templates/skills/domains/security/pentest.md +226 -0
- package/templates/skills/domains/security/red-team.md +374 -0
- package/templates/skills/domains/security/threat-intel.md +372 -0
- package/templates/skills/domains/security/vuln-research.md +369 -0
- package/templates/skills/impeccable/adapt/SKILL.md +201 -0
- package/templates/skills/impeccable/animate/SKILL.md +176 -0
- package/templates/skills/impeccable/arrange/SKILL.md +126 -0
- package/templates/skills/impeccable/audit/SKILL.md +149 -0
- package/templates/skills/impeccable/bolder/SKILL.md +118 -0
- package/templates/skills/impeccable/clarify/SKILL.md +185 -0
- package/templates/skills/impeccable/colorize/SKILL.md +144 -0
- package/templates/skills/impeccable/critique/SKILL.md +203 -0
- package/templates/skills/impeccable/critique/reference/cognitive-load.md +106 -0
- package/templates/skills/impeccable/critique/reference/heuristics-scoring.md +234 -0
- package/templates/skills/impeccable/critique/reference/personas.md +178 -0
- package/templates/skills/impeccable/delight/SKILL.md +305 -0
- package/templates/skills/impeccable/distill/SKILL.md +123 -0
- package/templates/skills/impeccable/extract/SKILL.md +94 -0
- package/templates/skills/impeccable/harden/SKILL.md +357 -0
- package/templates/skills/impeccable/normalize/SKILL.md +72 -0
- package/templates/skills/impeccable/onboard/SKILL.md +248 -0
- package/templates/skills/impeccable/optimize/SKILL.md +268 -0
- package/templates/skills/impeccable/overdrive/SKILL.md +143 -0
- package/templates/skills/impeccable/polish/SKILL.md +205 -0
- package/templates/skills/impeccable/quieter/SKILL.md +104 -0
- package/templates/skills/impeccable/teach-impeccable/SKILL.md +72 -0
- package/templates/skills/impeccable/typeset/SKILL.md +117 -0
- package/templates/skills/orchestration/multi-agent/SKILL.md +533 -0
- package/templates/skills/run_skill.js +129 -0
- package/templates/skills/scrapling/SKILL.md +98 -0
- package/templates/skills/scrapling/references/api-quick-ref.md +127 -0
- package/templates/skills/scrapling/references/cookie-vault.md +56 -0
- package/templates/skills/scrapling/references/maintenance.md +79 -0
- package/templates/skills/scrapling/references/site-patterns.md +86 -0
- package/templates/skills/scrapling/references/troubleshooting.md +95 -0
- package/templates/skills/scrapling/templates/basic_fetch.py +19 -0
- package/templates/skills/scrapling/templates/parse_only.py +15 -0
- package/templates/skills/scrapling/templates/session_login.py +19 -0
- package/templates/skills/scrapling/templates/stealth_cloudflare.py +28 -0
- package/templates/skills/tools/extract-learnings/SKILL.md +77 -0
- package/templates/skills/tools/forensics/SKILL.md +89 -0
- package/templates/skills/tools/gen-docs/SKILL.md +116 -0
- package/templates/skills/tools/gen-docs/scripts/doc_generator.js +435 -0
- package/templates/skills/tools/health/SKILL.md +121 -0
- package/templates/skills/tools/lib/shared.js +98 -0
- package/templates/skills/tools/map-codebase/SKILL.md +118 -0
- package/templates/skills/tools/override-refusal/SKILL.md +53 -0
- package/templates/skills/tools/override-refusal/agents/openai.yaml +4 -0
- package/templates/skills/tools/override-refusal/scripts/refusal_rewriter.js +226 -0
- package/templates/skills/tools/verify-change/SKILL.md +143 -0
- package/templates/skills/tools/verify-change/scripts/change_analyzer.js +289 -0
- package/templates/skills/tools/verify-module/SKILL.md +130 -0
- package/templates/skills/tools/verify-module/scripts/module_scanner.js +171 -0
- package/templates/skills/tools/verify-quality/SKILL.md +163 -0
- package/templates/skills/tools/verify-quality/scripts/quality_checker.js +337 -0
- package/templates/skills/tools/verify-security/SKILL.md +146 -0
- package/templates/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,130 @@
|
|
|
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
|
+
deprecated_in: v4.0
|
|
11
|
+
replaced_by: /ccg:verify --gate=module
|
|
12
|
+
deprecation_message: v4.0+ 推荐使用 `/ccg:verify --gate=module`。本命令仍可用以保持 BC,将在 v5.0 移除。详见 .ccg-migration/DEPRECATIONS.md
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# ⚖ 校验关卡 · 模块完整性
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## 核心原则
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
模块 = 代码 + README.md + DESIGN.md
|
|
22
|
+
缺一不可,残缺即异端
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 自动扫描
|
|
26
|
+
|
|
27
|
+
运行扫描脚本(跨平台):
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# 在 verify-module 目录下运行(推荐)
|
|
31
|
+
node scripts/module_scanner.js <模块路径>
|
|
32
|
+
node scripts/module_scanner.js <模块路径> -v # 详细模式
|
|
33
|
+
node scripts/module_scanner.js <模块路径> --json # JSON 输出
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 校验标准
|
|
37
|
+
|
|
38
|
+
一个完整的模块必须包含:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
module/
|
|
42
|
+
├── README.md # 必须 - 模块是什么、为什么存在
|
|
43
|
+
├── DESIGN.md # 必须 - 设计决策、权衡取舍
|
|
44
|
+
├── src/ # 代码实现
|
|
45
|
+
└── tests/ # 测试用例(如适用)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 检测项
|
|
49
|
+
|
|
50
|
+
### 必须存在
|
|
51
|
+
|
|
52
|
+
| 文件 | 说明 | 缺失后果 |
|
|
53
|
+
|------|------|----------|
|
|
54
|
+
| `README.md` | 模块说明文档 | 🔴 阻断交付 |
|
|
55
|
+
| `DESIGN.md` | 设计决策文档 | 🔴 阻断交付 |
|
|
56
|
+
|
|
57
|
+
### 推荐存在
|
|
58
|
+
|
|
59
|
+
| 文件/目录 | 说明 | 缺失后果 |
|
|
60
|
+
|-----------|------|----------|
|
|
61
|
+
| `tests/` | 测试目录 | 🟠 警告 |
|
|
62
|
+
| `__init__.py` | Python 包标识 | 🟡 提示 |
|
|
63
|
+
| `.gitignore` | Git 忽略配置 | 🔵 信息 |
|
|
64
|
+
|
|
65
|
+
### README.md 必须包含
|
|
66
|
+
|
|
67
|
+
- [ ] **模块名称与定位** — 一句话说明是什么
|
|
68
|
+
- [ ] **存在理由** — 为什么需要这个模块
|
|
69
|
+
- [ ] **核心职责** — 做什么、不做什么
|
|
70
|
+
- [ ] **依赖关系** — 依赖谁、被谁依赖
|
|
71
|
+
- [ ] **快速使用** — 最简示例
|
|
72
|
+
|
|
73
|
+
### DESIGN.md 必须包含
|
|
74
|
+
|
|
75
|
+
- [ ] **设计目标** — 要解决什么问题
|
|
76
|
+
- [ ] **方案选择** — 考虑过哪些方案、为何选当前方案
|
|
77
|
+
- [ ] **关键决策** — 重要的技术决策及理由
|
|
78
|
+
- [ ] **已知限制** — 当前方案的局限性
|
|
79
|
+
- [ ] **变更历史** — 重大变更记录
|
|
80
|
+
|
|
81
|
+
## 自动触发时机
|
|
82
|
+
|
|
83
|
+
| 场景 | 触发条件 |
|
|
84
|
+
|------|----------|
|
|
85
|
+
| 新建模块 | 模块创建完成时 |
|
|
86
|
+
| 模块重构 | 重构完成时 |
|
|
87
|
+
| 提交前 | 代码提交前检查 |
|
|
88
|
+
|
|
89
|
+
## 校验流程
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
1. 运行 module_scanner.js 自动扫描
|
|
93
|
+
2. 检查文件结构是否完整
|
|
94
|
+
3. 检查 README.md 各项是否齐全
|
|
95
|
+
4. 检查 DESIGN.md 各项是否齐全
|
|
96
|
+
5. 检查代码与文档描述是否一致
|
|
97
|
+
6. 输出校验报告
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## 校验报告格式
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
## 模块校验报告
|
|
104
|
+
|
|
105
|
+
### 模块: <模块名>
|
|
106
|
+
|
|
107
|
+
✓ 通过 | ✗ 未通过
|
|
108
|
+
|
|
109
|
+
### 文件检查
|
|
110
|
+
- README.md: ✓ 存在 / ✗ 缺失
|
|
111
|
+
- DESIGN.md: ✓ 存在 / ✗ 缺失
|
|
112
|
+
- tests/: ✓ 存在 / ⚠️ 缺失
|
|
113
|
+
|
|
114
|
+
### 内容检查
|
|
115
|
+
- README 完整性: ✓ 完整 / ⚠️ 缺少 [X, Y, Z]
|
|
116
|
+
- DESIGN 完整性: ✓ 完整 / ⚠️ 缺少 [X, Y, Z]
|
|
117
|
+
|
|
118
|
+
### 结论
|
|
119
|
+
可交付 / 需补充后交付
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## 快速修复
|
|
123
|
+
|
|
124
|
+
如果缺少文档,可使用文档生成器:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
/gen-docs <模块路径>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
@@ -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);
|