ccjk 12.2.2 → 12.3.2

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 (135) hide show
  1. package/dist/chunks/agent-teams.mjs +3 -3
  2. package/dist/chunks/agent.mjs +2 -2
  3. package/dist/chunks/agents.mjs +4 -5
  4. package/dist/chunks/api-cli.mjs +5 -5
  5. package/dist/chunks/api-providers.mjs +1 -1
  6. package/dist/chunks/api.mjs +4 -4
  7. package/dist/chunks/auto-bootstrap.mjs +1 -1
  8. package/dist/chunks/auto-fixer.mjs +3 -3
  9. package/dist/chunks/auto-init.mjs +3 -3
  10. package/dist/chunks/auto-updater.mjs +9 -9
  11. package/dist/chunks/banner.mjs +2 -2
  12. package/dist/chunks/boost.mjs +4 -4
  13. package/dist/chunks/ccjk-agents.mjs +3 -3
  14. package/dist/chunks/ccjk-all.mjs +6 -6
  15. package/dist/chunks/ccjk-config.mjs +2 -2
  16. package/dist/chunks/ccjk-hooks.mjs +4 -4
  17. package/dist/chunks/ccjk-mcp.mjs +5 -5
  18. package/dist/chunks/ccjk-setup.mjs +5 -5
  19. package/dist/chunks/ccjk-skills.mjs +5 -5
  20. package/dist/chunks/ccr.mjs +14 -14
  21. package/dist/chunks/ccu.mjs +2 -2
  22. package/dist/chunks/check-updates.mjs +6 -6
  23. package/dist/chunks/claude-code-config-manager.mjs +4 -4
  24. package/dist/chunks/claude-code-incremental-manager.mjs +6 -6
  25. package/dist/chunks/claude-config.mjs +1 -1
  26. package/dist/chunks/claude-wrapper.mjs +1 -1
  27. package/dist/chunks/cli-hook.mjs +9 -10
  28. package/dist/chunks/codex-config-switch.mjs +6 -6
  29. package/dist/chunks/codex-provider-manager.mjs +6 -6
  30. package/dist/chunks/codex-uninstaller.mjs +2 -2
  31. package/dist/chunks/codex.mjs +5 -5
  32. package/dist/chunks/commands.mjs +2 -2
  33. package/dist/chunks/commands2.mjs +1 -1
  34. package/dist/chunks/commit.mjs +2 -2
  35. package/dist/chunks/completion.mjs +2 -2
  36. package/dist/chunks/config-consolidator.mjs +2 -2
  37. package/dist/chunks/config-switch.mjs +7 -7
  38. package/dist/chunks/config.mjs +3 -3
  39. package/dist/chunks/config2.mjs +4 -4
  40. package/dist/chunks/config3.mjs +3 -3
  41. package/dist/chunks/constants.mjs +1 -1
  42. package/dist/chunks/context-opt.mjs +442 -0
  43. package/dist/chunks/convoy-manager.mjs +355 -3
  44. package/dist/chunks/dashboard.mjs +2 -2
  45. package/dist/chunks/doctor.mjs +4 -4
  46. package/dist/chunks/evolution.mjs +2 -2
  47. package/dist/chunks/health-alerts.mjs +530 -4
  48. package/dist/chunks/help.mjs +1 -1
  49. package/dist/chunks/index.mjs +0 -23
  50. package/dist/chunks/index10.mjs +571 -634
  51. package/dist/chunks/index11.mjs +569 -1061
  52. package/dist/chunks/index12.mjs +1076 -914
  53. package/dist/chunks/index13.mjs +951 -135
  54. package/dist/chunks/index14.mjs +184 -209
  55. package/dist/chunks/index15.mjs +218 -0
  56. package/dist/chunks/index2.mjs +24 -19
  57. package/dist/chunks/index3.mjs +12 -19085
  58. package/dist/chunks/index4.mjs +19092 -16
  59. package/dist/chunks/index5.mjs +16 -7602
  60. package/dist/chunks/index6.mjs +7590 -159
  61. package/dist/chunks/index7.mjs +171 -1602
  62. package/dist/chunks/index8.mjs +1602 -19
  63. package/dist/chunks/index9.mjs +15 -612
  64. package/dist/chunks/init.mjs +13 -13
  65. package/dist/chunks/installer.mjs +5 -5
  66. package/dist/chunks/installer2.mjs +1 -1
  67. package/dist/chunks/interview.mjs +4 -4
  68. package/dist/chunks/manager.mjs +1 -1
  69. package/dist/chunks/marketplace.mjs +2 -2
  70. package/dist/chunks/mcp-cli.mjs +9 -9
  71. package/dist/chunks/mcp.mjs +7 -7
  72. package/dist/chunks/memory.mjs +3 -3
  73. package/dist/chunks/menu-hierarchical.mjs +14 -14
  74. package/dist/chunks/menu.mjs +12 -12
  75. package/dist/chunks/metrics-display.mjs +1 -1
  76. package/dist/chunks/migrator.mjs +1 -1
  77. package/dist/chunks/monitor.mjs +2 -2
  78. package/dist/chunks/notification.mjs +4 -4
  79. package/dist/chunks/onboarding-wizard.mjs +2 -2
  80. package/dist/chunks/onboarding.mjs +4 -4
  81. package/dist/chunks/package.mjs +1 -1
  82. package/dist/chunks/paradigm.mjs +1 -1
  83. package/dist/chunks/permission-manager.mjs +2 -2
  84. package/dist/chunks/permissions.mjs +3 -3
  85. package/dist/chunks/persistence-manager.mjs +3 -3
  86. package/dist/chunks/plugin.mjs +2 -2
  87. package/dist/chunks/prompts.mjs +5 -5
  88. package/dist/chunks/providers.mjs +2 -2
  89. package/dist/chunks/quick-actions.mjs +2 -2
  90. package/dist/chunks/quick-provider.mjs +6 -5
  91. package/dist/chunks/quick-setup.mjs +10 -10
  92. package/dist/chunks/remote.mjs +5 -5
  93. package/dist/chunks/session.mjs +2 -2
  94. package/dist/chunks/sessions.mjs +1 -1
  95. package/dist/chunks/silent-updater.mjs +1 -1
  96. package/dist/chunks/simple-config.mjs +1 -1
  97. package/dist/chunks/skill2.mjs +3 -3
  98. package/dist/chunks/skills-sync.mjs +4 -4
  99. package/dist/chunks/skills.mjs +3 -3
  100. package/dist/chunks/slash-commands.mjs +3 -3
  101. package/dist/chunks/startup.mjs +1 -1
  102. package/dist/chunks/stats.mjs +2 -2
  103. package/dist/chunks/status.mjs +2 -2
  104. package/dist/chunks/team.mjs +3 -3
  105. package/dist/chunks/thinking.mjs +4 -4
  106. package/dist/chunks/trace.mjs +2 -2
  107. package/dist/chunks/uninstall.mjs +8 -8
  108. package/dist/chunks/update.mjs +9 -9
  109. package/dist/chunks/upgrade-manager.mjs +3 -3
  110. package/dist/chunks/version-checker.mjs +4 -4
  111. package/dist/chunks/vim.mjs +3 -3
  112. package/dist/chunks/workflows.mjs +1 -1
  113. package/dist/chunks/wsl.mjs +1 -1
  114. package/dist/chunks/zero-config.mjs +3 -3
  115. package/dist/cli.mjs +56 -23
  116. package/dist/index.mjs +5 -5
  117. package/dist/shared/{ccjk.CCcQfbni.mjs → ccjk.B1TwPltj.mjs} +1 -1
  118. package/dist/shared/{ccjk.CePkJq2S.mjs → ccjk.BfIpomdz.mjs} +1 -1
  119. package/dist/shared/{ccjk.D8ZLYSZZ.mjs → ccjk.CXzjn01x.mjs} +1 -1
  120. package/dist/shared/{ccjk.Cjj8SVrn.mjs → ccjk.Cot9p9_n.mjs} +1 -1
  121. package/dist/shared/{ccjk.CvChMYvB.mjs → ccjk.DCw2WnZU.mjs} +1 -1
  122. package/dist/shared/{ccjk.DG_o24cZ.mjs → ccjk.DJdmgr2d.mjs} +1 -1
  123. package/dist/shared/{ccjk.BIxuVL3_.mjs → ccjk.DcKLglJQ.mjs} +2 -2
  124. package/dist/shared/{ccjk.DLLw-h4Y.mjs → ccjk.DfXjf8EC.mjs} +2 -2
  125. package/dist/shared/{ccjk.KpFl2RDA.mjs → ccjk.DpstNaeR.mjs} +3 -3
  126. package/dist/shared/{ccjk.DOBWBkFR.mjs → ccjk.XsJWJuQP.mjs} +5 -5
  127. package/dist/shared/{ccjk._dESH4Rk.mjs → ccjk.dYDLfmph.mjs} +1 -1
  128. package/dist/shared/{ccjk.DS7UESmF.mjs → ccjk.hrRv8G6j.mjs} +4 -4
  129. package/dist/shared/{ccjk.BWFpnOr3.mjs → ccjk.mJpVRDZ8.mjs} +1 -1
  130. package/dist/templates/claude-code/common/settings.json +3 -1
  131. package/package.json +20 -18
  132. package/templates/claude-code/common/settings.json +3 -1
  133. package/dist/chunks/context-loader.mjs +0 -351
  134. package/dist/chunks/context.mjs +0 -372
  135. package/dist/chunks/health-check.mjs +0 -532
@@ -1,1171 +1,679 @@
1
- import { execSync } from 'node:child_process';
2
- import * as path from 'node:path';
3
- import * as process from 'node:process';
4
- import * as fs from 'node:fs';
1
+ import { existsSync, readFileSync, readdirSync, statSync, mkdirSync, writeFileSync } from 'node:fs';
2
+ import { j as join, d as dirname } from '../shared/ccjk.bQ7Dh1g4.mjs';
3
+ import { homedir } from 'node:os';
4
+ import { fileURLToPath } from 'node:url';
5
5
 
6
- function getFixCommits(options) {
7
- const { since, until, limit = 100, cwd = process.cwd() } = options;
8
- let gitCmd = 'git log --pretty=format:"%H|%h|%s|%an|%ai" --name-only';
9
- if (since) {
10
- gitCmd += ` ${since}..${until || "HEAD"}`;
6
+ class ProjectAnalyzer {
7
+ projectRoot;
8
+ constructor(projectRoot = process.cwd()) {
9
+ this.projectRoot = projectRoot;
11
10
  }
12
- gitCmd += ` -n ${limit}`;
13
- try {
14
- const output = execSync(gitCmd, { cwd, encoding: "utf-8" });
15
- return parseGitLog(output);
16
- } catch {
17
- return [];
11
+ /**
12
+ * Analyze project and return comprehensive analysis
13
+ */
14
+ async analyze() {
15
+ const packageJson = this.readPackageJson();
16
+ const techStack = this.detectTechStack(packageJson);
17
+ const projectType = this.detectProjectType(techStack, packageJson);
18
+ const frameworks = this.detectFrameworks(packageJson);
19
+ const hasTests = this.detectTestFramework(packageJson);
20
+ const hasDatabase = this.detectDatabase(packageJson);
21
+ const hasApi = this.detectApiEndpoints();
22
+ const buildTool = this.detectBuildTool(packageJson);
23
+ const cicd = this.detectCICD();
24
+ const patterns = this.detectPatterns();
25
+ const confidence = this.calculateConfidence(techStack, frameworks);
26
+ return {
27
+ projectRoot: this.projectRoot,
28
+ projectType,
29
+ techStack,
30
+ frameworks,
31
+ hasTests,
32
+ hasDatabase,
33
+ hasApi,
34
+ buildTool,
35
+ cicd,
36
+ patterns,
37
+ confidence,
38
+ packageJson: packageJson ?? void 0
39
+ };
18
40
  }
19
- }
20
- function parseGitLog(output) {
21
- const commits = [];
22
- const entries = output.trim().split("\n\n");
23
- for (const entry of entries) {
24
- const lines = entry.split("\n");
25
- if (lines.length === 0)
26
- continue;
27
- const [firstLine, ...fileLines] = lines;
28
- const parts = firstLine.split("|");
29
- if (parts.length < 5)
30
- continue;
31
- const [hash, shortHash, message, author, date] = parts;
32
- const files = fileLines.filter((f) => f.trim());
33
- if (isFixCommit(message)) {
34
- commits.push({
35
- hash,
36
- shortHash,
37
- message,
38
- author,
39
- date,
40
- files
41
- });
41
+ /**
42
+ * Read and parse package.json
43
+ */
44
+ readPackageJson() {
45
+ const packageJsonPath = join(this.projectRoot, "package.json");
46
+ if (!existsSync(packageJsonPath)) {
47
+ return null;
48
+ }
49
+ try {
50
+ const content = readFileSync(packageJsonPath, "utf-8");
51
+ return JSON.parse(content);
52
+ } catch {
53
+ return null;
42
54
  }
43
55
  }
44
- return commits;
45
- }
46
- function isFixCommit(message) {
47
- const fixPatterns = [
48
- /^fix[(:]/i,
49
- /^bugfix[(:]/i,
50
- /^hotfix[(:]/i,
51
- /\bfix\b/i,
52
- /\bbug\b/i,
53
- /\brepair\b/i,
54
- /\bresolve\b/i,
55
- /\bcorrect\b/i,
56
- /修复/,
57
- /修正/,
58
- /解决/,
59
- /bug/i
60
- ];
61
- return fixPatterns.some((p) => p.test(message));
62
- }
63
- function analyzeFixCommit(commit, cwd = process.cwd()) {
64
- const diff = getCommitDiff(commit.hash, cwd);
65
- const bugType = detectBugType(commit.message, diff);
66
- const severity = detectSeverity(commit.message, diff, commit.files);
67
- const rootCause = extractRootCause(commit.message, diff);
68
- const solution = extractSolution(diff);
69
- const preventionSuggestions = generatePreventionSuggestions(bugType);
70
- return {
71
- commit,
72
- bugType,
73
- severity,
74
- rootCause,
75
- solution,
76
- preventionSuggestions,
77
- relatedPostmortems: []
78
- };
79
- }
80
- function getCommitDiff(hash, cwd) {
81
- try {
82
- return execSync(`git show ${hash} --pretty="" --patch`, {
83
- cwd,
84
- encoding: "utf-8",
85
- maxBuffer: 1024 * 1024 * 10
86
- // 10MB
87
- });
88
- } catch {
89
- return "";
90
- }
91
- }
92
- function detectBugType(message, diff) {
93
- const content = `${message}
94
- ${diff}`.toLowerCase();
95
- const patterns = [
96
- {
97
- category: "type-safety",
98
- patterns: [
99
- /null|undefined|cannot read|typeerror/,
100
- /类型|空值|未定义/,
101
- /optional chaining|\?\./,
102
- /strict.*null|null.*check/
103
- ]
104
- },
105
- {
106
- category: "error-handling",
107
- patterns: [
108
- /try.*catch|exception|throw|error.*handling/,
109
- /unhandled.*rejection|promise.*reject/,
110
- /异常|错误处理|捕获/
111
- ]
112
- },
113
- {
114
- category: "performance",
115
- patterns: [
116
- /performance|slow|timeout|memory|leak/,
117
- /optimize|optimization|cache/,
118
- /性能|优化|缓存|内存/
119
- ]
120
- },
121
- {
122
- category: "security",
123
- patterns: [
124
- /security|xss|csrf|injection|auth/,
125
- /vulnerability|exploit|sanitize/,
126
- /安全|漏洞|注入|认证/
127
- ]
128
- },
129
- {
130
- category: "race-condition",
131
- patterns: [
132
- /race.*condition|concurrent|async.*await/,
133
- /deadlock|mutex|lock/,
134
- /竞态|并发|死锁/
135
- ]
136
- },
137
- {
138
- category: "logic-error",
139
- patterns: [
140
- /logic|incorrect|wrong.*result/,
141
- /逻辑|错误结果|计算错误/
142
- ]
143
- },
144
- {
145
- category: "api-misuse",
146
- patterns: [
147
- /api.*usage|incorrect.*call|wrong.*parameter/,
148
- /接口|调用错误|参数错误/
149
- ]
150
- },
151
- {
152
- category: "configuration",
153
- patterns: [
154
- /config|setting|environment|env/,
155
- /配置|环境|设置/
156
- ]
157
- },
158
- {
159
- category: "dependency",
160
- patterns: [
161
- /dependency|package|version|upgrade/,
162
- /依赖|版本|升级/
163
- ]
56
+ /**
57
+ * Detect technology stack from dependencies
58
+ */
59
+ detectTechStack(packageJson) {
60
+ if (!packageJson) {
61
+ return {
62
+ languages: ["javascript"],
63
+ runtime: "node",
64
+ packageManager: this.detectPackageManager()
65
+ };
164
66
  }
165
- ];
166
- for (const { category, patterns: categoryPatterns } of patterns) {
167
- if (categoryPatterns.some((p) => p.test(content))) {
168
- return category;
67
+ const deps = {
68
+ ...packageJson.dependencies,
69
+ ...packageJson.devDependencies
70
+ };
71
+ const languages = [];
72
+ let runtime = "node";
73
+ if (deps.typescript || existsSync(join(this.projectRoot, "tsconfig.json"))) {
74
+ languages.push("typescript");
75
+ } else {
76
+ languages.push("javascript");
169
77
  }
170
- }
171
- return "other";
172
- }
173
- function detectSeverity(message, diff, files) {
174
- const content = `${message}
175
- ${diff}`.toLowerCase();
176
- if (/critical|crash|data.*loss|security.*vuln/i.test(content) || files.some((f) => /auth|security|payment/i.test(f))) {
177
- return "critical";
178
- }
179
- if (/breaking|major|important|urgent/i.test(content) || files.length > 10) {
180
- return "high";
181
- }
182
- if (/moderate|minor.*issue/i.test(content) || files.length > 3) {
183
- return "medium";
184
- }
185
- return "low";
186
- }
187
- function extractRootCause(message, diff) {
188
- const causePatterns = [
189
- /caused by[:\s]+(.+)/i,
190
- /root cause[:\s]+(.+)/i,
191
- /because[:\s]+(.+)/i,
192
- /due to[:\s]+(.+)/i,
193
- /原因[::]\s*(.+)/,
194
- /由于\s*(.+)/
195
- ];
196
- for (const pattern of causePatterns) {
197
- const match = message.match(pattern);
198
- if (match) {
199
- return match[1].trim();
78
+ if (existsSync(join(this.projectRoot, "deno.json")) || existsSync(join(this.projectRoot, "deno.jsonc"))) {
79
+ runtime = "deno";
200
80
  }
201
- }
202
- const removedLines = diff.match(/^-[^-].*/gm) || [];
203
- const addedLines = diff.match(/^\+[^+].*/gm) || [];
204
- if (removedLines.length > 0 && addedLines.length > 0) {
205
- return `\u4EE3\u7801\u53D8\u66F4: \u79FB\u9664 ${removedLines.length} \u884C, \u65B0\u589E ${addedLines.length} \u884C`;
206
- }
207
- return "\u9700\u8981\u8FDB\u4E00\u6B65\u5206\u6790";
208
- }
209
- function extractSolution(diff) {
210
- const addedLines = diff.match(/^\+[^+].*/gm) || [];
211
- if (addedLines.length === 0) {
212
- return "\u5220\u9664\u4E86\u95EE\u9898\u4EE3\u7801";
213
- }
214
- if (addedLines.length <= 5) {
215
- return addedLines.map((l) => l.substring(1)).join("\n");
216
- }
217
- return `\u65B0\u589E ${addedLines.length} \u884C\u4EE3\u7801\u4FEE\u590D\u95EE\u9898`;
218
- }
219
- function generatePreventionSuggestions(bugType, _diff) {
220
- const suggestions = {
221
- "type-safety": [
222
- "\u542F\u7528 TypeScript strict \u6A21\u5F0F",
223
- "\u4F7F\u7528\u53EF\u9009\u94FE\u64CD\u4F5C\u7B26 (?.) \u8FDB\u884C\u7A7A\u503C\u68C0\u67E5",
224
- "\u4E3A\u6240\u6709\u5916\u90E8\u6570\u636E\u6DFB\u52A0\u8FD0\u884C\u65F6\u9A8C\u8BC1",
225
- "\u4F7F\u7528 zod \u6216 io-ts \u8FDB\u884C\u7C7B\u578B\u9A8C\u8BC1"
226
- ],
227
- "error-handling": [
228
- "\u4E3A\u6240\u6709\u5F02\u6B65\u64CD\u4F5C\u6DFB\u52A0 try-catch",
229
- "\u5B9E\u73B0\u5168\u5C40\u9519\u8BEF\u5904\u7406\u4E2D\u95F4\u4EF6",
230
- "\u6DFB\u52A0\u9519\u8BEF\u8FB9\u754C\u7EC4\u4EF6 (React)",
231
- "\u4F7F\u7528 Result \u7C7B\u578B\u66FF\u4EE3\u5F02\u5E38"
232
- ],
233
- "performance": [
234
- "\u6DFB\u52A0\u6027\u80FD\u76D1\u63A7\u548C\u544A\u8B66",
235
- "\u5B9E\u73B0\u7F13\u5B58\u7B56\u7565",
236
- "\u4F7F\u7528\u61D2\u52A0\u8F7D\u548C\u4EE3\u7801\u5206\u5272",
237
- "\u5B9A\u671F\u8FDB\u884C\u6027\u80FD\u6D4B\u8BD5"
238
- ],
239
- "security": [
240
- "\u5B9E\u65BD\u5B89\u5168\u4EE3\u7801\u5BA1\u67E5",
241
- "\u4F7F\u7528\u5B89\u5168\u626B\u63CF\u5DE5\u5177",
242
- "\u9075\u5FAA OWASP \u5B89\u5168\u6307\u5357",
243
- "\u5B9A\u671F\u66F4\u65B0\u4F9D\u8D56"
244
- ],
245
- "race-condition": [
246
- "\u4F7F\u7528\u9002\u5F53\u7684\u9501\u673A\u5236",
247
- "\u907F\u514D\u5171\u4EAB\u53EF\u53D8\u72B6\u6001",
248
- "\u4F7F\u7528\u539F\u5B50\u64CD\u4F5C",
249
- "\u6DFB\u52A0\u5E76\u53D1\u6D4B\u8BD5"
250
- ],
251
- "logic-error": [
252
- "\u589E\u52A0\u5355\u5143\u6D4B\u8BD5\u8986\u76D6",
253
- "\u5B9E\u65BD\u4EE3\u7801\u5BA1\u67E5",
254
- "\u6DFB\u52A0\u65AD\u8A00\u548C\u4E0D\u53D8\u91CF\u68C0\u67E5",
255
- "\u4F7F\u7528\u5F62\u5F0F\u5316\u9A8C\u8BC1\u5DE5\u5177"
256
- ],
257
- "api-misuse": [
258
- "\u5B8C\u5584 API \u6587\u6863",
259
- "\u6DFB\u52A0\u53C2\u6570\u9A8C\u8BC1",
260
- "\u63D0\u4F9B\u4F7F\u7528\u793A\u4F8B",
261
- "\u5B9E\u73B0 API \u7248\u672C\u63A7\u5236"
262
- ],
263
- "configuration": [
264
- "\u4F7F\u7528\u914D\u7F6E\u9A8C\u8BC1",
265
- "\u63D0\u4F9B\u9ED8\u8BA4\u914D\u7F6E",
266
- "\u6587\u6863\u5316\u6240\u6709\u914D\u7F6E\u9879",
267
- "\u5B9E\u73B0\u914D\u7F6E\u70ED\u91CD\u8F7D"
268
- ],
269
- "dependency": [
270
- "\u9501\u5B9A\u4F9D\u8D56\u7248\u672C",
271
- "\u5B9A\u671F\u66F4\u65B0\u4F9D\u8D56",
272
- "\u4F7F\u7528\u4F9D\u8D56\u626B\u63CF\u5DE5\u5177",
273
- "\u6D4B\u8BD5\u4F9D\u8D56\u5347\u7EA7"
274
- ],
275
- "memory-leak": [
276
- "\u5B9E\u73B0\u8D44\u6E90\u6E05\u7406",
277
- "\u4F7F\u7528\u5185\u5B58\u5206\u6790\u5DE5\u5177",
278
- "\u907F\u514D\u5FAA\u73AF\u5F15\u7528",
279
- "\u5B9A\u671F\u8FDB\u884C\u5185\u5B58\u6D4B\u8BD5"
280
- ],
281
- "other": [
282
- "\u589E\u52A0\u6D4B\u8BD5\u8986\u76D6",
283
- "\u5B9E\u65BD\u4EE3\u7801\u5BA1\u67E5",
284
- "\u5B8C\u5584\u6587\u6863"
285
- ]
286
- };
287
- return suggestions[bugType] || suggestions.other;
288
- }
289
- function generatePostmortem(analyses, existingIds) {
290
- const grouped = groupByCategory(analyses);
291
- const reports = [];
292
- let nextId = getNextId(existingIds);
293
- for (const [category, categoryAnalyses] of Object.entries(grouped)) {
294
- const merged = mergeAnalyses(categoryAnalyses);
295
- for (const analysis of merged) {
296
- const id = `PM-${String(nextId++).padStart(3, "0")}`;
297
- const report = {
298
- id,
299
- title: generateTitle(analysis),
300
- severity: analysis.severity,
301
- category: analysis.bugType,
302
- status: "active",
303
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
304
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
305
- relatedCommits: [analysis.commit],
306
- affectedVersions: {
307
- from: "unknown",
308
- to: "unknown"
309
- },
310
- description: generateDescription(analysis),
311
- rootCause: [analysis.rootCause],
312
- solution: {
313
- description: analysis.solution
314
- },
315
- preventionMeasures: analysis.preventionSuggestions,
316
- aiDirectives: generateAiDirectives(analysis),
317
- detectionPatterns: generateDetectionPatterns(analysis),
318
- relatedFiles: analysis.commit.files,
319
- tags: [category, analysis.severity],
320
- metadata: {
321
- generatedBy: "ccjk-postmortem",
322
- version: "1.0.0"
323
- }
324
- };
325
- reports.push(report);
81
+ if (existsSync(join(this.projectRoot, "bun.lockb"))) {
82
+ runtime = "bun";
326
83
  }
84
+ return {
85
+ languages,
86
+ runtime,
87
+ packageManager: this.detectPackageManager()
88
+ };
327
89
  }
328
- return reports;
329
- }
330
- function groupByCategory(analyses) {
331
- const grouped = {};
332
- for (const analysis of analyses) {
333
- const category = analysis.bugType;
334
- if (!grouped[category]) {
335
- grouped[category] = [];
90
+ /**
91
+ * Detect package manager
92
+ */
93
+ detectPackageManager() {
94
+ if (existsSync(join(this.projectRoot, "pnpm-lock.yaml"))) {
95
+ return "pnpm";
336
96
  }
337
- grouped[category].push(analysis);
338
- }
339
- return grouped;
340
- }
341
- function mergeAnalyses(analyses) {
342
- const merged = [];
343
- for (const analysis of analyses) {
344
- const similar = merged.find(
345
- (m) => calculateFileOverlap(m.commit.files, analysis.commit.files) > 0.5
346
- );
347
- if (similar) {
348
- similar.commit.files = Array.from(/* @__PURE__ */ new Set([...similar.commit.files, ...analysis.commit.files]));
349
- similar.preventionSuggestions = Array.from(/* @__PURE__ */ new Set([...similar.preventionSuggestions, ...analysis.preventionSuggestions]));
350
- } else {
351
- merged.push({ ...analysis });
97
+ if (existsSync(join(this.projectRoot, "yarn.lock"))) {
98
+ return "yarn";
352
99
  }
353
- }
354
- return merged;
355
- }
356
- function calculateFileOverlap(files1, files2) {
357
- const set1 = new Set(files1);
358
- const set2 = new Set(files2);
359
- const intersection = Array.from(set1).filter((f) => set2.has(f));
360
- const union = /* @__PURE__ */ new Set([...files1, ...files2]);
361
- return intersection.length / union.size;
362
- }
363
- function getNextId(existingIds) {
364
- const numbers = existingIds.map((id) => Number.parseInt(id.replace("PM-", ""), 10)).filter((n) => !Number.isNaN(n));
365
- return numbers.length > 0 ? Math.max(...numbers) + 1 : 1;
366
- }
367
- function generateTitle(analysis) {
368
- const categoryTitles = {
369
- "type-safety": "\u7C7B\u578B\u5B89\u5168\u95EE\u9898",
370
- "error-handling": "\u9519\u8BEF\u5904\u7406\u7F3A\u5931",
371
- "performance": "\u6027\u80FD\u95EE\u9898",
372
- "security": "\u5B89\u5168\u6F0F\u6D1E",
373
- "race-condition": "\u7ADE\u6001\u6761\u4EF6",
374
- "logic-error": "\u903B\u8F91\u9519\u8BEF",
375
- "api-misuse": "API \u4F7F\u7528\u4E0D\u5F53",
376
- "configuration": "\u914D\u7F6E\u95EE\u9898",
377
- "dependency": "\u4F9D\u8D56\u95EE\u9898",
378
- "memory-leak": "\u5185\u5B58\u6CC4\u6F0F",
379
- "other": "\u5176\u4ED6\u95EE\u9898"
380
- };
381
- const baseTitle = categoryTitles[analysis.bugType] || "\u672A\u5206\u7C7B\u95EE\u9898";
382
- const message = analysis.commit.message;
383
- const specificPart = message.replace(/^(fix|bugfix|hotfix)[(:]\s*/i, "").split("\n")[0];
384
- if (specificPart && specificPart.length < 50) {
385
- return `${baseTitle}: ${specificPart}`;
386
- }
387
- return baseTitle;
388
- }
389
- function generateDescription(analysis) {
390
- return `
391
- \u5728 ${analysis.commit.date} \u53D1\u73B0\u5E76\u4FEE\u590D\u4E86\u4E00\u4E2A ${analysis.bugType} \u7C7B\u578B\u7684\u95EE\u9898\u3002
392
-
393
- **\u63D0\u4EA4\u4FE1\u606F**: ${analysis.commit.message}
394
-
395
- **\u5F71\u54CD\u6587\u4EF6**:
396
- ${analysis.commit.files.map((f) => `- ${f}`).join("\n")}
397
-
398
- **\u6839\u672C\u539F\u56E0**: ${analysis.rootCause}
399
- `.trim();
400
- }
401
- function generateAiDirectives(analysis) {
402
- const directives = [];
403
- const categoryDirectives = {
404
- "type-safety": [
405
- "\u5904\u7406\u5916\u90E8\u6570\u636E\u65F6\u5FC5\u987B\u8FDB\u884C\u7A7A\u503C\u68C0\u67E5",
406
- "\u4F7F\u7528 TypeScript \u4E25\u683C\u6A21\u5F0F",
407
- "\u907F\u514D\u4F7F\u7528 any \u7C7B\u578B"
408
- ],
409
- "error-handling": [
410
- "\u6240\u6709\u5F02\u6B65\u64CD\u4F5C\u5FC5\u987B\u6709\u9519\u8BEF\u5904\u7406",
411
- "\u63D0\u4F9B\u6709\u610F\u4E49\u7684\u9519\u8BEF\u6D88\u606F",
412
- "\u5B9E\u73B0\u4F18\u96C5\u964D\u7EA7"
413
- ],
414
- "performance": [
415
- "\u907F\u514D\u5728\u5FAA\u73AF\u4E2D\u8FDB\u884C I/O \u64CD\u4F5C",
416
- "\u4F7F\u7528\u9002\u5F53\u7684\u7F13\u5B58\u7B56\u7565",
417
- "\u6CE8\u610F\u5927\u6570\u636E\u96C6\u7684\u5904\u7406"
418
- ],
419
- "security": [
420
- "\u9A8C\u8BC1\u6240\u6709\u7528\u6237\u8F93\u5165",
421
- "\u4F7F\u7528\u53C2\u6570\u5316\u67E5\u8BE2",
422
- "\u4E0D\u8981\u5728\u65E5\u5FD7\u4E2D\u8BB0\u5F55\u654F\u611F\u4FE1\u606F"
423
- ],
424
- "race-condition": [
425
- "\u6CE8\u610F\u5F02\u6B65\u64CD\u4F5C\u7684\u6267\u884C\u987A\u5E8F",
426
- "\u4F7F\u7528\u9002\u5F53\u7684\u540C\u6B65\u673A\u5236",
427
- "\u907F\u514D\u5171\u4EAB\u53EF\u53D8\u72B6\u6001"
428
- ],
429
- "logic-error": [
430
- "\u6DFB\u52A0\u8FB9\u754C\u6761\u4EF6\u68C0\u67E5",
431
- "\u4F7F\u7528\u65AD\u8A00\u9A8C\u8BC1\u5047\u8BBE",
432
- "\u7F16\u5199\u5355\u5143\u6D4B\u8BD5\u8986\u76D6\u8FB9\u754C\u60C5\u51B5"
433
- ],
434
- "api-misuse": [
435
- "\u67E5\u9605 API \u6587\u6863\u786E\u8BA4\u6B63\u786E\u7528\u6CD5",
436
- "\u68C0\u67E5\u53C2\u6570\u7C7B\u578B\u548C\u8303\u56F4",
437
- "\u5904\u7406\u6240\u6709\u53EF\u80FD\u7684\u8FD4\u56DE\u503C"
438
- ],
439
- "configuration": [
440
- "\u63D0\u4F9B\u5408\u7406\u7684\u9ED8\u8BA4\u503C",
441
- "\u9A8C\u8BC1\u914D\u7F6E\u503C\u7684\u6709\u6548\u6027",
442
- "\u6587\u6863\u5316\u914D\u7F6E\u9009\u9879"
443
- ],
444
- "dependency": [
445
- "\u68C0\u67E5\u4F9D\u8D56\u7684\u517C\u5BB9\u6027",
446
- "\u9605\u8BFB\u66F4\u65B0\u65E5\u5FD7",
447
- "\u5728\u5347\u7EA7\u524D\u8FDB\u884C\u6D4B\u8BD5"
448
- ],
449
- "memory-leak": [
450
- "\u53CA\u65F6\u6E05\u7406\u4E0D\u518D\u4F7F\u7528\u7684\u8D44\u6E90",
451
- "\u907F\u514D\u5FAA\u73AF\u5F15\u7528",
452
- "\u4F7F\u7528 WeakMap/WeakSet"
453
- ],
454
- "other": [
455
- "\u4ED4\u7EC6\u5BA1\u67E5\u4EE3\u7801\u53D8\u66F4",
456
- "\u6DFB\u52A0\u9002\u5F53\u7684\u6D4B\u8BD5"
457
- ]
458
- };
459
- directives.push(...categoryDirectives[analysis.bugType] || categoryDirectives.other);
460
- for (const file of analysis.commit.files) {
461
- if (file.includes("api") || file.includes("service")) {
462
- directives.push(`\u4FEE\u6539 ${path.basename(file)} \u65F6\u6CE8\u610F API \u517C\u5BB9\u6027`);
100
+ if (existsSync(join(this.projectRoot, "bun.lockb"))) {
101
+ return "bun";
463
102
  }
464
- if (file.includes("config")) {
465
- directives.push(`\u4FEE\u6539\u914D\u7F6E\u6587\u4EF6\u65F6\u786E\u4FDD\u5411\u540E\u517C\u5BB9`);
103
+ if (existsSync(join(this.projectRoot, "package-lock.json"))) {
104
+ return "npm";
466
105
  }
106
+ return "npm";
467
107
  }
468
- return Array.from(new Set(directives));
469
- }
470
- function generateDetectionPatterns(analysis) {
471
- const patterns = [];
472
- const categoryPatterns = {
473
- "type-safety": [
474
- {
475
- type: "regex",
476
- pattern: "\\.\\w+\\.\\w+\\.\\w+(?!\\?)",
477
- description: "\u8FDE\u7EED\u5C5E\u6027\u8BBF\u95EE\u672A\u4F7F\u7528\u53EF\u9009\u94FE",
478
- fileTypes: [".ts", ".tsx", ".js", ".jsx"],
479
- severity: "medium"
480
- },
481
- {
482
- type: "regex",
483
- pattern: "as any",
484
- description: "\u4F7F\u7528 any \u7C7B\u578B\u65AD\u8A00",
485
- fileTypes: [".ts", ".tsx"],
486
- severity: "low"
487
- }
488
- ],
489
- "error-handling": [
490
- {
491
- type: "regex",
492
- pattern: "catch\\s*\\(\\s*\\w*\\s*\\)\\s*\\{\\s*\\}",
493
- description: "\u7A7A\u7684 catch \u5757",
494
- fileTypes: [".ts", ".tsx", ".js", ".jsx"],
495
- severity: "high"
496
- },
497
- {
498
- type: "regex",
499
- pattern: "\\.then\\([^)]+\\)(?!\\.catch)",
500
- description: "Promise \u672A\u5904\u7406 rejection",
501
- fileTypes: [".ts", ".tsx", ".js", ".jsx"],
502
- severity: "medium"
503
- }
504
- ],
505
- "performance": [
506
- {
507
- type: "regex",
508
- pattern: "for\\s*\\([^)]+\\)\\s*\\{[^}]*await",
509
- description: "\u5FAA\u73AF\u4E2D\u4F7F\u7528 await",
510
- fileTypes: [".ts", ".tsx", ".js", ".jsx"],
511
- severity: "medium"
512
- }
513
- ],
514
- "security": [
515
- {
516
- type: "regex",
517
- pattern: "innerHTML\\s*=",
518
- description: "\u76F4\u63A5\u8BBE\u7F6E innerHTML \u53EF\u80FD\u5BFC\u81F4 XSS",
519
- fileTypes: [".ts", ".tsx", ".js", ".jsx"],
520
- severity: "high"
521
- },
522
- {
523
- type: "regex",
524
- pattern: "eval\\s*\\(",
525
- description: "\u4F7F\u7528 eval \u5B58\u5728\u5B89\u5168\u98CE\u9669",
526
- fileTypes: [".ts", ".tsx", ".js", ".jsx"],
527
- severity: "critical"
528
- }
529
- ],
530
- "race-condition": [],
531
- "logic-error": [],
532
- "api-misuse": [],
533
- "configuration": [],
534
- "dependency": [],
535
- "memory-leak": [
536
- {
537
- type: "regex",
538
- pattern: "addEventListener\\([^)]+\\)(?![\\s\\S]*removeEventListener)",
539
- description: "\u6DFB\u52A0\u4E8B\u4EF6\u76D1\u542C\u5668\u4F46\u672A\u79FB\u9664",
540
- fileTypes: [".ts", ".tsx", ".js", ".jsx"],
541
- severity: "medium"
542
- }
543
- ],
544
- "other": []
545
- };
546
- patterns.push(...categoryPatterns[analysis.bugType] || []);
547
- return patterns;
548
- }
549
- const PostmortemAnalyzer = {
550
- getFixCommits,
551
- analyzeFixCommit,
552
- generatePostmortem
553
- };
554
-
555
- const DEFAULT_CONFIG = {
556
- enabled: true,
557
- directory: "./postmortem",
558
- autoSyncToClaudeMd: true,
559
- maxSyncItems: 10,
560
- minSyncSeverity: "medium",
561
- detection: {
562
- enabled: true,
563
- excludePatterns: ["node_modules/**", "dist/**", "*.test.*", "*.spec.*"],
564
- includePatterns: ["src/**/*.ts", "src/**/*.tsx"]
565
- },
566
- aiAnalysis: {
567
- provider: "claude"
568
- }
569
- };
570
- const INDEX_FILE = "index.json";
571
- const CLAUDE_MD_SECTION_START = "<!-- POSTMORTEM_START -->";
572
- const CLAUDE_MD_SECTION_END = "<!-- POSTMORTEM_END -->";
573
- class PostmortemManager {
574
- config;
575
- projectRoot;
576
- postmortemDir;
577
- constructor(projectRoot = process.cwd(), config = {}) {
578
- this.projectRoot = projectRoot;
579
- this.config = { ...DEFAULT_CONFIG, ...config };
580
- this.postmortemDir = path.join(projectRoot, this.config.directory);
108
+ /**
109
+ * Detect project type
110
+ */
111
+ detectProjectType(techStack, packageJson) {
112
+ if (!packageJson) {
113
+ return "unknown";
114
+ }
115
+ const deps = {
116
+ ...packageJson.dependencies,
117
+ ...packageJson.devDependencies
118
+ };
119
+ const hasFrontend = deps.react || deps.vue || deps["@angular/core"] || deps.svelte;
120
+ const hasBackend = deps.express || deps.fastify || deps.koa || deps["@nestjs/core"] || deps.hapi;
121
+ if (deps.next || deps.nuxt || deps["@remix-run/react"] || deps["@sveltejs/kit"]) {
122
+ return "fullstack";
123
+ }
124
+ if (packageJson.bin || deps.commander || deps.yargs || deps.cac) {
125
+ return "cli";
126
+ }
127
+ if (packageJson.main && !hasFrontend && !hasBackend) {
128
+ return "library";
129
+ }
130
+ if (hasFrontend && hasBackend) {
131
+ return "fullstack";
132
+ }
133
+ if (hasFrontend) {
134
+ return "frontend";
135
+ }
136
+ if (hasBackend) {
137
+ return "backend";
138
+ }
139
+ return "unknown";
581
140
  }
582
- // ==========================================================================
583
- // Initialization
584
- // ==========================================================================
585
141
  /**
586
- * 初始化 Postmortem 系统
142
+ * Detect frameworks
587
143
  */
588
- async init() {
589
- this.ensureDirectories();
590
- const commits = PostmortemAnalyzer.getFixCommits({
591
- limit: 200,
592
- cwd: this.projectRoot
593
- });
594
- if (commits.length === 0) {
595
- this.saveIndex(this.createEmptyIndex());
596
- return { created: 0, directory: this.postmortemDir };
144
+ detectFrameworks(packageJson) {
145
+ if (!packageJson) {
146
+ return [];
597
147
  }
598
- const analyses = commits.map(
599
- (commit) => PostmortemAnalyzer.analyzeFixCommit(commit, this.projectRoot)
600
- );
601
- const reports = PostmortemAnalyzer.generatePostmortem(analyses, []);
602
- for (const report of reports) {
603
- this.saveReport(report);
148
+ const deps = {
149
+ ...packageJson.dependencies,
150
+ ...packageJson.devDependencies
151
+ };
152
+ const frameworks = [];
153
+ if (deps.react)
154
+ frameworks.push("react");
155
+ if (deps.vue)
156
+ frameworks.push("vue");
157
+ if (deps["@angular/core"])
158
+ frameworks.push("angular");
159
+ if (deps.svelte)
160
+ frameworks.push("svelte");
161
+ if (deps.next)
162
+ frameworks.push("next.js");
163
+ if (deps.nuxt)
164
+ frameworks.push("nuxt");
165
+ if (deps["@remix-run/react"])
166
+ frameworks.push("remix");
167
+ if (deps["@sveltejs/kit"])
168
+ frameworks.push("sveltekit");
169
+ if (deps.express)
170
+ frameworks.push("express");
171
+ if (deps.fastify)
172
+ frameworks.push("fastify");
173
+ if (deps.koa)
174
+ frameworks.push("koa");
175
+ if (deps["@nestjs/core"])
176
+ frameworks.push("nestjs");
177
+ if (deps.hapi)
178
+ frameworks.push("hapi");
179
+ return frameworks;
180
+ }
181
+ /**
182
+ * Detect test framework
183
+ */
184
+ detectTestFramework(packageJson) {
185
+ if (!packageJson) {
186
+ return false;
604
187
  }
605
- this.updateIndex();
606
- if (this.config.autoSyncToClaudeMd) {
607
- await this.syncToClaudeMd();
188
+ const deps = {
189
+ ...packageJson.dependencies,
190
+ ...packageJson.devDependencies
191
+ };
192
+ return !!(deps.vitest || deps.jest || deps.mocha || deps.ava || deps["@playwright/test"] || deps.cypress);
193
+ }
194
+ /**
195
+ * Detect database usage
196
+ */
197
+ detectDatabase(packageJson) {
198
+ if (!packageJson) {
199
+ return false;
608
200
  }
609
- return { created: reports.length, directory: this.postmortemDir };
201
+ const deps = {
202
+ ...packageJson.dependencies,
203
+ ...packageJson.devDependencies
204
+ };
205
+ return !!(deps.prisma || deps["@prisma/client"] || deps.drizzle || deps["drizzle-orm"] || deps.typeorm || deps.sequelize || deps.mongoose || deps.pg || deps.mysql || deps.mysql2 || deps.sqlite3 || deps["better-sqlite3"]);
610
206
  }
611
207
  /**
612
- * 确保目录存在
208
+ * Detect API endpoints
613
209
  */
614
- ensureDirectories() {
615
- const dirs = [
616
- this.postmortemDir,
617
- path.join(this.postmortemDir, "categories"),
618
- path.join(this.postmortemDir, "summaries")
210
+ detectApiEndpoints() {
211
+ const apiDirs = [
212
+ "src/api",
213
+ "src/routes",
214
+ "api",
215
+ "routes",
216
+ "pages/api",
217
+ // Next.js
218
+ "app/api"
219
+ // Next.js App Router
619
220
  ];
620
- for (const dir of dirs) {
621
- if (!fs.existsSync(dir)) {
622
- fs.mkdirSync(dir, { recursive: true });
623
- }
624
- }
221
+ return apiDirs.some((dir) => existsSync(join(this.projectRoot, dir)));
625
222
  }
626
- // ==========================================================================
627
- // Report Management
628
- // ==========================================================================
629
223
  /**
630
- * 保存 Postmortem 报告
224
+ * Detect build tool
631
225
  */
632
- saveReport(report) {
633
- const filename = `${report.id}-${this.slugify(report.title)}.md`;
634
- const filepath = path.join(this.postmortemDir, filename);
635
- const content = this.renderReportToMarkdown(report);
636
- fs.writeFileSync(filepath, content, "utf-8");
637
- const jsonPath = path.join(this.postmortemDir, `${report.id}.json`);
638
- fs.writeFileSync(jsonPath, JSON.stringify(report, null, 2), "utf-8");
639
- return filepath;
226
+ detectBuildTool(packageJson) {
227
+ if (!packageJson) {
228
+ return void 0;
229
+ }
230
+ const deps = {
231
+ ...packageJson.dependencies,
232
+ ...packageJson.devDependencies
233
+ };
234
+ if (deps.vite || existsSync(join(this.projectRoot, "vite.config.ts"))) {
235
+ return "vite";
236
+ }
237
+ if (deps.webpack || existsSync(join(this.projectRoot, "webpack.config.js"))) {
238
+ return "webpack";
239
+ }
240
+ if (deps.rollup || existsSync(join(this.projectRoot, "rollup.config.js"))) {
241
+ return "rollup";
242
+ }
243
+ if (deps.esbuild) {
244
+ return "esbuild";
245
+ }
246
+ if (deps.turbo || existsSync(join(this.projectRoot, "turbo.json"))) {
247
+ return "turbo";
248
+ }
249
+ return void 0;
640
250
  }
641
251
  /**
642
- * 读取 Postmortem 报告
252
+ * Detect CI/CD configuration
643
253
  */
644
- getReport(id) {
645
- const jsonPath = path.join(this.postmortemDir, `${id}.json`);
646
- if (!fs.existsSync(jsonPath)) {
647
- return null;
254
+ detectCICD() {
255
+ const cicd = [];
256
+ if (existsSync(join(this.projectRoot, ".github", "workflows"))) {
257
+ cicd.push("github-actions");
648
258
  }
649
- try {
650
- const content = fs.readFileSync(jsonPath, "utf-8");
651
- return JSON.parse(content);
652
- } catch {
653
- return null;
259
+ if (existsSync(join(this.projectRoot, ".gitlab-ci.yml"))) {
260
+ cicd.push("gitlab-ci");
261
+ }
262
+ if (existsSync(join(this.projectRoot, ".circleci", "config.yml"))) {
263
+ cicd.push("circleci");
654
264
  }
265
+ if (existsSync(join(this.projectRoot, ".travis.yml"))) {
266
+ cicd.push("travis-ci");
267
+ }
268
+ return cicd;
655
269
  }
656
270
  /**
657
- * 列出所有 Postmortem
271
+ * Detect common patterns in the project
658
272
  */
659
- listReports() {
660
- const index = this.loadIndex();
661
- return index?.reports || [];
273
+ detectPatterns() {
274
+ const patterns = [];
275
+ if (existsSync(join(this.projectRoot, "packages")) || existsSync(join(this.projectRoot, "apps"))) {
276
+ patterns.push("monorepo");
277
+ }
278
+ if (existsSync(join(this.projectRoot, "tsconfig.json"))) {
279
+ patterns.push("typescript");
280
+ }
281
+ if (existsSync(join(this.projectRoot, ".eslintrc.js")) || existsSync(join(this.projectRoot, "eslint.config.js"))) {
282
+ patterns.push("eslint");
283
+ }
284
+ if (existsSync(join(this.projectRoot, ".prettierrc")) || existsSync(join(this.projectRoot, "prettier.config.js"))) {
285
+ patterns.push("prettier");
286
+ }
287
+ if (existsSync(join(this.projectRoot, "Dockerfile")) || existsSync(join(this.projectRoot, "docker-compose.yml"))) {
288
+ patterns.push("docker");
289
+ }
290
+ if (existsSync(join(this.projectRoot, ".env.example")) || existsSync(join(this.projectRoot, ".env.local"))) {
291
+ patterns.push("env-config");
292
+ }
293
+ return patterns;
662
294
  }
663
295
  /**
664
- * 渲染报告为 Markdown
296
+ * Calculate confidence score based on detected information
665
297
  */
666
- renderReportToMarkdown(report) {
667
- const severityEmoji = {
668
- critical: "\u{1F534}",
669
- high: "\u{1F7E0}",
670
- medium: "\u{1F7E1}",
671
- low: "\u{1F7E2}"
672
- };
673
- return `# ${report.id}: ${report.title}
674
-
675
- ## \u5143\u6570\u636E
676
- - **ID**: ${report.id}
677
- - **\u4E25\u91CD\u7A0B\u5EA6**: ${severityEmoji[report.severity]} ${report.severity.toUpperCase()}
678
- - **\u7C7B\u522B**: ${report.category}
679
- - **\u72B6\u6001**: ${report.status}
680
- - **\u521B\u5EFA\u65F6\u95F4**: ${report.createdAt}
681
- - **\u66F4\u65B0\u65F6\u95F4**: ${report.updatedAt}
682
-
683
- ## \u76F8\u5173\u63D0\u4EA4
684
- ${report.relatedCommits.map((c) => `- \`${c.shortHash}\` - ${c.message} (${c.author}, ${c.date})`).join("\n")}
685
-
686
- ## \u5F71\u54CD\u7248\u672C
687
- - **\u4ECE**: ${report.affectedVersions.from}
688
- - **\u5230**: ${report.affectedVersions.to}
689
-
690
- ## \u95EE\u9898\u63CF\u8FF0
691
- ${report.description}
692
-
693
- ## \u6839\u672C\u539F\u56E0
694
- ${report.rootCause.map((c) => `- ${c}`).join("\n")}
695
-
696
- ## \u4FEE\u590D\u65B9\u6848
697
- ${report.solution.description}
698
-
699
- ${report.solution.codeExample ? `
700
- ### \u4EE3\u7801\u793A\u4F8B
701
-
702
- **\u274C \u9519\u8BEF\u5199\u6CD5**
703
- \`\`\`typescript
704
- ${report.solution.codeExample.bad}
705
- \`\`\`
706
-
707
- **\u2705 \u6B63\u786E\u5199\u6CD5**
708
- \`\`\`typescript
709
- ${report.solution.codeExample.good}
710
- \`\`\`
711
- ` : ""}
712
-
713
- ## \u9884\u9632\u63AA\u65BD
714
- ${report.preventionMeasures.map((m, i) => `${i + 1}. ${m}`).join("\n")}
715
-
716
- ## AI \u5F00\u53D1\u6307\u4EE4
717
- > \u4EE5\u4E0B\u6307\u4EE4\u4F1A\u81EA\u52A8\u6CE8\u5165\u5230 CLAUDE.md \u4E2D\uFF0C\u6307\u5BFC AI \u5728\u5F00\u53D1\u65F6\u907F\u514D\u7C7B\u4F3C\u95EE\u9898
718
-
719
- ${report.aiDirectives.map((d) => `- ${d}`).join("\n")}
720
-
721
- ## \u68C0\u6D4B\u6A21\u5F0F
722
- ${report.detectionPatterns.length > 0 ? report.detectionPatterns.map((p) => `
723
- ### ${p.description}
724
- - **\u7C7B\u578B**: ${p.type}
725
- - **\u6A21\u5F0F**: \`${p.pattern}\`
726
- - **\u9002\u7528\u6587\u4EF6**: ${p.fileTypes.join(", ")}
727
- - **\u4E25\u91CD\u7A0B\u5EA6**: ${p.severity}
728
- `).join("\n") : "\u6682\u65E0\u81EA\u52A8\u68C0\u6D4B\u6A21\u5F0F"}
729
-
730
- ## \u76F8\u5173\u6587\u4EF6
731
- ${report.relatedFiles.map((f) => `- \`${f}\``).join("\n")}
732
-
733
- ## \u6807\u7B7E
734
- ${report.tags.map((t) => `\`${t}\``).join(" ")}
735
-
736
- ---
737
- *\u7531 CCJK Postmortem System \u81EA\u52A8\u751F\u6210*
738
- `;
298
+ calculateConfidence(techStack, frameworks) {
299
+ let confidence = 0.5;
300
+ if (techStack.languages.length > 0)
301
+ confidence += 0.1;
302
+ if (techStack.runtime !== "unknown")
303
+ confidence += 0.1;
304
+ if (techStack.packageManager)
305
+ confidence += 0.1;
306
+ if (frameworks.length > 0)
307
+ confidence += 0.1;
308
+ if (frameworks.length > 2)
309
+ confidence += 0.1;
310
+ return Math.min(confidence, 1);
739
311
  }
740
312
  /**
741
- * 生成 slug
313
+ * Get file count in directory
742
314
  */
743
- slugify(text) {
744
- return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").substring(0, 50);
315
+ getFileCount(dir) {
316
+ try {
317
+ if (!existsSync(dir)) {
318
+ return 0;
319
+ }
320
+ let count = 0;
321
+ const items = readdirSync(dir);
322
+ for (const item of items) {
323
+ const fullPath = join(dir, item);
324
+ const stat = statSync(fullPath);
325
+ if (stat.isDirectory()) {
326
+ count += this.getFileCount(fullPath);
327
+ } else {
328
+ count++;
329
+ }
330
+ }
331
+ return count;
332
+ } catch {
333
+ return 0;
334
+ }
335
+ }
336
+ }
337
+ async function analyzeProject(projectRoot) {
338
+ const analyzer = new ProjectAnalyzer(projectRoot);
339
+ return analyzer.analyze();
340
+ }
341
+
342
+ const __filename$1 = fileURLToPath(import.meta.url);
343
+ const __dirname$1 = dirname(__filename$1);
344
+ class ConfigGenerator {
345
+ templatesDir;
346
+ outputDir;
347
+ constructor(templatesDir, outputDir) {
348
+ this.templatesDir = templatesDir || join(__dirname$1, "..", "templates");
349
+ this.outputDir = outputDir || join(homedir(), ".config", "claude");
745
350
  }
746
- // ==========================================================================
747
- // Index Management
748
- // ==========================================================================
749
351
  /**
750
- * 创建空索引
352
+ * Generate configurations from template selection
751
353
  */
752
- createEmptyIndex() {
354
+ async generate(selection) {
355
+ const agentConfigs = await this.generateAgents(selection.agents);
356
+ const skillConfigs = await this.generateSkills(selection.skills);
753
357
  return {
754
- version: "1.0.0",
755
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
756
- stats: {
757
- total: 0,
758
- bySeverity: { critical: 0, high: 0, medium: 0, low: 0 },
759
- byCategory: {
760
- "type-safety": 0,
761
- "error-handling": 0,
762
- "performance": 0,
763
- "security": 0,
764
- "logic-error": 0,
765
- "race-condition": 0,
766
- "memory-leak": 0,
767
- "api-misuse": 0,
768
- "configuration": 0,
769
- "dependency": 0,
770
- "other": 0
771
- },
772
- byStatus: { active: 0, resolved: 0, monitoring: 0, archived: 0 }
773
- },
774
- reports: []
358
+ agents: agentConfigs,
359
+ skills: skillConfigs,
360
+ summary: this.generateSummary(agentConfigs, skillConfigs)
775
361
  };
776
362
  }
777
363
  /**
778
- * 加载索引
364
+ * Generate agent configurations
779
365
  */
780
- loadIndex() {
781
- const indexPath = path.join(this.postmortemDir, INDEX_FILE);
782
- if (!fs.existsSync(indexPath)) {
783
- return null;
784
- }
785
- try {
786
- const content = fs.readFileSync(indexPath, "utf-8");
787
- return JSON.parse(content);
788
- } catch {
789
- return null;
366
+ async generateAgents(agents) {
367
+ const configs = [];
368
+ for (const agent of agents) {
369
+ const templatePath = join(this.templatesDir, "agents", agent.file);
370
+ if (!existsSync(templatePath)) {
371
+ console.warn(`Agent template not found: ${agent.file}`);
372
+ continue;
373
+ }
374
+ const content = readFileSync(templatePath, "utf-8");
375
+ const outputPath = join(this.outputDir, "agents", agent.file);
376
+ configs.push({
377
+ id: agent.id,
378
+ path: outputPath,
379
+ content
380
+ });
790
381
  }
382
+ return configs;
791
383
  }
792
384
  /**
793
- * 保存索引
385
+ * Generate skill configurations
794
386
  */
795
- saveIndex(index) {
796
- const indexPath = path.join(this.postmortemDir, INDEX_FILE);
797
- fs.writeFileSync(indexPath, JSON.stringify(index, null, 2), "utf-8");
387
+ async generateSkills(skills) {
388
+ const configs = [];
389
+ for (const skill of skills) {
390
+ const templatePath = join(this.templatesDir, "skills", skill.file);
391
+ if (!existsSync(templatePath)) {
392
+ console.warn(`Skill template not found: ${skill.file}`);
393
+ continue;
394
+ }
395
+ const content = readFileSync(templatePath, "utf-8");
396
+ const outputPath = join(this.outputDir, "skills", skill.file);
397
+ configs.push({
398
+ id: skill.id,
399
+ path: outputPath,
400
+ content
401
+ });
402
+ }
403
+ return configs;
798
404
  }
799
405
  /**
800
- * 更新索引
406
+ * Write configurations to disk
801
407
  */
802
- updateIndex() {
803
- const index = this.createEmptyIndex();
804
- const files = fs.readdirSync(this.postmortemDir).filter((f) => f.startsWith("PM-") && f.endsWith(".json"));
805
- for (const file of files) {
806
- try {
807
- const content = fs.readFileSync(path.join(this.postmortemDir, file), "utf-8");
808
- const report = JSON.parse(content);
809
- index.stats.total++;
810
- index.stats.bySeverity[report.severity]++;
811
- index.stats.byCategory[report.category]++;
812
- index.stats.byStatus[report.status]++;
813
- index.reports.push({
814
- id: report.id,
815
- title: report.title,
816
- severity: report.severity,
817
- category: report.category,
818
- status: report.status,
819
- createdAt: report.createdAt,
820
- filePath: file.replace(".json", ".md")
821
- });
822
- } catch {
408
+ async write(config) {
409
+ const agentsDir = join(this.outputDir, "agents");
410
+ const skillsDir = join(this.outputDir, "skills");
411
+ if (!existsSync(agentsDir)) {
412
+ mkdirSync(agentsDir, { recursive: true });
413
+ }
414
+ if (!existsSync(skillsDir)) {
415
+ mkdirSync(skillsDir, { recursive: true });
416
+ }
417
+ for (const agent of config.agents) {
418
+ const dir = join(agent.path, "..");
419
+ if (!existsSync(dir)) {
420
+ mkdirSync(dir, { recursive: true });
421
+ }
422
+ writeFileSync(agent.path, agent.content, "utf-8");
423
+ }
424
+ for (const skill of config.skills) {
425
+ const dir = join(skill.path, "..");
426
+ if (!existsSync(dir)) {
427
+ mkdirSync(dir, { recursive: true });
823
428
  }
429
+ writeFileSync(skill.path, skill.content, "utf-8");
824
430
  }
825
- index.reports.sort((a, b) => {
826
- const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
827
- const severityDiff = severityOrder[a.severity] - severityOrder[b.severity];
828
- if (severityDiff !== 0)
829
- return severityDiff;
830
- return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
831
- });
832
- index.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
833
- this.saveIndex(index);
834
- return index;
835
431
  }
836
- // ==========================================================================
837
- // CLAUDE.md Integration
838
- // ==========================================================================
839
432
  /**
840
- * 同步到 CLAUDE.md
433
+ * Generate summary
841
434
  */
842
- async syncToClaudeMd() {
843
- const claudeMdPath = path.join(this.projectRoot, "CLAUDE.md");
844
- const injection = this.generateClaudeMdInjection();
845
- let content = "";
846
- if (fs.existsSync(claudeMdPath)) {
847
- content = fs.readFileSync(claudeMdPath, "utf-8");
435
+ generateSummary(agents, skills) {
436
+ const lines = [];
437
+ lines.push("# Generated Configuration Summary\n");
438
+ lines.push(`Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
439
+ `);
440
+ lines.push("## Agents\n");
441
+ for (const agent of agents) {
442
+ lines.push(`- ${agent.id}: ${agent.path}`);
848
443
  }
849
- const startIndex = content.indexOf(CLAUDE_MD_SECTION_START);
850
- const endIndex = content.indexOf(CLAUDE_MD_SECTION_END);
851
- if (startIndex !== -1 && endIndex !== -1) {
852
- content = content.substring(0, startIndex) + content.substring(endIndex + CLAUDE_MD_SECTION_END.length);
444
+ lines.push("\n## Skills\n");
445
+ for (const skill of skills) {
446
+ lines.push(`- ${skill.id}: ${skill.path}`);
853
447
  }
854
- const injectionContent = `
855
- ${CLAUDE_MD_SECTION_START}
856
- ${injection.content}
857
- ${CLAUDE_MD_SECTION_END}
858
- `;
859
- content = `${content.trim()}
448
+ lines.push("\n## Usage\n");
449
+ lines.push("1. Agents are automatically loaded by Claude Code");
450
+ lines.push("2. Skills can be triggered using their command triggers");
451
+ lines.push("3. Use `claude` command to start coding with AI assistance");
452
+ return lines.join("\n");
453
+ }
454
+ }
455
+ async function generateConfigs(selection) {
456
+ const generator = new ConfigGenerator();
457
+ return generator.generate(selection);
458
+ }
459
+ async function writeConfigs(config) {
460
+ const generator = new ConfigGenerator();
461
+ await generator.write(config);
462
+ }
860
463
 
861
- ${injectionContent.trim()}
862
- `;
863
- fs.writeFileSync(claudeMdPath, content, "utf-8");
464
+ const __filename = fileURLToPath(import.meta.url);
465
+ const __dirname = dirname(__filename);
466
+ class TemplateSelector {
467
+ templatesDir;
468
+ constructor(templatesDir) {
469
+ this.templatesDir = templatesDir || join(__dirname, "..", "..", "..", "templates");
470
+ }
471
+ /**
472
+ * Select templates based on project analysis
473
+ */
474
+ async select(analysis) {
475
+ const agents = await this.selectAgents(analysis);
476
+ const skills = await this.selectSkills(analysis);
864
477
  return {
865
- synced: injection.sourcePostmortems.length,
866
- claudeMdPath
478
+ agents,
479
+ skills,
480
+ reasoning: this.generateReasoning(analysis, agents, skills)
867
481
  };
868
482
  }
869
483
  /**
870
- * 生成 CLAUDE.md 注入内容
484
+ * Select agent templates
871
485
  */
872
- generateClaudeMdInjection() {
873
- const index = this.loadIndex();
874
- const reports = [];
875
- if (index) {
876
- const severityOrder = {
877
- critical: 0,
878
- high: 1,
879
- medium: 2,
880
- low: 3
881
- };
882
- const minSeverityOrder = severityOrder[this.config.minSyncSeverity];
883
- for (const meta of index.reports) {
884
- if (severityOrder[meta.severity] <= minSeverityOrder && meta.status === "active") {
885
- const report = this.getReport(meta.id);
886
- if (report) {
887
- reports.push(report);
888
- }
889
- }
890
- if (reports.length >= this.config.maxSyncItems) {
891
- break;
892
- }
893
- }
486
+ async selectAgents(analysis) {
487
+ const selectedAgents = [];
488
+ const agentIndex = this.loadAgentIndex();
489
+ switch (analysis.projectType) {
490
+ case "frontend":
491
+ selectedAgents.push(
492
+ ...this.findAgentsByCategory(agentIndex, "frontend")
493
+ );
494
+ break;
495
+ case "backend":
496
+ selectedAgents.push(
497
+ ...this.findAgentsByCategory(agentIndex, "backend")
498
+ );
499
+ break;
500
+ case "fullstack":
501
+ selectedAgents.push(
502
+ ...this.findAgentsByCategory(agentIndex, "fullstack")
503
+ );
504
+ selectedAgents.push(
505
+ ...this.findAgentsByCategory(agentIndex, "frontend").slice(0, 1),
506
+ ...this.findAgentsByCategory(agentIndex, "backend").slice(0, 1)
507
+ );
508
+ break;
509
+ case "cli":
510
+ case "library":
511
+ selectedAgents.push(
512
+ ...this.findAgentsByCategory(agentIndex, "backend")
513
+ );
514
+ break;
894
515
  }
895
- const lines = [
896
- "## \u26A0\uFE0F \u5DF2\u77E5\u95EE\u9898\u9884\u8B66 (Postmortem Intelligence)",
897
- "",
898
- "> \u57FA\u4E8E\u5386\u53F2 bug \u5206\u6790\u81EA\u52A8\u751F\u6210\uFF0C\u5E2E\u52A9\u907F\u514D\u91CD\u590D\u72AF\u9519",
899
- ""
900
- ];
901
- if (reports.length === 0) {
902
- lines.push("\u6682\u65E0\u9700\u8981\u5173\u6CE8\u7684\u95EE\u9898\u3002");
903
- } else {
904
- const critical = reports.filter((r) => r.severity === "critical");
905
- const high = reports.filter((r) => r.severity === "high");
906
- const medium = reports.filter((r) => r.severity === "medium");
907
- if (critical.length > 0) {
908
- lines.push("### \u{1F534} \u4E25\u91CD");
909
- for (const r of critical) {
910
- lines.push(`- **${r.id}**: ${r.title}`);
911
- lines.push(` - ${r.aiDirectives[0] || r.preventionMeasures[0]}`);
912
- }
913
- lines.push("");
914
- }
915
- if (high.length > 0) {
916
- lines.push("### \u{1F7E0} \u9AD8\u4F18\u5148\u7EA7");
917
- for (const r of high) {
918
- lines.push(`- **${r.id}**: ${r.title}`);
919
- lines.push(` - ${r.aiDirectives[0] || r.preventionMeasures[0]}`);
920
- }
921
- lines.push("");
922
- }
923
- if (medium.length > 0) {
924
- lines.push("### \u{1F7E1} \u4E2D\u4F18\u5148\u7EA7");
925
- for (const r of medium) {
926
- lines.push(`- **${r.id}**: ${r.title}`);
927
- }
928
- lines.push("");
929
- }
930
- lines.push("### \u{1F4CB} \u5F00\u53D1\u6307\u4EE4");
931
- const allDirectives = /* @__PURE__ */ new Set();
932
- for (const r of reports.slice(0, 5)) {
933
- for (const d of r.aiDirectives.slice(0, 2)) {
934
- allDirectives.add(d);
935
- }
936
- }
937
- for (const d of allDirectives) {
938
- lines.push(`- ${d}`);
939
- }
940
- lines.push("");
941
- lines.push(`> \u8BE6\u7EC6\u4FE1\u606F\u8BF7\u67E5\u770B \`${this.config.directory}/\` \u76EE\u5F55`);
516
+ if (analysis.hasTests) {
517
+ selectedAgents.push(
518
+ ...this.findAgentsByCategory(agentIndex, "testing")
519
+ );
942
520
  }
943
- return {
944
- sectionId: "postmortem-warnings",
945
- title: "\u5DF2\u77E5\u95EE\u9898\u9884\u8B66",
946
- content: lines.join("\n"),
947
- priority: 100,
948
- sourcePostmortems: reports.map((r) => r.id),
949
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
950
- };
521
+ if (analysis.cicd && analysis.cicd.length > 0) {
522
+ selectedAgents.push(
523
+ ...this.findAgentsByCategory(agentIndex, "devops")
524
+ );
525
+ }
526
+ if (analysis.hasApi || analysis.hasDatabase) {
527
+ selectedAgents.push(
528
+ ...this.findAgentsByCategory(agentIndex, "security")
529
+ );
530
+ }
531
+ return this.deduplicateAndSort(selectedAgents);
951
532
  }
952
- // ==========================================================================
953
- // Code Checking
954
- // ==========================================================================
955
533
  /**
956
- * 检查代码是否可能触发已知问题
534
+ * Select skill templates
957
535
  */
958
- async checkCode(options = {}) {
959
- const { files, staged } = options;
960
- let filesToCheck = [];
961
- if (files && files.length > 0) {
962
- filesToCheck = files;
963
- } else if (staged) {
964
- filesToCheck = this.getStagedFiles();
965
- } else {
966
- filesToCheck = this.getAllSourceFiles();
536
+ async selectSkills(analysis) {
537
+ const selectedSkills = [];
538
+ const skillIndex = this.loadSkillIndex();
539
+ selectedSkills.push(
540
+ ...this.findSkillsByCategory(skillIndex, "git")
541
+ );
542
+ selectedSkills.push(
543
+ ...this.findSkillsByCategory(skillIndex, "code-quality")
544
+ );
545
+ if (analysis.hasTests) {
546
+ selectedSkills.push(
547
+ ...this.findSkillsByIds(skillIndex, ["generate-tests"])
548
+ );
967
549
  }
968
- const issues = [];
969
- const index = this.loadIndex();
970
- if (!index) {
971
- return this.createEmptyCheckReport(filesToCheck.length);
550
+ if (analysis.hasApi) {
551
+ selectedSkills.push(
552
+ ...this.findSkillsByIds(skillIndex, ["api-docs"])
553
+ );
972
554
  }
973
- const patterns = [];
974
- for (const meta of index.reports) {
975
- if (meta.status !== "active")
976
- continue;
977
- const report = this.getReport(meta.id);
978
- if (!report)
979
- continue;
980
- for (const pattern of report.detectionPatterns) {
981
- patterns.push({ pattern, postmortemId: report.id });
982
- }
555
+ if (analysis.hasDatabase) {
556
+ selectedSkills.push(
557
+ ...this.findSkillsByIds(skillIndex, ["database-migration"])
558
+ );
983
559
  }
984
- for (const file of filesToCheck) {
985
- const fullPath = path.isAbsolute(file) ? file : path.join(this.projectRoot, file);
986
- if (!fs.existsSync(fullPath))
987
- continue;
988
- const content = fs.readFileSync(fullPath, "utf-8");
989
- const lines = content.split("\n");
990
- for (const { pattern, postmortemId } of patterns) {
991
- if (!pattern.fileTypes.some((ft) => file.endsWith(ft))) {
992
- continue;
993
- }
994
- if (pattern.type === "regex") {
995
- try {
996
- const regex = new RegExp(pattern.pattern, "g");
997
- for (let i = 0; i < lines.length; i++) {
998
- const line = lines[i];
999
- const matches = line.match(regex);
1000
- if (matches) {
1001
- issues.push({
1002
- file,
1003
- line: i + 1,
1004
- column: line.indexOf(matches[0]) + 1,
1005
- pattern,
1006
- postmortemId,
1007
- message: `\u53EF\u80FD\u89E6\u53D1 ${postmortemId}: ${pattern.description}`,
1008
- suggestion: `\u53C2\u8003 ${this.config.directory}/${postmortemId}.md`
1009
- });
1010
- }
1011
- }
1012
- } catch {
1013
- }
1014
- }
1015
- }
560
+ if (analysis.projectType === "frontend" || analysis.projectType === "fullstack") {
561
+ selectedSkills.push(
562
+ ...this.findSkillsByIds(skillIndex, ["performance-optimization"])
563
+ );
1016
564
  }
1017
- const summary = {
1018
- critical: issues.filter((i) => i.pattern.severity === "critical").length,
1019
- high: issues.filter((i) => i.pattern.severity === "high").length,
1020
- medium: issues.filter((i) => i.pattern.severity === "medium").length,
1021
- low: issues.filter((i) => i.pattern.severity === "low").length
1022
- };
1023
- return {
1024
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1025
- filesChecked: filesToCheck.length,
1026
- issuesFound: issues,
1027
- summary,
1028
- passed: summary.critical === 0 && summary.high === 0
1029
- };
565
+ if (analysis.packageJson) {
566
+ selectedSkills.push(
567
+ ...this.findSkillsByIds(skillIndex, ["dependency-update"])
568
+ );
569
+ }
570
+ selectedSkills.push(
571
+ ...this.findSkillsByIds(skillIndex, ["refactor-code"])
572
+ );
573
+ return this.deduplicateAndSort(selectedSkills);
1030
574
  }
1031
575
  /**
1032
- * 获取暂存的文件
576
+ * Load agent index
1033
577
  */
1034
- getStagedFiles() {
578
+ loadAgentIndex() {
1035
579
  try {
1036
- const output = execSync("git diff --cached --name-only", {
1037
- cwd: this.projectRoot,
1038
- encoding: "utf-8"
1039
- });
1040
- return output.trim().split("\n").filter(Boolean);
580
+ const indexPath = join(this.templatesDir, "agents", "index.json");
581
+ const content = readFileSync(indexPath, "utf-8");
582
+ return JSON.parse(content);
1041
583
  } catch {
1042
- return [];
584
+ return { templates: [] };
1043
585
  }
1044
586
  }
1045
587
  /**
1046
- * 获取所有源文件
588
+ * Load skill index
1047
589
  */
1048
- getAllSourceFiles() {
1049
- const files = [];
1050
- const walk = (dir) => {
1051
- const entries = fs.readdirSync(dir, { withFileTypes: true });
1052
- for (const entry of entries) {
1053
- const fullPath = path.join(dir, entry.name);
1054
- const relativePath = path.relative(this.projectRoot, fullPath);
1055
- if (this.config.detection.excludePatterns.some((p) => this.matchGlob(relativePath, p))) {
1056
- continue;
1057
- }
1058
- if (entry.isDirectory()) {
1059
- walk(fullPath);
1060
- } else if (entry.isFile()) {
1061
- if (this.config.detection.includePatterns.some((p) => this.matchGlob(relativePath, p))) {
1062
- files.push(relativePath);
1063
- }
1064
- }
1065
- }
1066
- };
1067
- walk(this.projectRoot);
1068
- return files;
590
+ loadSkillIndex() {
591
+ try {
592
+ const indexPath = join(this.templatesDir, "skills", "index.json");
593
+ const content = readFileSync(indexPath, "utf-8");
594
+ return JSON.parse(content);
595
+ } catch {
596
+ return { templates: [] };
597
+ }
1069
598
  }
1070
599
  /**
1071
- * 简单的 glob 匹配
600
+ * Find agents by category
1072
601
  */
1073
- matchGlob(filepath, pattern) {
1074
- const regexPattern = pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\?/g, ".");
1075
- return new RegExp(`^${regexPattern}$`).test(filepath);
602
+ findAgentsByCategory(index, category) {
603
+ return index.templates.filter((agent) => agent.category === category);
1076
604
  }
1077
605
  /**
1078
- * 创建空的检查报告
606
+ * Find skills by category
1079
607
  */
1080
- createEmptyCheckReport(filesChecked) {
1081
- return {
1082
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1083
- filesChecked,
1084
- issuesFound: [],
1085
- summary: { critical: 0, high: 0, medium: 0, low: 0 },
1086
- passed: true
1087
- };
608
+ findSkillsByCategory(index, category) {
609
+ return index.templates.filter((skill) => skill.category === category);
1088
610
  }
1089
- // ==========================================================================
1090
- // Release Summary
1091
- // ==========================================================================
1092
611
  /**
1093
- * 生成发布摘要
612
+ * Find skills by IDs
1094
613
  */
1095
- async generateReleaseSummary(options) {
1096
- const { version, since, until } = options;
1097
- const commits = PostmortemAnalyzer.getFixCommits({
1098
- since,
1099
- until,
1100
- cwd: this.projectRoot
1101
- });
1102
- const analyses = commits.map(
1103
- (c) => PostmortemAnalyzer.analyzeFixCommit(c, this.projectRoot)
1104
- );
1105
- const existingIds = this.listReports().map((r) => r.id);
1106
- const newReports = PostmortemAnalyzer.generatePostmortem(analyses, existingIds);
1107
- const newIds = [];
1108
- for (const report of newReports) {
1109
- report.affectedVersions = { from: since || "unknown", to: version };
1110
- this.saveReport(report);
1111
- newIds.push(report.id);
1112
- }
1113
- this.updateIndex();
1114
- const summary = {
1115
- version,
1116
- releaseDate: (/* @__PURE__ */ new Date()).toISOString(),
1117
- fixCommitCount: commits.length,
1118
- newPostmortems: newIds,
1119
- updatedPostmortems: [],
1120
- summary: this.generateReleaseSummaryText(commits, newReports),
1121
- keyLessons: this.extractKeyLessons(newReports)
1122
- };
1123
- const summaryPath = path.join(this.postmortemDir, "summaries", `${version}.json`);
1124
- fs.writeFileSync(summaryPath, JSON.stringify(summary, null, 2), "utf-8");
1125
- if (this.config.autoSyncToClaudeMd) {
1126
- await this.syncToClaudeMd();
1127
- }
1128
- return summary;
614
+ findSkillsByIds(index, ids) {
615
+ return index.templates.filter((skill) => ids.includes(skill.id));
1129
616
  }
1130
617
  /**
1131
- * 生成发布摘要文本
618
+ * Deduplicate and sort templates by priority
1132
619
  */
1133
- generateReleaseSummaryText(commits, reports) {
1134
- const lines = [
1135
- `\u672C\u6B21\u53D1\u5E03\u5305\u542B ${commits.length} \u4E2A bug \u4FEE\u590D\uFF0C\u751F\u6210\u4E86 ${reports.length} \u4E2A\u65B0\u7684 Postmortem \u62A5\u544A\u3002`,
1136
- ""
1137
- ];
1138
- if (reports.length > 0) {
1139
- lines.push("\u4E3B\u8981\u95EE\u9898\u7C7B\u578B:");
1140
- const categories = /* @__PURE__ */ new Map();
1141
- for (const r of reports) {
1142
- categories.set(r.category, (categories.get(r.category) || 0) + 1);
1143
- }
1144
- for (const [cat, count] of categories) {
1145
- lines.push(`- ${cat}: ${count} \u4E2A`);
620
+ deduplicateAndSort(templates) {
621
+ const seen = /* @__PURE__ */ new Set();
622
+ const unique = [];
623
+ for (const template of templates) {
624
+ if (!seen.has(template.id)) {
625
+ seen.add(template.id);
626
+ unique.push(template);
1146
627
  }
1147
628
  }
1148
- return lines.join("\n");
629
+ return unique.sort((a, b) => b.priority - a.priority);
1149
630
  }
1150
631
  /**
1151
- * 提取关键教训
632
+ * Generate reasoning for template selection
1152
633
  */
1153
- extractKeyLessons(reports) {
1154
- const lessons = /* @__PURE__ */ new Set();
1155
- for (const report of reports) {
1156
- for (const measure of report.preventionMeasures.slice(0, 2)) {
1157
- lessons.add(measure);
1158
- }
634
+ generateReasoning(analysis, agents, skills) {
635
+ const reasons = [];
636
+ reasons.push(`Detected ${analysis.projectType} project`);
637
+ if (analysis.frameworks.length > 0) {
638
+ reasons.push(`Using frameworks: ${analysis.frameworks.join(", ")}`);
639
+ }
640
+ reasons.push(`Tech stack: ${analysis.techStack.languages.join(", ")} on ${analysis.techStack.runtime}`);
641
+ if (analysis.hasTests) {
642
+ reasons.push("Tests detected - including test engineer and test generation");
643
+ }
644
+ if (analysis.hasDatabase) {
645
+ reasons.push("Database detected - including migration skills");
646
+ }
647
+ if (analysis.hasApi) {
648
+ reasons.push("API endpoints detected - including API documentation");
1159
649
  }
1160
- return Array.from(lessons).slice(0, 10);
650
+ if (analysis.cicd && analysis.cicd.length > 0) {
651
+ reasons.push(`CI/CD detected (${analysis.cicd.join(", ")}) - including DevOps engineer`);
652
+ }
653
+ reasons.push(`
654
+ Selected ${agents.length} agents and ${skills.length} skills`);
655
+ return reasons.join("\n");
1161
656
  }
1162
657
  }
1163
- let managerInstance = null;
1164
- function getPostmortemManager(projectRoot, config) {
1165
- if (!managerInstance || projectRoot) {
1166
- managerInstance = new PostmortemManager(projectRoot, config);
1167
- }
1168
- return managerInstance;
658
+ async function selectTemplates(analysis) {
659
+ const selector = new TemplateSelector();
660
+ return selector.select(analysis);
661
+ }
662
+
663
+ async function smartGenerate(projectRoot) {
664
+ const analysis = await analyzeProject(projectRoot);
665
+ const selection = await selectTemplates(analysis);
666
+ const config = await generateConfigs(selection);
667
+ return {
668
+ analysis,
669
+ selection,
670
+ config
671
+ };
672
+ }
673
+ async function smartGenerateAndInstall(projectRoot) {
674
+ const result = await smartGenerate(projectRoot);
675
+ await writeConfigs(result.config);
676
+ return result;
1169
677
  }
1170
678
 
1171
- export { PostmortemAnalyzer, PostmortemManager, getPostmortemManager };
679
+ export { ConfigGenerator, ProjectAnalyzer, TemplateSelector, analyzeProject, generateConfigs, selectTemplates, smartGenerate, smartGenerateAndInstall, writeConfigs };