ai-project-manage-cli 4.0.5 → 4.0.7

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { readFileSync as readFileSync10 } from "fs";
5
- import { dirname as dirname2, join as join9 } from "path";
5
+ import { dirname as dirname2, join as join10 } from "path";
6
6
  import { fileURLToPath as fileURLToPath2 } from "url";
7
7
  import { Command } from "commander";
8
8
 
@@ -116,6 +116,17 @@ var requestConfig = {
116
116
  path: "/cli/requirements/update-dev-status"
117
117
  })
118
118
  },
119
+ requirementAttachment: {
120
+ list: defineEndpoint({
121
+ method: "GET",
122
+ path: "/requirement-attachments/list"
123
+ }),
124
+ download: defineEndpoint({
125
+ method: "GET",
126
+ path: "/requirement-attachments/download",
127
+ mode: "download"
128
+ })
129
+ },
119
130
  requirementArtifact: {
120
131
  list: defineEndpoint({
121
132
  method: "GET",
@@ -246,7 +257,7 @@ async function runComment(requirementId, file, model) {
246
257
  import { randomUUID } from "crypto";
247
258
  import WebSocket from "ws";
248
259
  import { Agent } from "@cursor/sdk";
249
- import { join as join5 } from "path";
260
+ import { join as join6 } from "path";
250
261
 
251
262
  // src/session-utils.ts
252
263
  import { appendFileSync } from "fs";
@@ -381,8 +392,33 @@ ${JSON.stringify(event, null, 2)}
381
392
  };
382
393
 
383
394
  // src/commands/upload-artifact.ts
384
- import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
385
- import { join as join3, relative, sep } from "path";
395
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
396
+ import { join as join4, relative, sep } from "path";
397
+
398
+ // src/commands/sync-requirement-attachments.ts
399
+ import { existsSync as existsSync2, writeFileSync as writeFileSync2 } from "fs";
400
+ import { join as join3 } from "path";
401
+ var REQUIREMENT_ATTACHMENTS_SUBDIR = "attachments";
402
+ async function syncRequirementAttachments(api, requirementId, workitemsDir) {
403
+ const dir = join3(workitemsDir, REQUIREMENT_ATTACHMENTS_SUBDIR);
404
+ await ensureDirExists(dir);
405
+ const { items } = await api.requirementAttachment.list({ requirementId });
406
+ if (items.length === 0) return;
407
+ for (const item of items) {
408
+ const dest = join3(dir, item.fileName);
409
+ if (existsSync2(dest)) continue;
410
+ const { blob } = await api.requirementAttachment.download({
411
+ attachmentId: item.id
412
+ });
413
+ const buffer = Buffer.from(await blob.arrayBuffer());
414
+ writeFileSync2(dest, buffer);
415
+ console.log(
416
+ `[apm] \u5DF2\u4E0B\u8F7D\u9644\u4EF6: ${REQUIREMENT_ATTACHMENTS_SUBDIR}/${item.fileName}`
417
+ );
418
+ }
419
+ }
420
+
421
+ // src/commands/upload-artifact.ts
386
422
  var EXCLUDED_RELATIVE_PATHS = /* @__PURE__ */ new Set([
387
423
  "defect.xml",
388
424
  "prd.md",
@@ -406,7 +442,8 @@ function* walkMarkdownFiles(dir) {
406
442
  const names = readdirSync2(dir);
407
443
  for (const name of names) {
408
444
  if (name.startsWith(".")) continue;
409
- const full = join3(dir, name);
445
+ if (name === REQUIREMENT_ATTACHMENTS_SUBDIR) continue;
446
+ const full = join4(dir, name);
410
447
  const st = statSync2(full);
411
448
  if (st.isDirectory()) {
412
449
  yield* walkMarkdownFiles(full);
@@ -421,7 +458,7 @@ async function runUploadArtifact(requirementId, workspaceDir) {
421
458
  const cfg = await ensureLoggedConfig();
422
459
  const api = createApmApiClient(cfg);
423
460
  const root = requirementWorkitemsDir(requirementId, workspaceDir);
424
- if (!existsSync2(root)) {
461
+ if (!existsSync3(root)) {
425
462
  console.error(
426
463
  `[apm] \u76EE\u5F55\u4E0D\u5B58\u5728: ${root}
427
464
  \u8BF7\u5148\u6267\u884C: apm pull ${requirementId}`
@@ -444,8 +481,8 @@ async function runUploadArtifact(requirementId, workspaceDir) {
444
481
  }
445
482
 
446
483
  // src/commands/pull.ts
447
- import { writeFileSync as writeFileSync2 } from "fs";
448
- import { join as join4 } from "path";
484
+ import { writeFileSync as writeFileSync3 } from "fs";
485
+ import { join as join5 } from "path";
449
486
  import { stringify as yamlStringify } from "yaml";
450
487
  var PULL_ARTIFACT_FILE_NAMES = ["api.md", "backend.md"];
451
488
  function normalizeArtifactPath(fileName) {
@@ -555,13 +592,13 @@ async function runPull(requirementId, workspaceDir) {
555
592
  },
556
593
  { lineWidth: 0 }
557
594
  );
558
- writeFileSync2(
559
- join4(WORKITEMS_DIR, "requirement-status.yaml"),
595
+ writeFileSync3(
596
+ join5(WORKITEMS_DIR, "requirement-status.yaml"),
560
597
  statusYaml.endsWith("\n") ? statusYaml : `${statusYaml}
561
598
  `,
562
599
  "utf8"
563
600
  );
564
- writeFileSync2(join4(WORKITEMS_DIR, "prd.md"), req2.content || "", "utf8");
601
+ writeFileSync3(join5(WORKITEMS_DIR, "prd.md"), req2.content || "", "utf8");
565
602
  const reviews = data.reviews ?? [];
566
603
  const reviewsXml = [
567
604
  "<reviews>",
@@ -581,15 +618,15 @@ async function runPull(requirementId, workspaceDir) {
581
618
  "</reviews>",
582
619
  ""
583
620
  ].join("\n");
584
- writeFileSync2(join4(WORKITEMS_DIR, "reviews.xml"), reviewsXml, "utf8");
621
+ writeFileSync3(join5(WORKITEMS_DIR, "reviews.xml"), reviewsXml, "utf8");
585
622
  const defectsXml = defectsToXml(data.defects ?? []);
586
- writeFileSync2(join4(WORKITEMS_DIR, "defect.xml"), defectsXml, "utf8");
623
+ writeFileSync3(join5(WORKITEMS_DIR, "defect.xml"), defectsXml, "utf8");
587
624
  const testCasesXml = unknownArrayToXml(
588
625
  "testcases",
589
626
  "case",
590
627
  data.testCases ?? []
591
628
  );
592
- writeFileSync2(join4(WORKITEMS_DIR, "testcase.xml"), testCasesXml, "utf8");
629
+ writeFileSync3(join5(WORKITEMS_DIR, "testcase.xml"), testCasesXml, "utf8");
593
630
  for (const fileName of PULL_ARTIFACT_FILE_NAMES) {
594
631
  const content = await fetchArtifactContentByFileName(
595
632
  api,
@@ -597,9 +634,10 @@ async function runPull(requirementId, workspaceDir) {
597
634
  fileName
598
635
  );
599
636
  if (content !== null) {
600
- writeFileSync2(join4(WORKITEMS_DIR, fileName), content, "utf8");
637
+ writeFileSync3(join5(WORKITEMS_DIR, fileName), content, "utf8");
601
638
  }
602
639
  }
640
+ await syncRequirementAttachments(api, requirementId, WORKITEMS_DIR);
603
641
  return WORKITEMS_DIR;
604
642
  }
605
643
 
@@ -646,7 +684,7 @@ function runConnect(opts) {
646
684
  return;
647
685
  }
648
686
  const payload = msg.payload;
649
- const apmRoot = join5(payload.cwd, ".apm");
687
+ const apmRoot = join6(payload.cwd, ".apm");
650
688
  console.log("[apm] ROOT:", apmRoot);
651
689
  console.log("[apm] workflow node:", payload.nodeId);
652
690
  try {
@@ -734,8 +772,12 @@ function runConnect(opts) {
734
772
  nodeId: payload.nodeId
735
773
  });
736
774
  console.log("[apm] \u5DE5\u4F5C\u6D41\u8282\u70B9\u72B6\u6001:", confirmResult.nodeStatus);
737
- } catch {
738
- console.error("[apm] \u65E0\u6CD5\u89E3\u6790 WebSocket \u6D88\u606F:", text);
775
+ } catch (err) {
776
+ console.error(
777
+ "[apm] \u65E0\u6CD5\u89E3\u6790 WebSocket \u6D88\u606F:",
778
+ text,
779
+ err.message
780
+ );
739
781
  }
740
782
  });
741
783
  ws.on("error", (err) => {
@@ -749,17 +791,17 @@ function runConnect(opts) {
749
791
  }
750
792
 
751
793
  // src/commands/init.ts
752
- import { join as join6 } from "path";
753
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
794
+ import { join as join7 } from "path";
795
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
754
796
  async function runInit(name) {
755
797
  await ensureWorkspaceApmDirForInit();
756
798
  await copyTemplateFiles(WORKSPACE_APM_DIR);
757
799
  if (name) {
758
- const apmConfigPath = join6(WORKSPACE_APM_DIR, "apm.config.json");
800
+ const apmConfigPath = join7(WORKSPACE_APM_DIR, "apm.config.json");
759
801
  const config = readFileSync4(apmConfigPath, "utf8");
760
802
  const configJson = JSON.parse(config);
761
803
  configJson.name = name;
762
- writeFileSync3(apmConfigPath, JSON.stringify(configJson, null, 2), "utf8");
804
+ writeFileSync4(apmConfigPath, JSON.stringify(configJson, null, 2), "utf8");
763
805
  }
764
806
  }
765
807
 
@@ -954,10 +996,10 @@ async function runBranch(requirementId, options = {}) {
954
996
 
955
997
  // src/commands/refine.ts
956
998
  import { readFileSync as readFileSync5 } from "fs";
957
- import { join as join7 } from "path";
999
+ import { join as join8 } from "path";
958
1000
  async function runRefine(requirementId) {
959
1001
  const cfg = await ensureLoggedConfig();
960
- const filePath = join7(WORKSPACE_APM_DIR, "workitems", requirementId, "prd.md");
1002
+ const filePath = join8(WORKSPACE_APM_DIR, "workitems", requirementId, "prd.md");
961
1003
  const content = readFileSync5(filePath, "utf8");
962
1004
  const api = createApmApiClient(cfg);
963
1005
  const data = await api.cliRequirements.refine({ requirementId, content });
@@ -990,14 +1032,14 @@ async function runUpdateStatus(requirementId, status) {
990
1032
  import path5 from "node:path";
991
1033
 
992
1034
  // src/commands/deploy/internal/apm-config.ts
993
- import { existsSync as existsSync3, readFileSync as readFileSync6 } from "node:fs";
1035
+ import { existsSync as existsSync4, readFileSync as readFileSync6 } from "node:fs";
994
1036
  import { resolve as resolve4 } from "node:path";
995
1037
  function loadApmConfig(options) {
996
1038
  const p = resolve4(
997
1039
  process.cwd(),
998
1040
  options?.configPath ?? resolve4(WORKSPACE_APM_DIR, "apm.config.json")
999
1041
  );
1000
- if (!existsSync3(p)) {
1042
+ if (!existsSync4(p)) {
1001
1043
  console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
1002
1044
  process.exit(1);
1003
1045
  }
@@ -1101,7 +1143,7 @@ import path4 from "node:path";
1101
1143
  import Docker from "dockerode";
1102
1144
 
1103
1145
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
1104
- import { existsSync as existsSync4, readFileSync as readFileSync7 } from "node:fs";
1146
+ import { existsSync as existsSync5, readFileSync as readFileSync7 } from "node:fs";
1105
1147
  import path from "node:path";
1106
1148
  function asOptionalTlsBuffer(value) {
1107
1149
  if (typeof value !== "string") {
@@ -1113,7 +1155,7 @@ function asOptionalTlsBuffer(value) {
1113
1155
  if (normalized === "") {
1114
1156
  return void 0;
1115
1157
  }
1116
- if (existsSync4(normalized)) {
1158
+ if (existsSync5(normalized)) {
1117
1159
  return readFileSync7(normalized);
1118
1160
  }
1119
1161
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
@@ -1324,7 +1366,7 @@ var DockerodeClient = class {
1324
1366
  var createDockerodeClient = (config) => new DockerodeClient(config);
1325
1367
 
1326
1368
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
1327
- import { existsSync as existsSync5, readFileSync as readFileSync8, statSync as statSync3 } from "node:fs";
1369
+ import { existsSync as existsSync6, readFileSync as readFileSync8, statSync as statSync3 } from "node:fs";
1328
1370
  import path2 from "node:path";
1329
1371
  function stripSurroundingQuotes(value) {
1330
1372
  const t = value.trim();
@@ -1341,7 +1383,7 @@ function loadEnvFromFile(envFilePath) {
1341
1383
  return {};
1342
1384
  }
1343
1385
  const targetPath = path2.resolve(envFilePath);
1344
- if (!existsSync5(targetPath) || !statSync3(targetPath).isFile()) {
1386
+ if (!existsSync6(targetPath) || !statSync3(targetPath).isFile()) {
1345
1387
  return {};
1346
1388
  }
1347
1389
  const raw = readFileSync8(targetPath, "utf-8");
@@ -1515,12 +1557,12 @@ function dockerPushImage(params, cwd) {
1515
1557
  }
1516
1558
 
1517
1559
  // src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
1518
- import { existsSync as existsSync6 } from "node:fs";
1560
+ import { existsSync as existsSync7 } from "node:fs";
1519
1561
  import path3 from "node:path";
1520
1562
  function resolveDockerBuildPaths(cwd) {
1521
1563
  const dockerfilePath = path3.join(cwd, "Dockerfile");
1522
1564
  Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
1523
- if (!existsSync6(dockerfilePath)) {
1565
+ if (!existsSync7(dockerfilePath)) {
1524
1566
  throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
1525
1567
  }
1526
1568
  Logger.info("\u2713 Dockerfile \u5B58\u5728");
@@ -1649,11 +1691,11 @@ import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
1649
1691
  import path7 from "node:path";
1650
1692
 
1651
1693
  // src/commands/deploy/internal/load-apm-dotenv.ts
1652
- import { existsSync as existsSync7, readFileSync as readFileSync9 } from "node:fs";
1653
- import { join as join8 } from "node:path";
1694
+ import { existsSync as existsSync8, readFileSync as readFileSync9 } from "node:fs";
1695
+ import { join as join9 } from "node:path";
1654
1696
  function loadApmDotEnvIfPresent() {
1655
- const p = join8(WORKSPACE_APM_DIR, ".env");
1656
- if (!existsSync7(p)) {
1697
+ const p = join9(WORKSPACE_APM_DIR, ".env");
1698
+ if (!existsSync8(p)) {
1657
1699
  return;
1658
1700
  }
1659
1701
  let text;
@@ -2017,7 +2059,7 @@ function registerDeployCommands(program) {
2017
2059
  function readCliVersion() {
2018
2060
  try {
2019
2061
  const dir = dirname2(fileURLToPath2(import.meta.url));
2020
- const pkgPath = join9(dir, "..", "package.json");
2062
+ const pkgPath = join10(dir, "..", "package.json");
2021
2063
  const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
2022
2064
  return pkg.version ?? "0.0.0";
2023
2065
  } catch {
@@ -2032,22 +2074,24 @@ function buildProgram() {
2032
2074
  ).version(readCliVersion(), "-V, --version", "\u663E\u793A\u7248\u672C\u53F7").helpOption("-h, --help", "\u663E\u793A\u5E2E\u52A9").showHelpAfterError(true);
2033
2075
  program.command("login").description(
2034
2076
  "\u8C03\u7528 POST /api/auth/login\uFF0C\u5C06 token \u5199\u5165 ~/.config/apm/config.json"
2035
- ).requiredOption("--email <\u90AE\u7BB1>", "\u767B\u5F55\u90AE\u7BB1").requiredOption("--password <\u5BC6\u7801>", "\u767B\u5F55\u5BC6\u7801").option("--server <url>", "API \u6839\u5730\u5740\uFF0C\u4F8B\u5982 http://127.0.0.1:3000").action(async (opts) => {
2036
- await runLogin(opts);
2037
- });
2077
+ ).requiredOption("--email <\u90AE\u7BB1>", "\u767B\u5F55\u90AE\u7BB1").requiredOption("--password <\u5BC6\u7801>", "\u767B\u5F55\u5BC6\u7801").option("--server <url>", "API \u6839\u5730\u5740\uFF0C\u4F8B\u5982 http://127.0.0.1:3000").action(
2078
+ async (opts) => {
2079
+ await runLogin(opts);
2080
+ }
2081
+ );
2038
2082
  program.command("connect").description("\u8FDE\u63A5 WebSocket /ws/agent\uFF0C\u5E76\u6309\u56FA\u5B9A\u95F4\u9694\u53D1\u9001 HEARTBEAT").option("--server <url>", "API \u6839\u5730\u5740\uFF08\u5199\u5165\u672C\u5730\u914D\u7F6E\uFF09").action((opts) => {
2039
2083
  runConnect(opts);
2040
2084
  });
2041
- program.command("init").description(
2042
- "\u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u521D\u59CB\u5316 .apm \u6A21\u677F\uFF08\u82E5 .apm \u5DF2\u5B58\u5728\u4E14\u975E\u7A7A\u5219\u62A5\u9519\uFF09"
2043
- ).option("--name <name>", "\u5DE5\u4F5C\u76EE\u5F55\u540D\u79F0").action(async (opts) => {
2085
+ program.command("init").description("\u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u521D\u59CB\u5316 .apm \u6A21\u677F\uFF08\u82E5 .apm \u5DF2\u5B58\u5728\u4E14\u975E\u7A7A\u5219\u62A5\u9519\uFF09").option("--name <name>", "\u5DE5\u4F5C\u76EE\u5F55\u540D\u79F0").action(async (opts) => {
2044
2086
  await runInit(opts.name);
2045
2087
  });
2046
- program.command("pull").description("GET /api/cli/requirements/pull\uFF0C\u540C\u6B65\u6570\u636E\u5230 .apm \u76EE\u5F55").argument("<requirementId>", "\u9700\u6C42 ID").action(async (requirementId) => {
2088
+ program.command("pull").description(
2089
+ "GET /api/cli/requirements/pull\uFF0C\u540C\u6B65\u6570\u636E\u4E0E\u9644\u4EF6\u5230 .apm/workitems/<\u9700\u6C42ID>"
2090
+ ).argument("<requirementId>", "\u9700\u6C42 ID").action(async (requirementId) => {
2047
2091
  await runPull(requirementId);
2048
2092
  });
2049
2093
  program.command("upload-artifact").description(
2050
- "\u5148\u6E05\u7A7A\u8BE5\u9700\u6C42\u5728\u5E73\u53F0\u4E0A\u7684\u4EA7\u7269\u6587\u6863\uFF0C\u518D\u5C06 .apm/workitems/<\u9700\u6C42ID> \u4E0B Markdown \u540C\u6B65\u4E0A\u53BB\uFF08\u6392\u9664 pull \u7CFB\u7EDF\u6587\u4EF6\uFF09"
2094
+ "\u5148\u6E05\u7A7A\u8BE5\u9700\u6C42\u5728\u5E73\u53F0\u4E0A\u7684\u4EA7\u7269\u6587\u6863\uFF0C\u518D\u5C06 .apm/workitems/<\u9700\u6C42ID> \u4E0B Markdown \u540C\u6B65\u4E0A\u53BB\uFF08\u6392\u9664 pull \u7CFB\u7EDF\u6587\u4EF6\u4E0E attachments \u76EE\u5F55\uFF09"
2051
2095
  ).argument("<requirementId>", "\u9700\u6C42 ID").action(async (requirementId) => {
2052
2096
  await runUploadArtifact(requirementId);
2053
2097
  });
@@ -2056,11 +2100,9 @@ function buildProgram() {
2056
2100
  ).argument("<requirementId>", "\u9700\u6C42 ID").option(
2057
2101
  "-m, --message <text>",
2058
2102
  "\u5DF2\u5728\u76EE\u6807\u5206\u652F\u4E14\u9700\u63D0\u4EA4\u672C\u5730\u6539\u52A8\u65F6\u4F7F\u7528\u7684\u63D0\u4EA4\u8BF4\u660E\uFF08\u9ED8\u8BA4\u81EA\u52A8\u751F\u6210\uFF09"
2059
- ).action(
2060
- async (requirementId, opts) => {
2061
- await runBranch(requirementId, { message: opts.message });
2062
- }
2063
- );
2103
+ ).action(async (requirementId, opts) => {
2104
+ await runBranch(requirementId, { message: opts.message });
2105
+ });
2064
2106
  program.command("comment").description("POST /api/cli/requirements/comment\uFF08\u6B63\u6587\u6765\u81EA\u6587\u4EF6\uFF09").argument("<requirementId>", "\u9700\u6C42 ID").requiredOption("--file <path>", "\u8BC4\u8BBA\u6B63\u6587\u6587\u4EF6\u8DEF\u5F84").option("--model <model>", "\u8BC4\u8BBA\u6A21\u578B").action(
2065
2107
  async (requirementId, options) => {
2066
2108
  await runComment(requirementId, options.file, options.model);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "4.0.5",
3
+ "version": "4.0.7",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -10,10 +10,11 @@ workitems/<需求 ID>
10
10
 
11
11
  - `status.yaml`: 需求的状态(包含当前的分支名称,需求状态,缺陷列表及状态)
12
12
  - `prd.md`: pull 后写入的需求聚合正文
13
+ - `attachments/`: pull 后从平台同步的需求附件(本地已存在则跳过下载)
13
14
  - `reviews.xml`: pull 后写入的评论与回复
14
15
  - `defect.xml`: 缺陷信息
15
16
  - `testcase.xml`: 测试用例占位数据
16
17
  - `change.md`: 代码变更信息
17
18
  - `specs/proposal.md`: 提议
18
19
  - `specs/tasks.md`: 任务
19
- - `specs/`
20
+ - `specs/`