@ats-cx/cx-cli 0.1.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/.env.example +8 -0
- package/README.md +119 -0
- package/bin/cx-cli.cjs +90 -0
- package/config/apm-provider.json +7 -0
- package/config/apm-provider.template.jsonc +48 -0
- package/config/diagnostic-rules.json +54 -0
- package/config/diagnostic-rules.template.jsonc +82 -0
- package/config/event-semantics.json +828 -0
- package/config/event-semantics.template.jsonc +13 -0
- package/config/toolkit.json +3 -0
- package/dist/apm-help.d.ts +5 -0
- package/dist/apm-help.js +108 -0
- package/dist/apm-output.d.ts +49 -0
- package/dist/apm-output.js +115 -0
- package/dist/budget.d.ts +14 -0
- package/dist/budget.js +16 -0
- package/dist/cli.d.ts +20 -0
- package/dist/cli.js +596 -0
- package/dist/context.d.ts +12 -0
- package/dist/context.js +20 -0
- package/dist/contract.d.ts +11 -0
- package/dist/contract.js +4 -0
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +8 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +8 -0
- package/dist/init.d.ts +17 -0
- package/dist/init.js +194 -0
- package/dist/output.d.ts +7 -0
- package/dist/output.js +7 -0
- package/dist/project-pull.d.ts +32 -0
- package/dist/project-pull.js +81 -0
- package/dist/report.d.ts +3 -0
- package/dist/report.js +42 -0
- package/dist/skills.d.ts +12 -0
- package/dist/skills.js +430 -0
- package/dist/tools/apm-tools.d.ts +115 -0
- package/dist/tools/apm-tools.js +329 -0
- package/dist/tools/conclusion-tools.d.ts +24 -0
- package/dist/tools/conclusion-tools.js +61 -0
- package/dist/tools/create-run.d.ts +61 -0
- package/dist/tools/create-run.js +273 -0
- package/dist/tools/log-tools.d.ts +68 -0
- package/dist/tools/log-tools.js +116 -0
- package/dist/tools/project-tools.d.ts +82 -0
- package/dist/tools/project-tools.js +139 -0
- package/dist/workspace.d.ts +51 -0
- package/dist/workspace.js +104 -0
- package/package.json +37 -0
- package/skills/apm-query/SKILL.md +304 -0
- package/skills/apm-query/agents/openai.yaml +10 -0
- package/skills/cx-cli-setup/SKILL.md +120 -0
- package/skills/cx-cli-setup/agents/openai.yaml +11 -0
- package/skills/editor-diagnostic/SKILL.md +255 -0
- package/skills/editor-diagnostic/agents/openai.yaml +13 -0
- package/skills/semantics-curation/SKILL.md +151 -0
- package/skills/semantics-curation/agents/openai.yaml +12 -0
package/dist/index.js
ADDED
package/dist/init.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CliResult } from "./output.js";
|
|
2
|
+
export interface InitOptions {
|
|
3
|
+
/** 从这里向上找 git 根作为工作区宿主。 */
|
|
4
|
+
cwd: string;
|
|
5
|
+
/** 工具箱根(由 CLI 自身安装位置推导:包根),用于读取 .env 模板与种子语义表。 */
|
|
6
|
+
toolkitRoot: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* `cx-cli init`:在宿主仓库根创建 .cx-cli 诊断工作区。
|
|
10
|
+
*
|
|
11
|
+
* 幂等:只补缺失的文件/目录;.env(凭据)与 event-semantics.json(策展成果)绝不覆盖。
|
|
12
|
+
* 已有 config.json 只定点移除无效或悬空的 toolkitConfigDir,其余键保留;.gitignore 每次重写为
|
|
13
|
+
* 当前模板,否则老工作区的 `*` 会把语义表挡在宿主仓库 git 之外。两者都记入 rewritten。
|
|
14
|
+
* 宿主不像编辑器仓库(缺 resource/ 或 src/apps/)只警告不阻塞。
|
|
15
|
+
* 退出码:0 成功(含警告与 .env 缺键)/ 1 找不到 git 根或文件系统错误。
|
|
16
|
+
*/
|
|
17
|
+
export declare function runInit(options: InitOptions): CliResult;
|
package/dist/init.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { isApmConfigured, loadConfig, SENTENCE } from "@ats-cx/cx-core";
|
|
4
|
+
import { UserFacingError } from "./errors.js";
|
|
5
|
+
import { formatOut, jsonOut } from "./output.js";
|
|
6
|
+
import { REQUIRED_ENV_KEYS } from "./project-pull.js";
|
|
7
|
+
import { centralSkillsWarning } from "./skills.js";
|
|
8
|
+
import { WORKSPACE_DIR_NAME } from "./workspace.js";
|
|
9
|
+
/**
|
|
10
|
+
* 工作区 .gitignore 模板:默认全屏蔽,只反向放行语义表与策展提案(spec 组件 D)。
|
|
11
|
+
*
|
|
12
|
+
* 用 `*` + 反向放行而不是逐项列举屏蔽项:工作区放着 DB 凭据与诊断产物,将来落进来的
|
|
13
|
+
* 新文件(用户自造的 .env 变体、后续版本新增的契约文件)必须默认进不了宿主仓库 git。
|
|
14
|
+
* 逐项列举是黑名单,漏一项就是凭据外泄;这里的白名单漏一项只是少留档一个文件。
|
|
15
|
+
*
|
|
16
|
+
* config.json 留在屏蔽内——它可能含钉死的本机路径,且产物布局是本机的事;进宿主 git 无意义。
|
|
17
|
+
* .gitignore 自身必须放行:同事 clone 后没有它,.env 就失去屏蔽。
|
|
18
|
+
* curation/ 要目录与内容各放行一行——父目录被 `*` 排除时,git 不会递归进去看内容。
|
|
19
|
+
*/
|
|
20
|
+
const WORKSPACE_GITIGNORE = `# 默认全屏蔽:工作区含 DB 凭据(.env)、本机绑定(config.json)与诊断产物
|
|
21
|
+
*
|
|
22
|
+
|
|
23
|
+
# 例外:语义表与策展提案随宿主仓库 git 留档
|
|
24
|
+
!.gitignore
|
|
25
|
+
!event-semantics.json
|
|
26
|
+
!curation/
|
|
27
|
+
!curation/**
|
|
28
|
+
`;
|
|
29
|
+
/**
|
|
30
|
+
* `cx-cli init`:在宿主仓库根创建 .cx-cli 诊断工作区。
|
|
31
|
+
*
|
|
32
|
+
* 幂等:只补缺失的文件/目录;.env(凭据)与 event-semantics.json(策展成果)绝不覆盖。
|
|
33
|
+
* 已有 config.json 只定点移除无效或悬空的 toolkitConfigDir,其余键保留;.gitignore 每次重写为
|
|
34
|
+
* 当前模板,否则老工作区的 `*` 会把语义表挡在宿主仓库 git 之外。两者都记入 rewritten。
|
|
35
|
+
* 宿主不像编辑器仓库(缺 resource/ 或 src/apps/)只警告不阻塞。
|
|
36
|
+
* 退出码:0 成功(含警告与 .env 缺键)/ 1 找不到 git 根或文件系统错误。
|
|
37
|
+
*/
|
|
38
|
+
export function runInit(options) {
|
|
39
|
+
try {
|
|
40
|
+
const cwd = resolve(options.cwd);
|
|
41
|
+
const hostRoot = findGitRoot(cwd);
|
|
42
|
+
if (!hostRoot) {
|
|
43
|
+
throw new UserFacingError(`错误: 从 ${cwd} 向上未找到 git 仓库根。\n请在编辑器源码仓库内运行 cx-cli init。`);
|
|
44
|
+
}
|
|
45
|
+
const warnings = [];
|
|
46
|
+
if (!existsSync(join(hostRoot, "resource")) || !existsSync(join(hostRoot, "src/apps"))) {
|
|
47
|
+
warnings.push(`宿主仓库 ${hostRoot} 缺少 resource/ 或 src/apps/,不像编辑器源码仓库;工作区照常创建。`);
|
|
48
|
+
}
|
|
49
|
+
const workspaceDir = join(hostRoot, WORKSPACE_DIR_NAME);
|
|
50
|
+
const created = [];
|
|
51
|
+
const skipped = [];
|
|
52
|
+
const rewritten = [];
|
|
53
|
+
mkdirSync(workspaceDir, { recursive: true });
|
|
54
|
+
for (const dir of ["runs", "data", "out"]) {
|
|
55
|
+
const path = join(workspaceDir, dir);
|
|
56
|
+
(existsSync(path) ? skipped : created).push(`${dir}/`);
|
|
57
|
+
mkdirSync(path, { recursive: true });
|
|
58
|
+
}
|
|
59
|
+
// 唯一的非幂等例外:老工作区里内容为 `*` 的 .gitignore 会把语义表挡在 git 外,
|
|
60
|
+
// 必须换成选择性模板,否则组件 D 的「宿主 git 承载语义历史」不成立。
|
|
61
|
+
writeFileSync(join(workspaceDir, ".gitignore"), WORKSPACE_GITIGNORE);
|
|
62
|
+
rewritten.push(".gitignore");
|
|
63
|
+
const workspaceConfig = {
|
|
64
|
+
runsDir: "runs",
|
|
65
|
+
projectHistoryDir: "data",
|
|
66
|
+
outDir: "out",
|
|
67
|
+
};
|
|
68
|
+
const configPath = join(workspaceDir, "config.json");
|
|
69
|
+
if (!existsSync(configPath)) {
|
|
70
|
+
writeFileSync(configPath, `${JSON.stringify(workspaceConfig, null, 2)}\n`);
|
|
71
|
+
created.push("config.json");
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
let existingConfig = null;
|
|
75
|
+
let invalidConfigJson = false;
|
|
76
|
+
try {
|
|
77
|
+
const parsed = JSON.parse(readFileSync(configPath, "utf8"));
|
|
78
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
79
|
+
existingConfig = parsed;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
invalidConfigJson = true;
|
|
84
|
+
}
|
|
85
|
+
const pointer = existingConfig?.toolkitConfigDir;
|
|
86
|
+
const resolvedPointer = typeof pointer === "string" && pointer.length > 0 ? resolve(workspaceDir, pointer) : null;
|
|
87
|
+
const hasPointer = existingConfig ? Object.hasOwn(existingConfig, "toolkitConfigDir") : false;
|
|
88
|
+
const toolkitConfigDir = join(resolve(options.toolkitRoot), "config");
|
|
89
|
+
if (invalidConfigJson) {
|
|
90
|
+
skipped.push("config.json");
|
|
91
|
+
warnings.push("config.json 不是合法 JSON,未修改;请手工修复或删除后重跑 cx-cli init。");
|
|
92
|
+
}
|
|
93
|
+
else if (existingConfig && hasPointer && (typeof pointer !== "string" || pointer.length === 0)) {
|
|
94
|
+
delete existingConfig.toolkitConfigDir;
|
|
95
|
+
writeFileSync(configPath, `${JSON.stringify(existingConfig, null, 2)}\n`);
|
|
96
|
+
rewritten.push("config.json");
|
|
97
|
+
warnings.push(`config.json 的 toolkitConfigDir 不是路径字符串,已移除;共享配置改用 ${toolkitConfigDir}。`);
|
|
98
|
+
}
|
|
99
|
+
else if (existingConfig &&
|
|
100
|
+
resolvedPointer &&
|
|
101
|
+
(!existsSync(resolvedPointer) || !statSync(resolvedPointer).isDirectory())) {
|
|
102
|
+
delete existingConfig.toolkitConfigDir;
|
|
103
|
+
writeFileSync(configPath, `${JSON.stringify(existingConfig, null, 2)}\n`);
|
|
104
|
+
rewritten.push("config.json");
|
|
105
|
+
warnings.push(`config.json 的 toolkitConfigDir 指向不存在的 ${resolvedPointer},已移除;共享配置改用 ${toolkitConfigDir}。`);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
skipped.push("config.json");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const envExample = join(resolve(options.toolkitRoot), ".env.example");
|
|
112
|
+
const envPath = join(workspaceDir, ".env");
|
|
113
|
+
if (existsSync(envPath)) {
|
|
114
|
+
skipped.push(".env");
|
|
115
|
+
}
|
|
116
|
+
else if (existsSync(envExample)) {
|
|
117
|
+
writeFileSync(envPath, readFileSync(envExample, "utf8"));
|
|
118
|
+
created.push(".env");
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
warnings.push(`工具箱缺少 ${envExample},未生成 .env 模板;请手工创建 ${envPath}。`);
|
|
122
|
+
}
|
|
123
|
+
const seedSemantics = join(resolve(options.toolkitRoot), "config", "event-semantics.json");
|
|
124
|
+
const semanticsPath = join(workspaceDir, "event-semantics.json");
|
|
125
|
+
if (existsSync(semanticsPath)) {
|
|
126
|
+
skipped.push("event-semantics.json");
|
|
127
|
+
}
|
|
128
|
+
else if (existsSync(seedSemantics)) {
|
|
129
|
+
copyFileSync(seedSemantics, semanticsPath);
|
|
130
|
+
created.push("event-semantics.json");
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
warnings.push(`工具箱缺少 ${seedSemantics},未生成工作区 event-semantics.json;诊断命令会因缺语义表报错。`);
|
|
134
|
+
}
|
|
135
|
+
const toolkitConfigDir = join(resolve(options.toolkitRoot), "config");
|
|
136
|
+
const apmState = loadConfig(toolkitConfigDir, { source: "toolkit" }).apmProviderState;
|
|
137
|
+
if (apmState?.kind === "invalid") {
|
|
138
|
+
warnings.push(`apm 配置无效:${apmState.error}`);
|
|
139
|
+
}
|
|
140
|
+
else if (!isApmConfigured(apmState)) {
|
|
141
|
+
warnings.push(SENTENCE(apmState.fillPath));
|
|
142
|
+
}
|
|
143
|
+
const skillsWarning = centralSkillsWarning(options.toolkitRoot);
|
|
144
|
+
if (skillsWarning) {
|
|
145
|
+
warnings.push(skillsWarning);
|
|
146
|
+
}
|
|
147
|
+
const missingEnvKeys = readMissingEnvKeys(envPath);
|
|
148
|
+
const hint = missingEnvKeys.length > 0
|
|
149
|
+
? `请在 ${envPath} 填入 ${missingEnvKeys.join(", ")}(数据库凭据向团队获取,只留本机、不入库),之后即可运行 cx-cli project pull。`
|
|
150
|
+
: apmState?.kind === "invalid"
|
|
151
|
+
? "工作区就绪;修好 apm 配置后可运行 run new / apm query。"
|
|
152
|
+
: !isApmConfigured(apmState)
|
|
153
|
+
? "工作区就绪;填好 apm 后可运行 run new / apm query。"
|
|
154
|
+
: "工作区就绪,可直接运行 cx-cli project pull / run new / apm query。";
|
|
155
|
+
return formatOut({ hostRoot, workspaceDir, created, skipped, rewritten, warnings, missingEnvKeys, hint });
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
if (error instanceof UserFacingError) {
|
|
159
|
+
return { exitCode: 1, stdout: error.message };
|
|
160
|
+
}
|
|
161
|
+
return jsonOut({ error: error instanceof Error ? error.message : String(error) }, 1);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** 向上找最近的 .git(目录或 worktree/submodule 的 .git 文件都算)。 */
|
|
165
|
+
function findGitRoot(start) {
|
|
166
|
+
for (let current = start;;) {
|
|
167
|
+
if (existsSync(join(current, ".git"))) {
|
|
168
|
+
return current;
|
|
169
|
+
}
|
|
170
|
+
const parent = dirname(current);
|
|
171
|
+
if (parent === current) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
current = parent;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* 静态检查 .env 缺哪些必填键(map 决策 10:只提示、不代填)。
|
|
179
|
+
* 不用 process.loadEnvFile:它写进程级 env 且已有同名变量优先,这里只关心文件本身的内容。
|
|
180
|
+
*/
|
|
181
|
+
function readMissingEnvKeys(envPath) {
|
|
182
|
+
if (!existsSync(envPath)) {
|
|
183
|
+
return [...REQUIRED_ENV_KEYS];
|
|
184
|
+
}
|
|
185
|
+
const values = new Map();
|
|
186
|
+
for (const line of readFileSync(envPath, "utf8").split(/\r?\n/)) {
|
|
187
|
+
const match = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=(.*)$/.exec(line);
|
|
188
|
+
if (match) {
|
|
189
|
+
values.set(match[1], match[2].trim());
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return REQUIRED_ENV_KEYS.filter(key => !values.get(key));
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=init.js.map
|
package/dist/output.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** exit code 约定:0 成功 / 1 业务失败(含 finalize 校验失败、run 不存在)/ 2 参数错误。 */
|
|
2
|
+
export interface CliResult {
|
|
3
|
+
exitCode: 0 | 1 | 2;
|
|
4
|
+
stdout: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function jsonOut(data: unknown, exitCode?: CliResult["exitCode"]): CliResult;
|
|
7
|
+
export declare function formatOut(data: unknown, exitCode?: CliResult["exitCode"]): CliResult;
|
package/dist/output.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ConfigSource, RemoteProjectPullResult } from "@ats-cx/cx-core";
|
|
2
|
+
import type { CliResult } from "./output.js";
|
|
3
|
+
import type { Workspace } from "./workspace.js";
|
|
4
|
+
/** project pull 必填的 .env 键(DB_PORT 缺省 3306 不算必填);init 的缺键提示复用同一清单。 */
|
|
5
|
+
export declare const REQUIRED_ENV_KEYS: readonly ["DB_HOST", "DB_USER", "DB_PASSWORD", "DB_NAME"];
|
|
6
|
+
export interface ProjectPullOptions {
|
|
7
|
+
history: boolean;
|
|
8
|
+
/** .env 路径,由调用方按优先级选定(--env > 工作区 .env > 机器级 .env)。 */
|
|
9
|
+
envPath: string;
|
|
10
|
+
/** 错误提示中的包内 .env 模板绝对路径。 */
|
|
11
|
+
envExamplePath: string;
|
|
12
|
+
configDir: string;
|
|
13
|
+
configSource: ConfigSource;
|
|
14
|
+
/** 命中的工作区;快照落盘路径(projectHistoryDir)随之落工作区内。 */
|
|
15
|
+
workspace?: Workspace | null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* `project pull`:唯一直连 DB 的命令。
|
|
19
|
+
*
|
|
20
|
+
* 退出码:0 全部成功 / 1 业务失败(.env 缺失或缺键、连接或 SQL 失败、全部 id 都查到 0 条、有记录落盘失败)。
|
|
21
|
+
* 单个 id 查到 0 条只记警告继续——多 id 拉取时不该被一个空项目拖垮。
|
|
22
|
+
*/
|
|
23
|
+
export declare function runProjectPull(projectIds: string[], options: ProjectPullOptions): Promise<CliResult>;
|
|
24
|
+
/**
|
|
25
|
+
* 单个 projectId 查到 0 条只是警告;全部 id 都空,或有记录落盘失败,才算这次 pull 失败。
|
|
26
|
+
* 单独成函数是为了让这段退出码语义可直接单测(成功路径需要真实 DB,测不到)。
|
|
27
|
+
*
|
|
28
|
+
* 空 projects 列表是「没有要拉的项目」,不是「全部为空」——`every` 对空数组返回 true,
|
|
29
|
+
* 不先挡掉会落进失败分支返回一个没有任何 warning 解释的 exit 1。
|
|
30
|
+
* 参数合法性由 commander 负责(缺参数已 exit 2),这里只描述拉取结果。
|
|
31
|
+
*/
|
|
32
|
+
export declare function pullExitCode(result: RemoteProjectPullResult): 0 | 1;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { createMysqlClient, createRemoteProjectProvider, loadConfig } from "@ats-cx/cx-core";
|
|
4
|
+
import { UserFacingError } from "./errors.js";
|
|
5
|
+
import { formatOut, jsonOut } from "./output.js";
|
|
6
|
+
import { applyWorkspace } from "./workspace.js";
|
|
7
|
+
/** project pull 必填的 .env 键(DB_PORT 缺省 3306 不算必填);init 的缺键提示复用同一清单。 */
|
|
8
|
+
export const REQUIRED_ENV_KEYS = ["DB_HOST", "DB_USER", "DB_PASSWORD", "DB_NAME"];
|
|
9
|
+
/**
|
|
10
|
+
* `project pull`:唯一直连 DB 的命令。
|
|
11
|
+
*
|
|
12
|
+
* 退出码:0 全部成功 / 1 业务失败(.env 缺失或缺键、连接或 SQL 失败、全部 id 都查到 0 条、有记录落盘失败)。
|
|
13
|
+
* 单个 id 查到 0 条只记警告继续——多 id 拉取时不该被一个空项目拖垮。
|
|
14
|
+
*/
|
|
15
|
+
export async function runProjectPull(projectIds, options) {
|
|
16
|
+
try {
|
|
17
|
+
const dbConfig = loadDbConfig(options.envPath, options.envExamplePath);
|
|
18
|
+
const shared = loadConfig(options.configDir, { source: options.configSource });
|
|
19
|
+
const { projectHistoryDir } = options.workspace ? applyWorkspace(shared, options.workspace) : shared;
|
|
20
|
+
const client = await createMysqlClient(dbConfig);
|
|
21
|
+
let result;
|
|
22
|
+
try {
|
|
23
|
+
const provider = createRemoteProjectProvider(client, projectHistoryDir);
|
|
24
|
+
result = options.history ? await provider.pullHistory(projectIds) : await provider.pullLatest(projectIds);
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
await client.close();
|
|
28
|
+
}
|
|
29
|
+
return formatOut({ mode: options.history ? "history" : "latest", projectHistoryDir, ...result }, pullExitCode(result));
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (error instanceof UserFacingError) {
|
|
33
|
+
return { exitCode: 1, stdout: error.message };
|
|
34
|
+
}
|
|
35
|
+
return jsonOut({ error: error instanceof Error ? error.message : String(error) }, 1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 单个 projectId 查到 0 条只是警告;全部 id 都空,或有记录落盘失败,才算这次 pull 失败。
|
|
40
|
+
* 单独成函数是为了让这段退出码语义可直接单测(成功路径需要真实 DB,测不到)。
|
|
41
|
+
*
|
|
42
|
+
* 空 projects 列表是「没有要拉的项目」,不是「全部为空」——`every` 对空数组返回 true,
|
|
43
|
+
* 不先挡掉会落进失败分支返回一个没有任何 warning 解释的 exit 1。
|
|
44
|
+
* 参数合法性由 commander 负责(缺参数已 exit 2),这里只描述拉取结果。
|
|
45
|
+
*/
|
|
46
|
+
export function pullExitCode(result) {
|
|
47
|
+
if (result.projects.length === 0) {
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
const failed = result.projects.some(project => project.failures.length > 0);
|
|
51
|
+
const allEmpty = result.projects.every(project => project.snapshotCount === 0);
|
|
52
|
+
return failed || allEmpty ? 1 : 0;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 只有 project 命令组加载 .env(诊断命令不碰 DB)。用 Node 原生 loadEnvFile,不引 dotenv。
|
|
56
|
+
* 注意其语义:进程已有的同名环境变量优先于文件值。
|
|
57
|
+
*/
|
|
58
|
+
function loadDbConfig(envPath, envExamplePath) {
|
|
59
|
+
const file = resolve(envPath);
|
|
60
|
+
const template = resolve(envExamplePath);
|
|
61
|
+
if (!existsSync(file)) {
|
|
62
|
+
throw new UserFacingError(`错误: 未找到 .env 文件 ${file}。\n请填入数据库连接信息(模板见 ${template}),或用 --env 指定路径。`);
|
|
63
|
+
}
|
|
64
|
+
process.loadEnvFile(file);
|
|
65
|
+
const missing = REQUIRED_ENV_KEYS.filter(key => !process.env[key]);
|
|
66
|
+
if (missing.length > 0) {
|
|
67
|
+
throw new UserFacingError(`错误: ${file} 缺少数据库连接配置: ${missing.join(", ")}。\n请补齐数据库连接配置(模板见 ${template})。`);
|
|
68
|
+
}
|
|
69
|
+
const port = process.env.DB_PORT ? Number(process.env.DB_PORT) : 3306;
|
|
70
|
+
if (!Number.isInteger(port) || port <= 0) {
|
|
71
|
+
throw new UserFacingError(`错误: ${file} 的 DB_PORT 不是合法端口: ${process.env.DB_PORT}。`);
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
host: String(process.env.DB_HOST),
|
|
75
|
+
user: String(process.env.DB_USER),
|
|
76
|
+
password: String(process.env.DB_PASSWORD),
|
|
77
|
+
database: String(process.env.DB_NAME),
|
|
78
|
+
port,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=project-pull.js.map
|
package/dist/report.d.ts
ADDED
package/dist/report.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/** 按 spec §10 的固定六段渲染 report.md,头部声明源码版本漂移风险。 */
|
|
4
|
+
export function renderReport(runDir, result, complaint) {
|
|
5
|
+
const sourceVersions = existsSync(join(runDir, "source-versions.json"))
|
|
6
|
+
? JSON.parse(readFileSync(join(runDir, "source-versions.json"), "utf8"))
|
|
7
|
+
: [];
|
|
8
|
+
const versionNote = sourceVersions.length
|
|
9
|
+
? sourceVersions
|
|
10
|
+
.map(v => `- ${v.repoId}: ${v.reachable ? `commit ${v.commit ?? "未知"}${v.dirty ? "(工作区有未提交改动)" : ""}` : "不可达"}`)
|
|
11
|
+
.join("\n")
|
|
12
|
+
: "- 无源码仓库参与本次诊断";
|
|
13
|
+
const listOrNone = (items) => (items.length ? items.map(item => `- ${item}`).join("\n") : "- 无");
|
|
14
|
+
return [
|
|
15
|
+
`# 诊断报告 ${result.runId}`,
|
|
16
|
+
"",
|
|
17
|
+
`> 源码证据基于以下版本,与事发时间可能存在版本漂移:`,
|
|
18
|
+
versionNote,
|
|
19
|
+
"",
|
|
20
|
+
"## Case 摘要",
|
|
21
|
+
`- projectId: ${result.projectId}`,
|
|
22
|
+
`- 用户反馈: ${complaint || "(未提供)"}`,
|
|
23
|
+
`- 诊断状态: ${result.diagnosisStatus}`,
|
|
24
|
+
"",
|
|
25
|
+
"## 已确认事实",
|
|
26
|
+
listOrNone(result.confirmedFacts.map(fact => `${fact.description}(证据: ${fact.evidenceRefs.join(", ") || "无"})`)),
|
|
27
|
+
"",
|
|
28
|
+
"## 最可能根因",
|
|
29
|
+
listOrNone(result.rootCauses.map(cause => `[${cause.confidence}] ${cause.description}(证据: ${cause.evidenceRefs.join(", ") || "无"})`)),
|
|
30
|
+
"",
|
|
31
|
+
"## 关键证据",
|
|
32
|
+
listOrNone(result.evidenceRefs),
|
|
33
|
+
"",
|
|
34
|
+
"## 仍需补充的证据",
|
|
35
|
+
listOrNone(result.missingEvidence),
|
|
36
|
+
"",
|
|
37
|
+
"## 建议下一步",
|
|
38
|
+
listOrNone(result.recommendedNextActions),
|
|
39
|
+
"",
|
|
40
|
+
].join("\n");
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=report.js.map
|
package/dist/skills.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CliResult } from "./output.js";
|
|
2
|
+
export declare const SKILLS_INSTALL_HELP = "Usage: cx-cli skills install\n\n\u628A\u5305\u5185 skills/<name> \u8F6F\u94FE\u8FDB\u672C\u673A Agent\uFF08\u4E24\u6BB5\u94FE\uFF09:\n <\u5305\u6839>/skills/<name> -> ~/.agents/skills/<name> \u4E2D\u592E skills \u5E93\uFF1BCodex \u76F4\u63A5\u8BFB\u8FD9\u91CC\n ~/.agents/skills/<name> -> ~/.claude/skills/<name> \u4EC5\u5F53 ~/.claude/ \u5B58\u5728\uFF1BClaude Code \u53EA\u8BFB\u8FD9\u91CC\n\u5E76\u521B\u5EFA\u673A\u5668\u7EA7\u8986\u76D6\u76EE\u5F55 ~/.config/cx-cli/\uFF08\u9075 $XDG_CONFIG_HOME\uFF09\uFF0C\u7F3A apm-provider.local.json \u5219\u843D\u9AA8\u67B6\uFF08\u5DF2\u6709\u4E0D\u8986\u76D6\uFF09\u3002\n\n\u5E42\u7B49: \u5DF2\u6307\u5411\u5F53\u524D\u5305\u7684\u94FE\u4E0D\u52A8\uFF1B\u6307\u5411\u522B\u5904\u6216\u60AC\u7A7A\u7684\u8F6F\u94FE\u91CD\u6307\uFF08\u8F93\u51FA updated\uFF09\uFF1B\u76EE\u6807\u662F\u771F\u5B9E\u6587\u4EF6/\u76EE\u5F55\u5219\u8DF3\u8FC7\u5E76\u8B66\u544A\uFF0C\u4E0D\u5220\u3002\n\u53EA\u652F\u6301 npm \u5168\u5C40\u5B89\u88C5\uFF08npm i -g @ats-cx/cx-cli\uFF09\u4E0E\u7EF4\u62A4\u8005\u7684 pnpm link\uFF1Bnpx \u7F13\u5B58\u4E0E pnpm add -g \u4E0B\u62D2\u7EDD\uFF08exit 1\uFF09\u3002\n\u88C5\u597D\u540E Agent \u65E0\u9700\u91CD\u542F\uFF1B\u5305\u5347\u7EA7\u540E\u65B0\u589E\u7684 skill \u9700\u91CD\u8DD1\u672C\u547D\u4EE4\uFF1B\u66F4\u540D / \u5220\u9664\u7684\u65E7\u94FE\u7531\u672C\u547D\u4EE4\u81EA\u52A8\u6E05\u7406\uFF08pruned\uFF09\u3002\n\n\u8F93\u51FA:\n { packageRoot, packageVersion, centralDir, agents, skills, created, updated, skipped, pruned, configDir, warnings, hint }";
|
|
3
|
+
export declare const SKILLS_STATUS_HELP = "Usage: cx-cli skills status\n\n\u68C0\u67E5\u5305\u5185\u6BCF\u4E2A skill \u7684\u4E24\u6BB5\u94FE\uFF08\u4E2D\u592E\u5E93 ~/.agents/skills/<name>\uFF1B~/.claude/ \u5B58\u5728\u65F6\u518D\u67E5 ~/.claude/skills/<name>\uFF09\n\u4E0E\u673A\u5668\u7EA7\u8986\u76D6\u76EE\u5F55 ~/.config/cx-cli/ \u662F\u5426\u5C31\u7EEA\u3002\u94FE\u72B6\u6001: linked / stale\uFF08\u6307\u5411\u522B\u5904\uFF09/ dangling\uFF08\u60AC\u7A7A\uFF09/ missing / occupied\uFF08\u88AB\u771F\u5B9E\u76EE\u5F55\u5360\u7528\uFF09\u3002\n\u5168\u90E8 linked \u624D ok\uFF1B\u5426\u5219 exit 1 \u5E76\u5728 issues \u5217\u51FA\u3002apm \u672A\u914D\u7F6E\u53EA\u8FDB hint\uFF0C\u4E0D\u5F71\u54CD ok\u3002\n\n\u8F93\u51FA:\n { ok, packageRoot, packageVersion, centralDir, agents, skills: [{ name, central, claude? }], orphans, issues, configDir, hint }";
|
|
4
|
+
export declare const SKILLS_UNINSTALL_HELP = "Usage: cx-cli skills uninstall\n\n\u89E3\u9664\u672C\u5305\u7684 skills \u94FE\u63A5: \u5220 ~/.claude/skills/<name>\u3001~/.codex/skills/<name> \u4E2D\u6307\u5411\u4E2D\u592E\u5E93\u7684\u8F6F\u94FE\uFF0C\n\u518D\u5220 ~/.agents/skills/<name> \u4E2D\u6307\u5411\u5F53\u524D\u5305\u6216\u5DF2\u60AC\u7A7A\u7684\u8F6F\u94FE\u3002\u6307\u5411\u522B\u5904\u7684\u8F6F\u94FE\u4E0E\u771F\u5B9E\u6587\u4EF6/\u76EE\u5F55\u4E00\u5F8B\u4E0D\u52A8\uFF08\u5217\u5165 skipped\uFF09\u3002\n\u4E0D\u52A8 ~/.config/cx-cli/\u3002\u5378\u8F7D\u987A\u5E8F: \u5148\u672C\u547D\u4EE4\uFF0C\u518D npm rm -g @ats-cx/cx-cli\u3002\n\n\u8F93\u51FA:\n { packageRoot, packageVersion, removed, skipped: [{ path, reason }], warnings, hint }";
|
|
5
|
+
export interface SkillsCommandOptions {
|
|
6
|
+
packageRoot: string;
|
|
7
|
+
packageVersion: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function centralSkillsWarning(packageRoot: string): string | null;
|
|
10
|
+
export declare function runSkillsInstall(options: SkillsCommandOptions): CliResult;
|
|
11
|
+
export declare function runSkillsStatus(options: SkillsCommandOptions): CliResult;
|
|
12
|
+
export declare function runSkillsUninstall(options: SkillsCommandOptions): CliResult;
|