ai-project-manage-cli 4.0.11 → 4.0.13
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/dist/index.js
CHANGED
|
@@ -247,11 +247,9 @@ var KIND_MAP = {
|
|
|
247
247
|
clarify: "CLARIFY",
|
|
248
248
|
difficulty: "DIFFICULTY",
|
|
249
249
|
business: "BUSINESS",
|
|
250
|
-
coordination: "COORDINATION",
|
|
251
250
|
CLARIFY: "CLARIFY",
|
|
252
251
|
DIFFICULTY: "DIFFICULTY",
|
|
253
|
-
BUSINESS: "BUSINESS"
|
|
254
|
-
COORDINATION: "COORDINATION"
|
|
252
|
+
BUSINESS: "BUSINESS"
|
|
255
253
|
};
|
|
256
254
|
var STANCE_VALUES = /* @__PURE__ */ new Set(["frontend", "backend", "fullstack"]);
|
|
257
255
|
function asRecord(value) {
|
|
@@ -273,8 +271,13 @@ function parseKind(raw, index) {
|
|
|
273
271
|
const key = String(raw ?? "").trim();
|
|
274
272
|
const kind = KIND_MAP[key] ?? KIND_MAP[key.toLowerCase()];
|
|
275
273
|
if (!kind) {
|
|
274
|
+
if (key.toLowerCase() === "coordination" || key === "COORDINATION") {
|
|
275
|
+
throw new Error(
|
|
276
|
+
`items[${index}].kind \u4E0D\u518D\u652F\u6301 coordination\uFF08\u8054\u8C03\u4F9D\u8D56\uFF09\uFF1B\u8BF7\u52FF\u8F93\u51FA\u63A5\u53E3/\u8054\u8C03\u7C7B\u8BC4\u5BA1\u6761\u76EE`
|
|
277
|
+
);
|
|
278
|
+
}
|
|
276
279
|
throw new Error(
|
|
277
|
-
`items[${index}].kind \u65E0\u6548\uFF0C\u5E94\u4E3A clarify | difficulty | business
|
|
280
|
+
`items[${index}].kind \u65E0\u6548\uFF0C\u5E94\u4E3A clarify | difficulty | business`
|
|
278
281
|
);
|
|
279
282
|
}
|
|
280
283
|
return kind;
|
|
@@ -307,9 +310,6 @@ function parseStructuredReviewYaml(raw, cliModel) {
|
|
|
307
310
|
if (!body) {
|
|
308
311
|
throw new Error(`items[${index}].body \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
309
312
|
}
|
|
310
|
-
if (kind === "COORDINATION" && item.reply != null && String(item.reply).trim()) {
|
|
311
|
-
throw new Error(`items[${index}] \u8054\u8C03\u4F9D\u8D56\uFF08coordination\uFF09\u4E0D\u5141\u8BB8 reply`);
|
|
312
|
-
}
|
|
313
313
|
return {
|
|
314
314
|
startLine: anchor.start,
|
|
315
315
|
endLine: anchor.end,
|
|
@@ -1206,6 +1206,38 @@ async function runUpdate() {
|
|
|
1206
1206
|
}
|
|
1207
1207
|
}
|
|
1208
1208
|
|
|
1209
|
+
// src/commands/update-skills.ts
|
|
1210
|
+
import { cpSync as cpSync2, existsSync as existsSync4, rmSync, statSync as statSync3 } from "fs";
|
|
1211
|
+
import { join as join10 } from "path";
|
|
1212
|
+
var TEMPLATE_SKILLS_DIR = join10(CLI_TEMPLATE_DIR, "skills");
|
|
1213
|
+
async function runUpdateSkills() {
|
|
1214
|
+
const apmDir = WORKSPACE_APM_DIR;
|
|
1215
|
+
if (!existsSync4(apmDir)) {
|
|
1216
|
+
console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
|
|
1217
|
+
process.exit(1);
|
|
1218
|
+
}
|
|
1219
|
+
const apmStat = statSync3(apmDir);
|
|
1220
|
+
if (!apmStat.isDirectory()) {
|
|
1221
|
+
throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
|
|
1222
|
+
}
|
|
1223
|
+
let templateStat;
|
|
1224
|
+
try {
|
|
1225
|
+
templateStat = statSync3(TEMPLATE_SKILLS_DIR);
|
|
1226
|
+
} catch {
|
|
1227
|
+
throw new Error(`[apm] \u5185\u7F6E\u6280\u80FD\u6A21\u677F\u4E0D\u5B58\u5728: ${TEMPLATE_SKILLS_DIR}`);
|
|
1228
|
+
}
|
|
1229
|
+
if (!templateStat.isDirectory()) {
|
|
1230
|
+
throw new Error(`[apm] \u5185\u7F6E\u6280\u80FD\u6A21\u677F\u4E0D\u662F\u76EE\u5F55: ${TEMPLATE_SKILLS_DIR}`);
|
|
1231
|
+
}
|
|
1232
|
+
const skillsDir = join10(apmDir, "skills");
|
|
1233
|
+
if (existsSync4(skillsDir)) {
|
|
1234
|
+
rmSync(skillsDir, { recursive: true, force: true });
|
|
1235
|
+
}
|
|
1236
|
+
await ensureDirExists(apmDir);
|
|
1237
|
+
cpSync2(TEMPLATE_SKILLS_DIR, skillsDir, { recursive: true });
|
|
1238
|
+
console.log("[apm] \u5DF2\u66F4\u65B0 .apm/skills");
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1209
1241
|
// src/commands/update-dev-status.ts
|
|
1210
1242
|
async function runUpdateDevStatus(requirementId, status) {
|
|
1211
1243
|
const cfg = await ensureLoggedConfig();
|
|
@@ -1232,14 +1264,14 @@ async function runUpdateStatus(requirementId, status) {
|
|
|
1232
1264
|
import path5 from "node:path";
|
|
1233
1265
|
|
|
1234
1266
|
// src/commands/deploy/internal/apm-config.ts
|
|
1235
|
-
import { existsSync as
|
|
1267
|
+
import { existsSync as existsSync5, readFileSync as readFileSync7 } from "node:fs";
|
|
1236
1268
|
import { resolve as resolve4 } from "node:path";
|
|
1237
1269
|
function loadApmConfig(options) {
|
|
1238
1270
|
const p = resolve4(
|
|
1239
1271
|
process.cwd(),
|
|
1240
1272
|
options?.configPath ?? resolve4(WORKSPACE_APM_DIR, "apm.config.json")
|
|
1241
1273
|
);
|
|
1242
|
-
if (!
|
|
1274
|
+
if (!existsSync5(p)) {
|
|
1243
1275
|
console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
|
|
1244
1276
|
process.exit(1);
|
|
1245
1277
|
}
|
|
@@ -1343,7 +1375,7 @@ import path4 from "node:path";
|
|
|
1343
1375
|
import Docker from "dockerode";
|
|
1344
1376
|
|
|
1345
1377
|
// src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
|
|
1346
|
-
import { existsSync as
|
|
1378
|
+
import { existsSync as existsSync6, readFileSync as readFileSync8 } from "node:fs";
|
|
1347
1379
|
import path from "node:path";
|
|
1348
1380
|
function asOptionalTlsBuffer(value) {
|
|
1349
1381
|
if (typeof value !== "string") {
|
|
@@ -1355,7 +1387,7 @@ function asOptionalTlsBuffer(value) {
|
|
|
1355
1387
|
if (normalized === "") {
|
|
1356
1388
|
return void 0;
|
|
1357
1389
|
}
|
|
1358
|
-
if (
|
|
1390
|
+
if (existsSync6(normalized)) {
|
|
1359
1391
|
return readFileSync8(normalized);
|
|
1360
1392
|
}
|
|
1361
1393
|
const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
|
|
@@ -1566,7 +1598,7 @@ var DockerodeClient = class {
|
|
|
1566
1598
|
var createDockerodeClient = (config) => new DockerodeClient(config);
|
|
1567
1599
|
|
|
1568
1600
|
// src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
|
|
1569
|
-
import { existsSync as
|
|
1601
|
+
import { existsSync as existsSync7, readFileSync as readFileSync9, statSync as statSync4 } from "node:fs";
|
|
1570
1602
|
import path2 from "node:path";
|
|
1571
1603
|
function stripSurroundingQuotes(value) {
|
|
1572
1604
|
const t = value.trim();
|
|
@@ -1583,7 +1615,7 @@ function loadEnvFromFile(envFilePath) {
|
|
|
1583
1615
|
return {};
|
|
1584
1616
|
}
|
|
1585
1617
|
const targetPath = path2.resolve(envFilePath);
|
|
1586
|
-
if (!
|
|
1618
|
+
if (!existsSync7(targetPath) || !statSync4(targetPath).isFile()) {
|
|
1587
1619
|
return {};
|
|
1588
1620
|
}
|
|
1589
1621
|
const raw = readFileSync9(targetPath, "utf-8");
|
|
@@ -1757,12 +1789,12 @@ function dockerPushImage(params, cwd) {
|
|
|
1757
1789
|
}
|
|
1758
1790
|
|
|
1759
1791
|
// src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
|
|
1760
|
-
import { existsSync as
|
|
1792
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
1761
1793
|
import path3 from "node:path";
|
|
1762
1794
|
function resolveDockerBuildPaths(cwd) {
|
|
1763
1795
|
const dockerfilePath = path3.join(cwd, "Dockerfile");
|
|
1764
1796
|
Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
|
|
1765
|
-
if (!
|
|
1797
|
+
if (!existsSync8(dockerfilePath)) {
|
|
1766
1798
|
throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
|
|
1767
1799
|
}
|
|
1768
1800
|
Logger.info("\u2713 Dockerfile \u5B58\u5728");
|
|
@@ -1891,11 +1923,11 @@ import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
|
|
|
1891
1923
|
import path7 from "node:path";
|
|
1892
1924
|
|
|
1893
1925
|
// src/commands/deploy/internal/load-apm-dotenv.ts
|
|
1894
|
-
import { existsSync as
|
|
1895
|
-
import { join as
|
|
1926
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
|
|
1927
|
+
import { join as join11 } from "node:path";
|
|
1896
1928
|
function loadApmDotEnvIfPresent() {
|
|
1897
|
-
const p =
|
|
1898
|
-
if (!
|
|
1929
|
+
const p = join11(WORKSPACE_APM_DIR, ".env");
|
|
1930
|
+
if (!existsSync9(p)) {
|
|
1899
1931
|
return;
|
|
1900
1932
|
}
|
|
1901
1933
|
let text;
|
|
@@ -1925,14 +1957,14 @@ function loadApmDotEnvIfPresent() {
|
|
|
1925
1957
|
}
|
|
1926
1958
|
|
|
1927
1959
|
// src/commands/deploy/internal/minio.ts
|
|
1928
|
-
import { statSync as
|
|
1960
|
+
import { statSync as statSync5 } from "node:fs";
|
|
1929
1961
|
import { readdir } from "node:fs/promises";
|
|
1930
1962
|
import path6 from "node:path";
|
|
1931
1963
|
import * as Minio from "minio";
|
|
1932
1964
|
var DEFAULT_MAX_FILE_SIZE_MB = 50;
|
|
1933
1965
|
async function isDirectoryPath(dir) {
|
|
1934
1966
|
try {
|
|
1935
|
-
const st =
|
|
1967
|
+
const st = statSync5(dir);
|
|
1936
1968
|
return st.isDirectory();
|
|
1937
1969
|
} catch {
|
|
1938
1970
|
return false;
|
|
@@ -1962,7 +1994,7 @@ async function collectFiles(root) {
|
|
|
1962
1994
|
if (e.isDirectory()) {
|
|
1963
1995
|
await walk(abs, rel);
|
|
1964
1996
|
} else if (e.isFile()) {
|
|
1965
|
-
const st =
|
|
1997
|
+
const st = statSync5(abs);
|
|
1966
1998
|
out.push({
|
|
1967
1999
|
absPath: abs,
|
|
1968
2000
|
relativePath: rel.replace(/\\/g, "/"),
|
|
@@ -2280,6 +2312,11 @@ function buildProgram() {
|
|
|
2280
2312
|
).action(async () => {
|
|
2281
2313
|
await runUpdate();
|
|
2282
2314
|
});
|
|
2315
|
+
program.command("update-skills").description(
|
|
2316
|
+
"\u5220\u9664\u5DE5\u4F5C\u533A .apm/skills \u540E\uFF0C\u4ECE\u5F53\u524D CLI \u5185\u7F6E\u6A21\u677F\u91CD\u65B0\u590D\u5236\u6280\u80FD\u76EE\u5F55"
|
|
2317
|
+
).action(async () => {
|
|
2318
|
+
await runUpdateSkills();
|
|
2319
|
+
});
|
|
2283
2320
|
program.command("pull").description(
|
|
2284
2321
|
"GET /api/cli/requirements/pull\uFF0C\u540C\u6B65\u6570\u636E\u4E0E\u9644\u4EF6\u5230 .apm/workitems/<\u9700\u6C42ID>"
|
|
2285
2322
|
).argument("<requirementId>", "\u9700\u6C42 ID").action(async (requirementId) => {
|
package/package.json
CHANGED
|
@@ -39,7 +39,7 @@ description: 根据需求 ID 读取 prd.md 与 reviews.xml,润色为简短、
|
|
|
39
39
|
| 属性 / 节点 | 含义 |
|
|
40
40
|
| --------------- | --------------------------------------------------------------------------------------- |
|
|
41
41
|
| `start` / `end` | 锚定 `prd.md` 行号(1-based,闭区间),合并时优先据此定位段落 |
|
|
42
|
-
| `kind` | `clarify` / `difficulty` / `business`
|
|
42
|
+
| `kind` | `clarify` / `difficulty` / `business`;**仅处理含非空 `reply` 的条目**(历史 `coordination` 无 reply,忽略) |
|
|
43
43
|
| `body` | 评审意见(辅助理解,不作为正文来源) |
|
|
44
44
|
| `reply` | 产品回复,**并入正文**的权威补充 |
|
|
45
45
|
|
|
@@ -47,7 +47,7 @@ description: 根据需求 ID 读取 prd.md 与 reviews.xml,润色为简短、
|
|
|
47
47
|
|
|
48
48
|
1. **正文 = 需求原文 + 已拍板补充**:将各 `<item>` 中非空 `reply` 并入 `prd.md` 对应行区间(或该行所在需求点);未回复的评审本次不处理。
|
|
49
49
|
2. **定位**:优先按 `start`–`end` 行号找到段落;行号区间与章节标题不一致时,以**业务语义**归入最近的需求点,**不要**机械插入到错误章节。
|
|
50
|
-
3. **冲突**:`reply` 与原文冲突时以 `reply`
|
|
50
|
+
3. **冲突**:`reply` 与原文冲突时以 `reply` 为准;无 `reply` 的条目(含历史联调类)润色时忽略。
|
|
51
51
|
4. **同一议题只写一处**;合并后篇幅短于或接近原文。
|
|
52
52
|
5. **图片**:原文图片语法原样保留;理解图片后把规则写入对应需求点的文字描述。
|
|
53
53
|
|
|
@@ -31,7 +31,7 @@ description: 结合本仓库上下文对需求做结构化评审,只有当用
|
|
|
31
31
|
|
|
32
32
|
**去重规则**(按立场选用):
|
|
33
33
|
|
|
34
|
-
- **前端立场**:只写界面与交互侧待澄清,不写落库/API
|
|
34
|
+
- **前端立场**:只写界面与交互侧待澄清,不写落库/API 契约类问题。
|
|
35
35
|
- **后端立场**:只写流程、规则与数据侧待澄清,不写控件选型、Tab 布局等纯 UI 问题。
|
|
36
36
|
- **全栈立场**:同一业务点合并为一条 `clarify`,**不要**拆成两句意思重复的问题。
|
|
37
37
|
|
|
@@ -42,15 +42,25 @@ description: 结合本仓库上下文对需求做结构化评审,只有当用
|
|
|
42
42
|
3. **锚定 PRD**:每条 `items[]` 必须写 **`anchor: { start, end }`**,与 Read 读到的 `prd.md` 行号一致;**不**在 `body` 里复述该段需求原文。
|
|
43
43
|
4. **问题 = 文档缺口**:只写 PRD **未写清**且**影响本端理解边界**的点。已写清楚的规则不要重复质疑。
|
|
44
44
|
|
|
45
|
-
##
|
|
45
|
+
## 禁止输出的内容(不要写入 items)
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
以下内容**一律跳过**,不得创建任何评审条目(**禁止**使用已废弃的 `coordination` 类型):
|
|
48
48
|
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
49
|
+
- 接口字段名、请求体结构、子表编码、API 路径
|
|
50
|
+
- 前后端谁传哪个参数、与现网实现对齐的改造方案
|
|
51
|
+
- 「联调依赖」「需与后端/前端对齐接口」类事项
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
上述问题属于**技术评审**范畴,由研发在技术评审阶段处理,本产品评审只面向产品经理可回答的业务澄清。
|
|
54
|
+
|
|
55
|
+
## 允许的 kind
|
|
56
|
+
|
|
57
|
+
仅可使用以下三种(YAML 小写):
|
|
58
|
+
|
|
59
|
+
| `kind` | 含义 | 何时使用 |
|
|
60
|
+
| ------------ | ---------- | -------------------------------- |
|
|
61
|
+
| `clarify` | 待澄清 | 文档未写清、影响本端理解边界 |
|
|
62
|
+
| `difficulty` | 实现难度高 | 仅改造面大或业务逻辑影响大时 |
|
|
63
|
+
| `business` | 业务合理性 | 仅方案与业务目标明显冲突或非较优 |
|
|
54
64
|
|
|
55
65
|
## 评审原则
|
|
56
66
|
|
|
@@ -75,14 +85,14 @@ description: 结合本仓库上下文对需求做结构化评审,只有当用
|
|
|
75
85
|
### 步骤 3:评审与提交
|
|
76
86
|
|
|
77
87
|
1. **锁定立场**,填入 YAML `reviewer.stance`(`frontend` / `backend` / `fullstack`)。
|
|
78
|
-
2. **拆条**:每条对应 `prd.md` 连续行号;按需设置 `kind`(`clarify` / `difficulty` / `business
|
|
88
|
+
2. **拆条**:每条对应 `prd.md` 连续行号;按需设置 `kind`(`clarify` / `difficulty` / `business`)与 `body`。
|
|
79
89
|
3. **成文**:按 **[output-template.md](./output-template.md)** 写 YAML。
|
|
80
90
|
4. 使用 **Write** 写入临时文件(建议 `/tmp/apm-review-<需求ID>.yaml`)。
|
|
81
91
|
5. **自检**:
|
|
82
92
|
- 每条均有 `anchor`,且无全篇评审;
|
|
83
93
|
- `stance` 与立场一致;
|
|
84
94
|
- `clarify` 均为产品可回答的业务问题;
|
|
85
|
-
- `coordination`
|
|
95
|
+
- **未**出现 `coordination` 或联调/API 契约类条目;
|
|
86
96
|
- 无重复语义的条目。
|
|
87
97
|
6. 在项目根目录执行:
|
|
88
98
|
|
|
@@ -29,11 +29,6 @@ items:
|
|
|
29
29
|
kind: difficulty
|
|
30
30
|
body: |
|
|
31
31
|
- 用产品语言说明改造面:涉及哪些页面/流程/规则,为何面大
|
|
32
|
-
|
|
33
|
-
- anchor: { start: 44, end: 55 }
|
|
34
|
-
kind: coordination
|
|
35
|
-
body: |
|
|
36
|
-
- 需与后端对齐的接口/字段诉求(不用产品回复)
|
|
37
32
|
```
|
|
38
33
|
|
|
39
34
|
## kind 与技能表述
|
|
@@ -43,17 +38,18 @@ items:
|
|
|
43
38
|
| `clarify` | 待澄清 | 文档未写清、影响本端理解边界 |
|
|
44
39
|
| `difficulty` | 实现难度高 | 仅改造面大或业务逻辑影响大时 |
|
|
45
40
|
| `business` | 业务合理性 | 仅方案明显不合理时 |
|
|
46
|
-
|
|
41
|
+
|
|
42
|
+
**禁止**使用 `coordination`(联调依赖已废弃):接口/联调类事项不要写入评审。
|
|
47
43
|
|
|
48
44
|
## 字段规则
|
|
49
45
|
|
|
50
|
-
| 字段 | 规则
|
|
51
|
-
| ----------------- |
|
|
52
|
-
| `reviewer.stance` | 必填:`frontend` / `backend` / `fullstack`
|
|
53
|
-
| `reviewer.model` | 建议填写;可被 CLI `--model` 覆盖
|
|
46
|
+
| 字段 | 规则 |
|
|
47
|
+
| ----------------- | ---------------------------------------------------------- |
|
|
48
|
+
| `reviewer.stance` | 必填:`frontend` / `backend` / `fullstack` |
|
|
49
|
+
| `reviewer.model` | 建议填写;可被 CLI `--model` 覆盖 |
|
|
54
50
|
| `anchor` | 必填 `{ start, end }`,1-based 闭区间,与 Read `prd.md` 行号一致 |
|
|
55
|
-
| `body` | 必填;产品语言;**禁止**全篇评审(必须有行号)
|
|
56
|
-
| 同区间多类型 |
|
|
51
|
+
| `body` | 必填;产品语言;**禁止**全篇评审(必须有行号) |
|
|
52
|
+
| 同区间多类型 | 可拆成多条 `items`(例如同时 `clarify` 与 `difficulty`) |
|
|
57
53
|
|
|
58
54
|
## 提交命令
|
|
59
55
|
|