listpage_cli 0.0.312 → 0.0.314
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/bin/adapters/cli-interaction.js +9 -3
- package/bin/adapters/node-fs-adapter.js +8 -0
- package/bin/app/parse-args.js +1 -1
- package/bin/cli.js +3 -3
- package/bin/commands/project-command.js +12 -1
- package/bin/commands/skill/add-command.js +9 -0
- package/bin/commands/skill/list-command.js +9 -0
- package/bin/commands/skill-command.js +21 -0
- package/bin/services/{install-skill-service.js → skill-add-service.js} +8 -5
- package/bin/services/skill-list-service.js +27 -0
- package/package.json +1 -1
- package/templates/backend-template/package.json.tmpl +1 -1
- package/templates/frontend-template/package.json.tmpl +3 -3
- package/templates/rush-template/AGENTS.md +36 -0
- package/templates/rush-template/docs/.gitkeep +0 -0
- package/templates/skills-template/feishu-prd-reviewer/SKILL.md +45 -52
- package/templates/skills-template/feishu-prd-reviewer/example.md +11 -0
- package/templates/skills-template/listpage-cli-deploy/SKILL.md +123 -0
- package/bin/commands/install-skill-command.js +0 -9
|
@@ -156,9 +156,12 @@ function printHelp() {
|
|
|
156
156
|
` 用法: ${helpColor("listpage_cli init", "dim")}`,
|
|
157
157
|
" 说明: 进入中文引导式交互,按提示填写即可",
|
|
158
158
|
"",
|
|
159
|
-
` ${helpColor("
|
|
160
|
-
` 用法: ${helpColor("listpage_cli
|
|
161
|
-
" 说明:
|
|
159
|
+
` ${helpColor("skill", "green")}`,
|
|
160
|
+
` 用法: ${helpColor("listpage_cli skill list", "dim")}`,
|
|
161
|
+
" 说明: 列出 CLI 包内可安装的技能名称(模板目录下的子文件夹)",
|
|
162
|
+
"",
|
|
163
|
+
` 用法: ${helpColor("listpage_cli skill add [技能名称]", "dim")}`,
|
|
164
|
+
" 说明: 安装技能到 Cursor;省略技能名时默认为 test,安装到当前命令执行目录的 .cursor/skills/",
|
|
162
165
|
"",
|
|
163
166
|
` ${helpColor("project", "green")}`,
|
|
164
167
|
` 用法: ${helpColor("listpage_cli project build", "dim")}`,
|
|
@@ -173,6 +176,9 @@ function printHelp() {
|
|
|
173
176
|
` 备注: ${helpColor("默认会清理并拉取镜像;传入 --skip-image 时只执行容器相关步骤(假定镜像已是最新且已存在)", "yellow")}`,
|
|
174
177
|
` ${helpColor("参数优先级为 CLI > profile > base", "yellow")}`,
|
|
175
178
|
"",
|
|
179
|
+
` 用法: ${helpColor("listpage_cli project all [tag] [--profile dev] [--platform linux/amd64] [--env KEY=VALUE] [--skip-image]", "dim")}`,
|
|
180
|
+
" 说明: 串行执行 build -> release -> deploy,任一步失败即中断并返回对应错误",
|
|
181
|
+
"",
|
|
176
182
|
` ${helpColor("lark", "green")}`,
|
|
177
183
|
` 用法: ${helpColor("listpage_cli lark read <doc_token>", "dim")}`,
|
|
178
184
|
` ${helpColor("listpage_cli lark check-todo <doc_token> <todo_index>", "dim")}`,
|
|
@@ -88,6 +88,14 @@ function createNodeFsAdapter() {
|
|
|
88
88
|
throw toFsPortError("writeBuffer", targetPath, error);
|
|
89
89
|
}
|
|
90
90
|
},
|
|
91
|
+
removeDirRecursive: (targetPath) => {
|
|
92
|
+
try {
|
|
93
|
+
(0, fs_1.rmSync)(targetPath, { recursive: true, force: true });
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
throw toFsPortError("removeDirRecursive", targetPath, error);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
91
99
|
};
|
|
92
100
|
}
|
|
93
101
|
function toFsPortError(operation, targetPath, error) {
|
package/bin/app/parse-args.js
CHANGED
package/bin/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ const cli_interaction_1 = require("./adapters/cli-interaction");
|
|
|
5
5
|
const execute_1 = require("./app/execute");
|
|
6
6
|
const command_result_1 = require("./domain/command-result");
|
|
7
7
|
const init_command_1 = require("./commands/init-command");
|
|
8
|
-
const
|
|
8
|
+
const skill_command_1 = require("./commands/skill-command");
|
|
9
9
|
const project_command_1 = require("./commands/project-command");
|
|
10
10
|
const lark_command_1 = require("./commands/lark-command");
|
|
11
11
|
const node_fs_adapter_1 = require("./adapters/node-fs-adapter");
|
|
@@ -23,7 +23,7 @@ const initCommandHandler = (0, init_command_1.createInitCommandHandler)({
|
|
|
23
23
|
fs: fsAdapter,
|
|
24
24
|
files: filesystemCapability,
|
|
25
25
|
});
|
|
26
|
-
const
|
|
26
|
+
const skillCommandHandler = (0, skill_command_1.createSkillCommandHandler)({
|
|
27
27
|
fs: fsAdapter,
|
|
28
28
|
files: filesystemCapability,
|
|
29
29
|
config: {
|
|
@@ -45,7 +45,7 @@ async function main() {
|
|
|
45
45
|
printVersion: cli_interaction_1.printVersion,
|
|
46
46
|
handlers: {
|
|
47
47
|
init: initCommandHandler,
|
|
48
|
-
|
|
48
|
+
skill: skillCommandHandler,
|
|
49
49
|
project: projectCommandHandler,
|
|
50
50
|
lark: larkCommandHandler,
|
|
51
51
|
},
|
|
@@ -18,8 +18,19 @@ function createProjectCommandHandler(deps) {
|
|
|
18
18
|
return release(input);
|
|
19
19
|
case "deploy":
|
|
20
20
|
return deploy(input);
|
|
21
|
+
case "all": {
|
|
22
|
+
const buildResult = await build(input);
|
|
23
|
+
if (!buildResult.ok) {
|
|
24
|
+
return buildResult;
|
|
25
|
+
}
|
|
26
|
+
const releaseResult = await release(input);
|
|
27
|
+
if (!releaseResult.ok) {
|
|
28
|
+
return releaseResult;
|
|
29
|
+
}
|
|
30
|
+
return deploy(input);
|
|
31
|
+
}
|
|
21
32
|
default:
|
|
22
|
-
return (0, command_result_1.commandError)("错误: 未知的子命令。目前仅支持: build, release, deploy", "invalid_subcommand", 1);
|
|
33
|
+
return (0, command_result_1.commandError)("错误: 未知的子命令。目前仅支持: build, release, deploy, all", "invalid_subcommand", 1);
|
|
23
34
|
}
|
|
24
35
|
};
|
|
25
36
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSkillAddCommandHandler = createSkillAddCommandHandler;
|
|
4
|
+
const skill_add_service_1 = require("../../services/skill-add-service");
|
|
5
|
+
function createSkillAddCommandHandler(deps) {
|
|
6
|
+
return async (input) => {
|
|
7
|
+
return (0, skill_add_service_1.runSkillAddFlow)(input, deps);
|
|
8
|
+
};
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSkillListCommandHandler = createSkillListCommandHandler;
|
|
4
|
+
const skill_list_service_1 = require("../../services/skill-list-service");
|
|
5
|
+
function createSkillListCommandHandler(deps) {
|
|
6
|
+
return async (_input) => {
|
|
7
|
+
return (0, skill_list_service_1.runSkillListFlow)(deps);
|
|
8
|
+
};
|
|
9
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSkillCommandHandler = createSkillCommandHandler;
|
|
4
|
+
const command_result_1 = require("../domain/command-result");
|
|
5
|
+
const add_command_1 = require("./skill/add-command");
|
|
6
|
+
const list_command_1 = require("./skill/list-command");
|
|
7
|
+
function createSkillCommandHandler(deps) {
|
|
8
|
+
const add = (0, add_command_1.createSkillAddCommandHandler)(deps);
|
|
9
|
+
const list = (0, list_command_1.createSkillListCommandHandler)(deps);
|
|
10
|
+
return async (input) => {
|
|
11
|
+
const sub = input.positionals[0];
|
|
12
|
+
switch (sub) {
|
|
13
|
+
case "add":
|
|
14
|
+
return add(input);
|
|
15
|
+
case "list":
|
|
16
|
+
return list(input);
|
|
17
|
+
default:
|
|
18
|
+
return (0, command_result_1.commandError)("用法: listpage_cli skill add [技能名称]\n listpage_cli skill list", command_result_1.COMMAND_ERROR_CODES.unknownCommand, 1);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.runSkillAddFlow = runSkillAddFlow;
|
|
4
4
|
const command_result_1 = require("../domain/command-result");
|
|
5
|
-
function
|
|
6
|
-
const skillName = input.positionals[
|
|
5
|
+
function runSkillAddFlow(input, deps) {
|
|
6
|
+
const skillName = input.positionals[1] ?? deps.config.defaultSkillName;
|
|
7
7
|
const sourceDir = deps.fs.join(deps.config.getSkillTemplateRoot(), skillName);
|
|
8
8
|
if (!deps.fs.exists(sourceDir)) {
|
|
9
9
|
return (0, command_result_1.commandError)(`技能 ${skillName} 不存在: ${sourceDir}`, command_result_1.COMMAND_ERROR_CODES.skillNotFound, 1);
|
|
10
10
|
}
|
|
11
|
-
const targetDirResult = resolveTargetDir(
|
|
11
|
+
const targetDirResult = resolveTargetDir(skillName, deps.fs);
|
|
12
12
|
if (targetDirResult.kind === "error") {
|
|
13
13
|
return targetDirResult.result;
|
|
14
14
|
}
|
|
15
15
|
try {
|
|
16
|
+
if (deps.fs.exists(targetDirResult.targetDir)) {
|
|
17
|
+
deps.fs.removeDirRecursive(targetDirResult.targetDir);
|
|
18
|
+
}
|
|
16
19
|
deps.files.copySkillDir(sourceDir, targetDirResult.targetDir);
|
|
17
20
|
return (0, command_result_1.commandOk)(`已安装 ${skillName} 技能到 ${targetDirResult.targetDir}`);
|
|
18
21
|
}
|
|
@@ -21,7 +24,7 @@ function runInstallSkillFlow(input, deps) {
|
|
|
21
24
|
return (0, command_result_1.commandError)(`安装技能失败(${skillName} -> ${targetDirResult.targetDir}): ${message}`, command_result_1.COMMAND_ERROR_CODES.fsOperationFailed, 1);
|
|
22
25
|
}
|
|
23
26
|
}
|
|
24
|
-
function resolveTargetDir(
|
|
27
|
+
function resolveTargetDir(skillName, fs) {
|
|
25
28
|
const baseDir = fs.cwd();
|
|
26
29
|
if (!baseDir || !baseDir.trim()) {
|
|
27
30
|
return {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runSkillListFlow = runSkillListFlow;
|
|
4
|
+
const command_result_1 = require("../domain/command-result");
|
|
5
|
+
function runSkillListFlow(deps) {
|
|
6
|
+
const root = deps.config.getSkillTemplateRoot();
|
|
7
|
+
if (!deps.fs.exists(root)) {
|
|
8
|
+
return (0, command_result_1.commandError)(`技能模板目录不存在: ${root}`, command_result_1.COMMAND_ERROR_CODES.invalidPath, 1);
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const names = deps.fs
|
|
12
|
+
.readDir(root)
|
|
13
|
+
.filter((name) => {
|
|
14
|
+
const entry = deps.fs.join(root, name);
|
|
15
|
+
return deps.fs.exists(entry) && deps.fs.isDirectory(entry);
|
|
16
|
+
})
|
|
17
|
+
.sort((a, b) => a.localeCompare(b));
|
|
18
|
+
if (names.length === 0) {
|
|
19
|
+
return (0, command_result_1.commandOk)("(暂无可用技能)");
|
|
20
|
+
}
|
|
21
|
+
return (0, command_result_1.commandOk)(names.join("\n"));
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
25
|
+
return (0, command_result_1.commandError)(`列出技能失败: ${message}`, command_result_1.COMMAND_ERROR_CODES.fsOperationFailed, 1);
|
|
26
|
+
}
|
|
27
|
+
}
|
package/package.json
CHANGED
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "rsbuild build",
|
|
8
|
-
"dev": "rsbuild dev
|
|
8
|
+
"dev": "rsbuild dev",
|
|
9
9
|
"format": "prettier --write .",
|
|
10
10
|
"preview": "rsbuild preview"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"listpage-http": "0.0.
|
|
14
|
-
"listpage-components": "0.0.
|
|
13
|
+
"listpage-http": "0.0.314",
|
|
14
|
+
"listpage-components": "0.0.314",
|
|
15
15
|
"antd": "6.3.1",
|
|
16
16
|
"ahooks": "^3.9.5",
|
|
17
17
|
"@ant-design/icons": "~6.0.2",
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# 项目说明
|
|
2
|
+
|
|
3
|
+
本仓库使用 **Rush monorepo** 管理,**不要**按普通 npm 项目执行命令。
|
|
4
|
+
|
|
5
|
+
## 常用命令(Rush)
|
|
6
|
+
|
|
7
|
+
### 依赖管理
|
|
8
|
+
|
|
9
|
+
- **安装依赖**:`rush install`(在仓库根目录执行,对应 `npm install`)
|
|
10
|
+
- **更新依赖 / 锁文件**:`rush update`(修改 package.json 或依赖后执行)
|
|
11
|
+
- **添加依赖到某个项目**:
|
|
12
|
+
- `cd apps/fe && rush add -p package-name`:给前端加依赖
|
|
13
|
+
- `cd servers/be && rush add -p package-name`:给后端加依赖
|
|
14
|
+
- **添加 dev 依赖**:`rush add -p package-name --dev`(同样需先 `cd` 到目标项目目录)
|
|
15
|
+
|
|
16
|
+
**不要使用** `npm install`、`pnpm add` 等在子项目中直接安装依赖。
|
|
17
|
+
|
|
18
|
+
### 项目脚本
|
|
19
|
+
|
|
20
|
+
- **前端开发**:`cd apps/fe && rushx dev`
|
|
21
|
+
- **后端开发**:`cd servers/be && rushx dev`
|
|
22
|
+
- **构建**:`rush build`(根目录,会构建所有项目)
|
|
23
|
+
|
|
24
|
+
### Prisma(后端 schema 变更后)
|
|
25
|
+
|
|
26
|
+
- **生成 Prisma 客户端**:`cd servers/be && rushx prisma:gen`(或 `npx prisma generate`)——更新本地依赖
|
|
27
|
+
- **同步到远程数据库**:`cd servers/be && npx prisma db push`——将 schema 改动推到数据库
|
|
28
|
+
|
|
29
|
+
### 项目自带命令
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## 子项目说明
|
|
33
|
+
|
|
34
|
+
- `apps/fe`:前端(详见 apps/fe/AGENTS.md)
|
|
35
|
+
- `servers/be`:后端(详见 servers/be/AGENTS.md)
|
|
36
|
+
|
|
File without changes
|
|
@@ -6,22 +6,18 @@ description: 评审飞书 PRD(支持评审后结合待办反向澄清需求)
|
|
|
6
6
|
# Feishu PRD Reviewer(产品视角)
|
|
7
7
|
|
|
8
8
|
## 触发条件
|
|
9
|
-
|
|
10
9
|
- 用户要求处理该飞书 PRD(评审或补齐待办),并明确提供文档地址(URL)或 `doc_token`
|
|
11
10
|
- 通过 `npx listpage_cli lark read <doc_token>` 能成功读取到文档内容
|
|
12
11
|
|
|
13
12
|
## 输入
|
|
14
|
-
|
|
15
13
|
- 飞书 PRD URL(用于解析 `doc_token`)
|
|
16
14
|
- 或用户直接提供 `doc_token`
|
|
17
15
|
|
|
18
16
|
## 输出
|
|
19
|
-
|
|
20
17
|
- PRD 含待办:结合“AI 评审章节 + 待办问题”逐条处理待办,更新需求清单描述并回写对应区块、将待办标记为 done;随后结束
|
|
21
18
|
- PRD 不含待办:生成产品经理易读的评审结论并回写区块;随后结束
|
|
22
19
|
|
|
23
20
|
## 核心原则
|
|
24
|
-
|
|
25
21
|
- **互斥执行**:一次仅执行一个模式。
|
|
26
22
|
- 有待办:只做“待办处理”,不做任何新的需求评审与结论生成(但可读取既有 AI 评审章节作为待办处理输入)。
|
|
27
23
|
- 无待办:只做“需求评审”,不进行待办处理流程(不创建 requirements.md)。
|
|
@@ -31,7 +27,6 @@ description: 评审飞书 PRD(支持评审后结合待办反向澄清需求)
|
|
|
31
27
|
- 约束强执行:待办处理阶段严格遵循标题层级与提示语移除规则(见“待办处理约束”)
|
|
32
28
|
|
|
33
29
|
## 执行流程
|
|
34
|
-
|
|
35
30
|
1. 获取 `doc_token`:从 URL 提取,或直接使用用户提供的 token
|
|
36
31
|
2. 读取需求文档:在项目根目录执行
|
|
37
32
|
- `npx listpage_cli lark read <doc_token>`
|
|
@@ -44,14 +39,14 @@ description: 评审飞书 PRD(支持评审后结合待办反向澄清需求)
|
|
|
44
39
|
- 文件路径示例:`.listpage/lark/<doc_token>/requirements.md`
|
|
45
40
|
- 标题约束:严禁使用 H1/H2,所有标题必须从 H3 开始
|
|
46
41
|
- 内容约束:移除提示语“下面部分由产品经理编写,内容为准备评审的需求点”
|
|
47
|
-
|
|
42
|
+
- 内容来源:以原文档中“需求清单”作为基底,并结合“AI 评审章节(如 robot_face 回写的评审结论)”与待办问题进行澄清改写;去掉“需求清单”标题与相关提示语
|
|
48
43
|
- 循环处理每条待办
|
|
49
44
|
1. 读取待办事项内容
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
2. 同步读取对应需求点的 AI 评审内容(需求描述/问题/可行性/风险点)
|
|
46
|
+
3. 以“待办问题 + AI 评审结论”共同作为输入,改写 `requirements.md` 中对应需求点,让需求描述更清晰、可执行
|
|
47
|
+
4. 如 AI 评审与待办口径冲突,以“待办明确的新口径”优先,并在需求描述中消除歧义
|
|
48
|
+
5. 回写时只更新需求清单内容,不改变既定回写命令和回写目标区块
|
|
49
|
+
6. 直到所有待办处理完成
|
|
55
50
|
- 回写飞书文档(待办全部完成后)
|
|
56
51
|
- 执行:`npx listpage_cli lark update-block <doc_token> woman "<markdown_file_path>"`
|
|
57
52
|
- 更新待办状态
|
|
@@ -59,66 +54,64 @@ description: 评审飞书 PRD(支持评审后结合待办反向澄清需求)
|
|
|
59
54
|
- 将待办标记为完成(done)
|
|
60
55
|
5. 流程结束:待办处理完成后停止,不再执行后续步骤
|
|
61
56
|
6. 代码评审与结论生成(无待办时执行)
|
|
62
|
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
- 阶段 C:最终回写飞书
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
57
|
+
- 目标:先把“需求点列表”固定到 `review.md`,再对每条需求点按序逐条评审并回写,避免一次性把所有需求点塞进上下文导致爆炸
|
|
58
|
+
- 阶段 A:需求点提炼并写入 `review.md`(仅拆条,不写评审)
|
|
59
|
+
- 从步骤 2 读取到的 PRD 内容中,提取“需求清单”区段的全部需求点(包含图片/补充信息的隐含诉求)
|
|
60
|
+
- 将需求拆成 N 条“可执行条目”,为每条生成稳定编号(例如 `i=1..N`)
|
|
61
|
+
- 在本地创建/覆盖 Markdown:`.listpage/lark/<doc_token>/review.md`
|
|
62
|
+
- 在 `review.md` 顶部添加一个“执行待办清单”(仅用于阶段 B 驱动逐条处理)
|
|
63
|
+
- 格式示例(必须是 Markdown checklist):`- [ ] 需求点 1`
|
|
64
|
+
- 对每条生成一行:`- [ ] 需求点 i`
|
|
65
|
+
- 阶段 A 不把任何条目标为已完成
|
|
66
|
+
- 每条使用固定区块边界,格式示例:`### 需求点 i`(注意:仅要求禁止 H1/H2,H3 允许)
|
|
67
|
+
- 仅在每个 `### 需求点 i` 区块内写“需求描述”,并严格复用 `example.md` 的字段与缩进/子 bullet 结构(产品视角)
|
|
68
|
+
- 阶段 A 不写“问题/可行性/风险点/业务合理性”,只负责把每个需求点的“需求描述”结构固定下来;业务合理性在阶段 B 的第一步再写(仅不合理时出现)
|
|
69
|
+
- 格式约束:严禁使用 H1/H2 标题或表格;允许使用列表与加粗字段
|
|
70
|
+
- 阶段 B:逐条评审并回写 `review.md`(按序、只处理一条)
|
|
71
|
+
- 对 i = 1..N 循环执行,直到全部条目完成
|
|
72
|
+
1. 只读取 `review.md` 中两处内容:
|
|
73
|
+
- 执行待办清单里第 i 行(`- [ ] 需求点 i` 或 `- [x] 需求点 i`)
|
|
74
|
+
- `### 需求点 i` 对应的区块(不要把其它条目的内容也纳入本轮处理)
|
|
75
|
+
2. 第一步(业务合理性):结合仓库内可读的产品/业务上下文(如 AGENTS、领域约定、既有能力边界)判断该需求点是否合理——用户是否想清楚、提出的方案是否当下较优、是否与既有业务目标或流程冲突。**仅当**判定为「不合理、需再商榷、或用户方案非当下较优」时,在本条区块内增加 `- **业务合理性:**:`(说明理由、风险或更可取的替代思路);合理则**不**增加该字段。判断时避免引用具体代码路径/函数/文件名;不写实现猜测。
|
|
76
|
+
3. 第二步(信息完备性 + 落地性):只基于 PRD 需求本身进行判断,不做“结合代码去猜口径/猜实现”的推断;并且收紧“问题”触发条件:
|
|
77
|
+
- 只有当 PRD 信息不足以形成“开发可执行的需求描述”(关键落点/口径/范围/汇总规则无法确定)时,才输出 `**问题:**`(不写 `可行性/风险点`)
|
|
78
|
+
- 如果基于该需求点本身已足够做出“可以实现”的判断(即使仍存在非关键细节,也不阻塞落地),则不要问问题:直接输出 `**可行性:**`(成本等级 + 精简的必要说明)
|
|
79
|
+
4. 将输出严格复用 `example.md` 的区块结构,并回写到对应 `### 需求点 i` 区块,保持其它区块不变:
|
|
80
|
+
- 不新增其它字段/标题/正文块(`业务合理性` 为**仅在不合理时**允许的例外字段,见 `example.md`)
|
|
81
|
+
- 仅按需要填写 `需求描述` / `业务合理性` / `问题` / `可行性` / `风险点`(这些字段与缩进/子 bullet 结构必须与 `example.md` 一致)
|
|
82
|
+
- 不允许出现代码/组件/函数/文件名;不要根据代码猜口径(需求不清晰就只提问题)
|
|
83
|
+
5. 把执行待办清单里第 i 行从 `- [ ] 需求点 i` 更新为 `- [x] 需求点 i`(作为“本条已完成”的唯一状态)
|
|
84
|
+
- 阶段 C:最终回写飞书
|
|
85
|
+
- 等循环全部完成后,在本地生成“回写版”文件:`review_writeback.md`
|
|
86
|
+
- `review_writeback.md` 最顶部新增 `### 评审人` 标题,并下一行写 `<当前模型名称>`(格式与 `example.md` 一致)
|
|
87
|
+
- 该文件同时保留 `### 评审人` 区块与所有 `### 需求点 i` 区块
|
|
88
|
+
- 必须删除顶部的“执行待办清单”(`- [ ]/- [x] 需求点 i` 那一段及其所在行),确保飞书里不出现 checklist 待办格式
|
|
89
|
+
- 执行飞书回写时,仅使用 `review_writeback.md`:
|
|
90
|
+
- `npx listpage_cli lark update-block <doc_token> robot_face "review_writeback.md"`
|
|
96
91
|
|
|
97
92
|
## 评审方法(无待办时)
|
|
98
|
-
|
|
99
93
|
(无待办时执行第 6 步的“阶段 A/B/C”,本节仅保留总体输出原则)
|
|
100
|
-
|
|
101
94
|
- 需求点提炼:从“需求清单”区段理解全部需要落地的需求点(包括图片描述与补充信息中的隐含诉求),并拆成可逐条评审的 N 条条目
|
|
95
|
+
- 第一步(业务合理性,逐条):结合现有业务与产品上下文(仓库内可读说明、领域约定、能力边界;**非**具体实现代码)判断该需求点是否合理——用户是否想清楚、用户给出的方案是否当下较优、是否与业务目标或既有流程冲突。**仅当**判定为不合理或需再商榷时,才在对应区块增加 `- **业务合理性:**:`;合理则**不**输出该字段
|
|
96
|
+
- 第二步(信息完备与落地):在业务合理性字段(若有)之后,再按信息缺口与可行性规则补充 `问题` / `可行性` / `风险点`(互斥与触发条件见下)
|
|
102
97
|
- 输出模板:每个 `### 需求点 i` 区块的呈现结构必须严格复用 `example.md`
|
|
103
98
|
- `- **需求描述:**:` 必须出现
|
|
99
|
+
- `- **业务合理性:**:` **仅当**第一步判定不合理/需商榷时出现;与 `问题`、`可行性`、`风险点` 的并存规则见 `example.md`
|
|
104
100
|
- 仅当信息缺口会阻塞落地:才出现 `- **问题:**:`(此时不出现 `**可行性:**` 与 `**风险点:**`)
|
|
105
101
|
- 其余情况(能实现/不阻塞落地):出现 `- **可行性:**:`(可追加 `- **风险点:**:`,仅影响范围大时)
|
|
106
102
|
- 澄清优先(但不泛问):不阻塞落地时不提问;不做口径猜测
|
|
107
|
-
-
|
|
103
|
+
- 禁止代码猜测:不引用仓库代码/组件/函数/文件名作为“证据”;也不要根据代码推断不清晰的口径(应改为提问)。业务合理性判断允许使用**产品/业务层**上下文,与“禁止用代码猜 PRD 口径”并行不悖
|
|
108
104
|
|
|
109
105
|
## 写回文档(无待办时)
|
|
110
|
-
|
|
111
106
|
- 本地写回:通过第 6 步阶段 C 生成 `review_writeback.md`
|
|
112
107
|
- 格式要求:严禁使用(H1-H2)标题或表格;只保留 `### 评审人` 与所有 `### 需求点 i` 区块
|
|
113
108
|
- 同步飞书:执行 `npx listpage_cli lark update-block <doc_token> robot_face "review_writeback.md"`
|
|
114
109
|
|
|
115
110
|
## 待办处理约束(强制)
|
|
116
|
-
|
|
117
111
|
- 新建 requirements.md 文档禁止 H1/H2,标题从 H3 开始
|
|
118
112
|
- 必须移除指定提示语,并去掉“需求清单”标题本身
|
|
119
113
|
- 必须把“AI 评审章节 + 待办问题”合并后再更新需求清单,不可仅依据待办原文机械改写
|
|
120
114
|
|
|
121
115
|
## 成功标准
|
|
122
|
-
|
|
123
116
|
- 有待办:所有待办均回写并标记 done;已基于“AI 评审章节 + 待办问题”完成需求清单澄清更新;仅完成待办处理相关的文档更新;不生成新的需求评审结论(也不创建新的 review.md)
|
|
124
|
-
- 无待办:生成的评审结论回写成功;且回写内容满足“无
|
|
117
|
+
- 无待办:生成的评审结论回写成功;且回写内容满足“无H1-H2标题,无表格”的格式要求
|
|
@@ -34,3 +34,14 @@
|
|
|
34
34
|
- **风险点:**
|
|
35
35
|
- (风险 1:可能影响的不止是展示文案,例如统计口径/汇总逻辑/跨模块联动等)
|
|
36
36
|
- (风险 2)
|
|
37
|
+
|
|
38
|
+
### 需求点 5(业务合理性:仅不合理时出现)
|
|
39
|
+
|
|
40
|
+
- **需求描述:**: (一句话概括要做什么,产品视角)
|
|
41
|
+
- **业务合理性:**
|
|
42
|
+
- (为何不合理/与用户目标或现有业务冲突/用户方案非当下较优;可写更可取的替代思路或需产品先拍板的点)
|
|
43
|
+
- **可行性:**
|
|
44
|
+
- 成本:低/中/高
|
|
45
|
+
- (仍可在指出业务问题的同时评估落地成本;若信息仍不足以落地,则改用「需求点 2」结构:业务合理性 + **问题**,不出现可行性/风险点)
|
|
46
|
+
|
|
47
|
+
字段顺序与并存:`需求描述` 始终第一;`业务合理性` 仅在不合理时出现在 `需求描述` 之后。其后仍遵循「有问题则只写问题,无问题则写可行性(可加风险点)」。
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: listpage-cli-deploy
|
|
3
|
+
description: Runs listpage_cli project build, release, and deploy in sequence (Docker build/push to registry and remote container). After a successful deploy, derive and tell the user the app access URL from listpage.config.json and backend listen port (see skill body). Use ONLY when the user explicitly asks to deploy using listpage_cli—e.g. mentions deploying with listpage_cli, listpage_cli 部署, or similar. Do NOT use for generic deployment, Docker/compose/K8s/CI, or Aliyun push without an explicit listpage_cli request. Default tag/environment is dev; users may name other environments in listpage.config.json docker.releaseProfiles (e.g. test, prod).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# listpage_cli 自动化部署
|
|
7
|
+
|
|
8
|
+
## 何时使用本技能
|
|
9
|
+
|
|
10
|
+
- **仅当用户明确说要通过 `listpage_cli` 部署**(例如:用 listpage_cli 部署、listpage_cli 发版等**且语境指向该 CLI**)时再应用本技能。
|
|
11
|
+
- **不要**因泛泛的「部署」「上线」「打镜像」「推阿里云」等就启用;除非用户同时点明 **listpage_cli**。
|
|
12
|
+
|
|
13
|
+
## Tag 与环境
|
|
14
|
+
|
|
15
|
+
- `project release` / `project deploy` 的 **`<tag>`** 在习惯上对应**环境名**(开发 / 测试 / 生产等),与 `listpage.config.json` 里 **`docker.releaseProfiles`** 下的 key 一致;同一仓库可配置多套 profile(如 `dev`、`test`、`prod`),每套可有各自的 `tag`、`envFile` 等。
|
|
16
|
+
- **若用户未指定环境或 tag,默认使用 `dev`**。
|
|
17
|
+
|
|
18
|
+
在**项目根目录**(需存在 `listpage.config.json`)按顺序执行三条命令即可完成构建、镜像推送与远程容器部署。
|
|
19
|
+
|
|
20
|
+
## 前置条件
|
|
21
|
+
|
|
22
|
+
- 已安装并可执行 `listpage_cli`(可先运行 `listpage_cli --help` 核对子命令)。
|
|
23
|
+
- 本机已安装 **Docker**,且 **Docker 守护进程已启动**(`release` / `deploy` 会用到本地 Docker 与远程配置)。
|
|
24
|
+
- `listpage.config.json` 中 `docker.registry`、`docker.remote`、`docker.releaseProfiles` 等已按目标环境填好(见下文「常见坑」)。
|
|
25
|
+
|
|
26
|
+
## 部署步骤(顺序固定)
|
|
27
|
+
|
|
28
|
+
在项目根目录执行:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
listpage_cli project build
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- 校验当前目录为有效项目根,构建产物写入 **`.listpage/output`**。
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
listpage_cli project release <tag>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- 默认:`listpage_cli project release dev`(未指定环境时)。
|
|
41
|
+
- `<tag>` 与 `listpage.config.json` 中 `docker.releaseProfiles` 的环境 key 对齐(如 `dev` / `test` / `prod`);也可用 CLI 覆盖项,见 `listpage_cli --help` 中 `project release` 说明。
|
|
42
|
+
- 流程:校验 `.listpage/output` → 登录 / 构建 / 打 tag / 推送到配置的镜像仓库。
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
listpage_cli project deploy <tag>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
- 默认:`listpage_cli project deploy dev`(与 `release` 使用同一环境名)。
|
|
49
|
+
- 使用 `docker.remote` + `docker.container` 部署;支持 `ports`、`envFile`、`env` 等合并。
|
|
50
|
+
- 若镜像已在目标环境且仅需更新容器步骤,可使用 CLI 中的 `--skip-image`(以 `listpage_cli --help` 为准)。
|
|
51
|
+
|
|
52
|
+
**CLI 参数优先级**(来自官方说明):`CLI 参数 > profile > 配置文件中的 base 项`。可选参数包括 `--profile`、`--platform`、`--env KEY=VALUE` 等,需要时查阅 `listpage_cli --help`。
|
|
53
|
+
|
|
54
|
+
## 部署成功后:告知用户访问地址
|
|
55
|
+
|
|
56
|
+
在 **`project deploy` 成功结束**后,必须结合**项目根目录的 `listpage.config.json`**(以及必要时后端监听端口)拼出**应用访问地址**,并用一句话告诉用户。**不要**把 `docker.registry` 密码、`envFile` 内容等敏感信息写入回复。
|
|
57
|
+
|
|
58
|
+
### 推导规则(按顺序)
|
|
59
|
+
|
|
60
|
+
1. **主机(host)**
|
|
61
|
+
使用 `docker.remote.host`(远程部署目标机的 IP 或域名)。**不要**用 `docker.remote.port`(那是连接 Docker API 的端口,例如 2376,不是浏览器访问端口)。
|
|
62
|
+
|
|
63
|
+
2. **协议(scheme)**
|
|
64
|
+
- **默认**:面向用户的应用 URL 使用 **`http`**。
|
|
65
|
+
- `docker.remote.protocol`(如 `https`)仅表示连接 **Docker daemon** 的方式,**不能**直接当作浏览器访问应用的协议。
|
|
66
|
+
- 若用户在前方挂了 **Nginx / 负载均衡 / 全站 HTTPS**,实际对外可能是 **`https`**;技能无法从配置文件唯一确定时,以 **`http` + host + 端口** 为主,并**简短说明**「若前面有 HTTPS 终止,请改用 https」。
|
|
67
|
+
|
|
68
|
+
3. **路径(pathname / 前缀)**
|
|
69
|
+
- **一般情况**:路径前缀与 **`docker.container.name`** 一致,格式为 **`/<container.name>`**(注意前导 `/`,且通常**全小写**与配置一致)。
|
|
70
|
+
- **少数差异**:若前端 `assetPrefix` / `base`(如 `apps/fe` 的 `rsbuild.config.ts`)与 `docker.container.name` 不一致,应以**前端实际配置的 public 路径**为准(部署仍成功但路径不同时需核对前端)。默认在技能中优先 **`docker.container.name`**。
|
|
71
|
+
|
|
72
|
+
4. **端口(浏览器访问的 host 端口)**
|
|
73
|
+
读取 `docker.container.ports` 中每一项 **`"hostPort:containerPort"`** 字符串(与 Docker `-p` 一致)。
|
|
74
|
+
- 先确定**容器内后端 HTTP 监听端口**:查看 `servers/be`(或配置里 `artifacts.backend.projectDir`)中 `listen(...)`、`Dockerfile` 的 `EXPOSE` 或 `PORT` 环境变量;常见为 **3000**。
|
|
75
|
+
- 在 `ports` 数组里找到 **右侧(container)端口等于该后端端口** 的那一条,取**左侧(host)端口**作为 URL 中的端口。
|
|
76
|
+
- **多条映射时**:始终使用「**对应后端对外提供 Web 服务的那一个容器端口**」所映射到主机上的端口,而不是 Docker API 或其它服务端口。
|
|
77
|
+
- **单条映射**(如 `"3333:3000"`):后端在容器内监听 3000 → 浏览器访问 **`3333`**。
|
|
78
|
+
|
|
79
|
+
5. **拼 URL**
|
|
80
|
+
|
|
81
|
+
`{scheme}://{docker.remote.host}:{上一步得到的 host 端口}/{路径前缀去掉多余斜杠}`
|
|
82
|
+
|
|
83
|
+
路径前缀:若规则 3 得到 `/<name>`,则完整示例形态为:
|
|
84
|
+
`http://{host}:{hostPort}/{container.name}/`
|
|
85
|
+
末尾是否带 `/` 可按习惯,保持与前端 `assetPrefix` 一致即可。
|
|
86
|
+
|
|
87
|
+
### 示例(与配置字段对应)
|
|
88
|
+
|
|
89
|
+
若 `docker.remote.host` 为 `localhost`,`docker.container.name` 为 `contract-payment-collection`,`docker.container.ports` 为 `["3333:3000"]`,且后端在容器内监听 **3000**,则访问地址为:
|
|
90
|
+
|
|
91
|
+
`http://localhost:3333/contract-payment-collection`
|
|
92
|
+
|
|
93
|
+
(若用户仅配置了 http 场景,上述即为默认输出;支持 https 时由用户或前置代理决定,可在说明中一句带过。)
|
|
94
|
+
|
|
95
|
+
## 常见坑与配置要点
|
|
96
|
+
|
|
97
|
+
### 1. Dockerfile 基础镜像(如 `FROM node:25-slim`)
|
|
98
|
+
|
|
99
|
+
本地构建镜像时,若基础镜像未拉取过,`docker build` 可能失败或变慢。部署前可在本机先执行 `docker pull node:25-slim`(或与 `servers/be/Dockerfile` 中 `FROM` 一致的镜像),保证与构建环境一致。
|
|
100
|
+
|
|
101
|
+
### 2. `docker.remote.tls`(ca / cert / key)
|
|
102
|
+
|
|
103
|
+
`listpage.config.json` 里 `docker.remote.tls` 的 `ca`、`cert`、`key` 路径指向的证书**与目标远程 Docker 主机绑定**;换一台部署机或重建 TLS 时,需替换为对应文件并保持路径可访问。
|
|
104
|
+
|
|
105
|
+
### 3. `docker.build.platform`(如 `linux/amd64`)
|
|
106
|
+
|
|
107
|
+
该字段影响**构建出的镜像架构**。例如:在 Apple Silicon Mac 上若要把镜像跑到 **x86 云服务器**,通常使用 `linux/amd64`;若目标为本机或 ARM 机器,需改为匹配的 `platform`(或通过 CLI 传 `--platform` 覆盖)。与运行环境不一致会导致容器无法运行或性能异常。
|
|
108
|
+
|
|
109
|
+
### 4. 阿里云镜像仓库账号
|
|
110
|
+
|
|
111
|
+
`docker.registry` 中的 `url`、`namespace`、`username`、`password` 为**敏感信息**。技能与文档中只描述「在配置中填写」,不要在对话、提交记录或示例中粘贴真实密码;
|
|
112
|
+
|
|
113
|
+
## 快速排错
|
|
114
|
+
|
|
115
|
+
| 现象 | 可检查项 |
|
|
116
|
+
|------|----------|
|
|
117
|
+
| `build` 失败 | 是否在项目根目录、`listpage.config.json` 是否完整、前后端构建脚本是否正常 |
|
|
118
|
+
| `release` 失败 | Docker 是否启动、基础镜像是否可拉取、registry 账号与网络、platform 是否与预期一致 |
|
|
119
|
+
| `deploy` 失败 | `docker.remote` 地址/端口/协议、TLS 证书是否匹配当前主机、`envFile` 路径是否存在 |
|
|
120
|
+
|
|
121
|
+
## 参考命令说明
|
|
122
|
+
|
|
123
|
+
执行 `listpage_cli --help` 可查看 `project build` / `project release` / `project deploy` 的完整参数与备注。
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createInstallSkillCommandHandler = createInstallSkillCommandHandler;
|
|
4
|
-
const install_skill_service_1 = require("../services/install-skill-service");
|
|
5
|
-
function createInstallSkillCommandHandler(deps) {
|
|
6
|
-
return async (input) => {
|
|
7
|
-
return (0, install_skill_service_1.runInstallSkillFlow)(input, deps);
|
|
8
|
-
};
|
|
9
|
-
}
|