ai-project-manage-cli 5.0.12 → 5.0.14

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
@@ -499,8 +499,9 @@ function formatSessionMessagesXml(sessionId, messages) {
499
499
  }
500
500
 
501
501
  // src/commands/sync-session-attachments.ts
502
- import { writeFileSync as writeFileSync3 } from "fs";
502
+ import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
503
503
  import { join as join4 } from "path";
504
+ var MANIFEST_FILE = ".sync-manifest.json";
504
505
  async function downloadAttachment(cfg, sessionId, attachmentId) {
505
506
  const base = cfg.baseUrl.trim().replace(/\/+$/, "");
506
507
  const q = new URLSearchParams({ sessionId, attachmentId });
@@ -515,16 +516,65 @@ async function downloadAttachment(cfg, sessionId, attachmentId) {
515
516
  }
516
517
  return Buffer.from(await res.arrayBuffer());
517
518
  }
519
+ function loadManifest(dir) {
520
+ const path8 = join4(dir, MANIFEST_FILE);
521
+ if (!existsSync2(path8)) {
522
+ return { version: 1, attachments: {} };
523
+ }
524
+ try {
525
+ const parsed = JSON.parse(
526
+ readFileSync3(path8, "utf8")
527
+ );
528
+ if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
529
+ return parsed;
530
+ }
531
+ } catch {
532
+ }
533
+ return { version: 1, attachments: {} };
534
+ }
535
+ function saveManifest(dir, manifest) {
536
+ writeFileSync3(
537
+ join4(dir, MANIFEST_FILE),
538
+ `${JSON.stringify(manifest, null, 2)}
539
+ `,
540
+ "utf8"
541
+ );
542
+ }
543
+ function isAttachmentUpToDate(entry, item, dest) {
544
+ if (!entry || !existsSync2(dest)) return false;
545
+ if (entry.name !== item.name) return false;
546
+ const createdAt = item.createdAt ?? "";
547
+ return entry.createdAt === createdAt;
548
+ }
518
549
  async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
519
- if (attachments.length === 0) return;
520
550
  const dir = join4(sessionDir(sessionId, apmRoot), SESSION_ATTACHMENTS_SUBDIR);
521
551
  await ensureDirExists(dir);
552
+ if (attachments.length === 0) {
553
+ saveManifest(dir, { version: 1, attachments: {} });
554
+ return;
555
+ }
556
+ const manifest = loadManifest(dir);
557
+ const nextManifest = { version: 1, attachments: {} };
522
558
  for (const item of attachments) {
523
559
  const dest = join4(dir, item.name);
560
+ const entry = manifest.attachments[item.id];
561
+ const createdAt = item.createdAt ?? "";
562
+ if (isAttachmentUpToDate(entry, item, dest)) {
563
+ nextManifest.attachments[item.id] = entry;
564
+ console.log(
565
+ `[apm] \u9644\u4EF6\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: ${SESSION_ATTACHMENTS_SUBDIR}/${item.name}`
566
+ );
567
+ continue;
568
+ }
524
569
  const buffer = await downloadAttachment(cfg, sessionId, item.id);
525
570
  writeFileSync3(dest, buffer);
571
+ nextManifest.attachments[item.id] = {
572
+ name: item.name,
573
+ createdAt
574
+ };
526
575
  console.log(`[apm] \u5DF2\u4E0B\u8F7D\u9644\u4EF6: ${SESSION_ATTACHMENTS_SUBDIR}/${item.name}`);
527
576
  }
577
+ saveManifest(dir, nextManifest);
528
578
  }
529
579
 
530
580
  // src/commands/pull.ts
@@ -592,7 +642,7 @@ async function runPull(sessionId, apmRoot) {
592
642
  import { spawnSync } from "child_process";
593
643
 
594
644
  // src/version.ts
595
- import { readFileSync as readFileSync3 } from "fs";
645
+ import { readFileSync as readFileSync4 } from "fs";
596
646
  import { dirname as dirname2, join as join6 } from "path";
597
647
  import { fileURLToPath as fileURLToPath2 } from "url";
598
648
  var CLI_PACKAGE_NAME = "ai-project-manage-cli";
@@ -600,7 +650,7 @@ function readCliVersion() {
600
650
  try {
601
651
  const dir = dirname2(fileURLToPath2(import.meta.url));
602
652
  const pkgPath = join6(dir, "..", "package.json");
603
- const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
653
+ const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
604
654
  return pkg.version ?? "0.0.0";
605
655
  } catch {
606
656
  return "0.0.0";
@@ -667,14 +717,14 @@ async function runUpdate() {
667
717
  }
668
718
 
669
719
  // src/commands/update-skills.ts
670
- import { existsSync as existsSync3, mkdirSync as mkdirSync4, statSync as statSync3 } from "fs";
720
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, statSync as statSync3 } from "fs";
671
721
  import { join as join8 } from "path";
672
722
 
673
723
  // src/skills-sync.ts
674
724
  import {
675
725
  copyFileSync,
676
726
  cpSync as cpSync2,
677
- existsSync as existsSync2,
727
+ existsSync as existsSync3,
678
728
  mkdirSync as mkdirSync3,
679
729
  readdirSync as readdirSync2,
680
730
  rmSync,
@@ -690,14 +740,14 @@ function sanitizeSkillDirName(name) {
690
740
  return trimmed.replace(/[/\\:*?"<>|]/g, "_");
691
741
  }
692
742
  function listBaseSkillDirNames() {
693
- if (!existsSync2(BASE_SKILLS_TEMPLATE_DIR)) return [];
743
+ if (!existsSync3(BASE_SKILLS_TEMPLATE_DIR)) return [];
694
744
  return readdirSync2(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
695
745
  const path8 = join7(BASE_SKILLS_TEMPLATE_DIR, name);
696
746
  return statSync2(path8).isDirectory();
697
747
  });
698
748
  }
699
749
  function syncAgentsGuide(apmDir) {
700
- if (!existsSync2(AGENTS_TEMPLATE_PATH)) return false;
750
+ if (!existsSync3(AGENTS_TEMPLATE_PATH)) return false;
701
751
  mkdirSync3(apmDir, { recursive: true });
702
752
  copyFileSync(AGENTS_TEMPLATE_PATH, join7(apmDir, "AGENTS.md"));
703
753
  return true;
@@ -730,7 +780,7 @@ function syncSupplementarySkills(skillsDir, list) {
730
780
  written.push(dirName);
731
781
  }
732
782
  const removed = [];
733
- if (!existsSync2(skillsDir)) return { written, skipped, removed };
783
+ if (!existsSync3(skillsDir)) return { written, skipped, removed };
734
784
  for (const entry of readdirSync2(skillsDir)) {
735
785
  const full = join7(skillsDir, entry);
736
786
  if (!statSync2(full).isDirectory()) continue;
@@ -745,7 +795,7 @@ function syncSupplementarySkills(skillsDir, list) {
745
795
  // src/commands/update-skills.ts
746
796
  async function runUpdateSkills() {
747
797
  const apmDir = workspaceApmDir();
748
- if (!existsSync3(apmDir)) {
798
+ if (!existsSync4(apmDir)) {
749
799
  console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
750
800
  process.exit(1);
751
801
  }
@@ -786,7 +836,7 @@ async function runUpdateSkills() {
786
836
  }
787
837
 
788
838
  // src/commands/sync-document.ts
789
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
839
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
790
840
  async function runSyncDocument(sessionId, options) {
791
841
  const trimmedSessionId = sessionId.trim();
792
842
  if (!trimmedSessionId) {
@@ -799,7 +849,7 @@ async function runSyncDocument(sessionId, options) {
799
849
  process.exit(1);
800
850
  }
801
851
  const absPath = resolveSessionDocumentPath(trimmedSessionId, fileArg);
802
- if (!existsSync4(absPath)) {
852
+ if (!existsSync5(absPath)) {
803
853
  const docsDir = sessionDocsDir(trimmedSessionId);
804
854
  console.error(
805
855
  `[apm] \u6587\u6863\u4E0D\u5B58\u5728: ${absPath}
@@ -807,7 +857,7 @@ async function runSyncDocument(sessionId, options) {
807
857
  );
808
858
  process.exit(1);
809
859
  }
810
- const content = readFileSync4(absPath, "utf8");
860
+ const content = readFileSync5(absPath, "utf8");
811
861
  const name = documentPlatformName(absPath);
812
862
  const cfg = await ensureLoggedConfig();
813
863
  const api = createApmApiClient(cfg);
@@ -1156,19 +1206,19 @@ async function runConnect(options) {
1156
1206
  import path5 from "node:path";
1157
1207
 
1158
1208
  // src/commands/deploy/internal/apm-config.ts
1159
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
1209
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
1160
1210
  import { resolve as resolve4 } from "node:path";
1161
1211
  function loadApmConfig(options) {
1162
1212
  const p = resolve4(
1163
1213
  process.cwd(),
1164
1214
  options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
1165
1215
  );
1166
- if (!existsSync5(p)) {
1216
+ if (!existsSync6(p)) {
1167
1217
  console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
1168
1218
  process.exit(1);
1169
1219
  }
1170
1220
  try {
1171
- const raw = readFileSync5(p, "utf8");
1221
+ const raw = readFileSync6(p, "utf8");
1172
1222
  return JSON.parse(raw);
1173
1223
  } catch (e) {
1174
1224
  console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
@@ -1265,7 +1315,7 @@ import path4 from "node:path";
1265
1315
  import Docker from "dockerode";
1266
1316
 
1267
1317
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
1268
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
1318
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "node:fs";
1269
1319
  import path from "node:path";
1270
1320
  function asOptionalTlsBuffer(value) {
1271
1321
  if (typeof value !== "string") {
@@ -1277,8 +1327,8 @@ function asOptionalTlsBuffer(value) {
1277
1327
  if (normalized === "") {
1278
1328
  return void 0;
1279
1329
  }
1280
- if (existsSync6(normalized)) {
1281
- return readFileSync6(normalized);
1330
+ if (existsSync7(normalized)) {
1331
+ return readFileSync7(normalized);
1282
1332
  }
1283
1333
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
1284
1334
  if (looksLikePath) {
@@ -1488,7 +1538,7 @@ var DockerodeClient = class {
1488
1538
  var createDockerodeClient = (config) => new DockerodeClient(config);
1489
1539
 
1490
1540
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
1491
- import { existsSync as existsSync7, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
1541
+ import { existsSync as existsSync8, readFileSync as readFileSync8, statSync as statSync4 } from "node:fs";
1492
1542
  import path2 from "node:path";
1493
1543
  function stripSurroundingQuotes(value) {
1494
1544
  const t = value.trim();
@@ -1505,10 +1555,10 @@ function loadEnvFromFile(envFilePath) {
1505
1555
  return {};
1506
1556
  }
1507
1557
  const targetPath = path2.resolve(envFilePath);
1508
- if (!existsSync7(targetPath) || !statSync4(targetPath).isFile()) {
1558
+ if (!existsSync8(targetPath) || !statSync4(targetPath).isFile()) {
1509
1559
  return {};
1510
1560
  }
1511
- const raw = readFileSync7(targetPath, "utf-8");
1561
+ const raw = readFileSync8(targetPath, "utf-8");
1512
1562
  const result = {};
1513
1563
  for (const line of raw.split(/\r?\n/)) {
1514
1564
  const normalized = line.trim();
@@ -1679,12 +1729,12 @@ function dockerPushImage(params, cwd) {
1679
1729
  }
1680
1730
 
1681
1731
  // src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
1682
- import { existsSync as existsSync8 } from "node:fs";
1732
+ import { existsSync as existsSync9 } from "node:fs";
1683
1733
  import path3 from "node:path";
1684
1734
  function resolveDockerBuildPaths(cwd) {
1685
1735
  const dockerfilePath = path3.join(cwd, "Dockerfile");
1686
1736
  Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
1687
- if (!existsSync8(dockerfilePath)) {
1737
+ if (!existsSync9(dockerfilePath)) {
1688
1738
  throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
1689
1739
  }
1690
1740
  Logger.info("\u2713 Dockerfile \u5B58\u5728");
@@ -1813,16 +1863,16 @@ import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
1813
1863
  import path7 from "node:path";
1814
1864
 
1815
1865
  // src/commands/deploy/internal/load-apm-dotenv.ts
1816
- import { existsSync as existsSync9, readFileSync as readFileSync8 } from "node:fs";
1866
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "node:fs";
1817
1867
  import { join as join9 } from "node:path";
1818
1868
  function loadApmDotEnvIfPresent() {
1819
1869
  const p = join9(workspaceApmDir(), ".env");
1820
- if (!existsSync9(p)) {
1870
+ if (!existsSync10(p)) {
1821
1871
  return;
1822
1872
  }
1823
1873
  let text;
1824
1874
  try {
1825
- text = readFileSync8(p, "utf8");
1875
+ text = readFileSync9(p, "utf8");
1826
1876
  } catch {
1827
1877
  return;
1828
1878
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "5.0.12",
3
+ "version": "5.0.14",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -17,7 +17,8 @@
17
17
  1. 读取 `.apm/sessions/<会话ID>/session.yaml`,必要时读取`messages.xml` 了解当前会话状态与历史消息
18
18
  2. 根据你的名字从 session.yaml 中找到你对应的人设(system_persona)
19
19
  3. 根据人设完成用户指定的任务
20
- 4. 任务有阶段行进展或者任务完成后必须使用 `apm append-message` 回复消息
20
+ 4. 任务有阶段行进展或者任务完成后必须使用 `apm append-message` 回复消息,回复要求简洁不用对本轮工作进行总结
21
+ 5. 工作日志放到 `.apm/sessions/<会话ID>/docs/<position>.md` 中,并执行`apm sync-document <会话ID> --file=<position>`同步到远程
21
22
 
22
23
  ## 目录声明
23
24
 
@@ -1,28 +1,27 @@
1
1
  ## 工作流程
2
+
2
3
  1. 选择开发模式:**Quick/Spec**
3
4
 
4
- **Quick 开发**(满足越多越适用):
5
- - 影响范围局部:少量文件或单一层次(例如仅前端组件、或仅一个后端模块小改)。
6
- - 无新表结构/大规模迁移/权限模型变更。
7
- - PRD 验收点清晰且数量少(经验上 **≤3** 条独立验收维度)。
8
- - 不需要跨多服务的架构裁定即可开工。
9
- **Spec 开发**:(命中任一条即可):
10
- - 前后端联动、多包改造或新公共抽象。
11
- - 新数据模型、迁移、或安全/审计/权限相关。
12
- - PRD 范围大、条款多,或存在明显「待确认/多方案」需先规划。
13
- - 评估认为不先产出 **proposal / design / specs /
5
+ **Quick 开发**(满足越多越适用): - 影响范围局部:少量文件或单一层次(例如仅前端组件、或仅一个后端模块小改)。 - 无新表结构/大规模迁移/权限模型变更。 - PRD 验收点清晰且数量少(经验上 **≤3** 条独立验收维度)。 - 不需要跨多服务的架构裁定即可开工。
6
+ **Spec 开发**:(命中任一条即可): - 前后端联动、多包改造或新公共抽象。 - 新数据模型、迁移、或安全/审计/权限相关。 - PRD 范围大、条款多,或存在明显「待确认/多方案」需先规划。 - 评估认为不先产出 **proposal / design / specs / tasks** 不宜直接编码。
14
7
 
15
- 2. 如果为 **Quick 开发**(子Agent中)执行:
8
+ 2. 如果为 **Quick 开发**(子 Agent 中)执行:
16
9
 
17
- + 父 Agent 已通过 **Read** 掌握 `PRD.md`;若启动新子 Agent,在委派提示中写明会话ID、消息ID、工作项路径、以及「实现须严格对照 PRD,改动范围最小化」。
18
- + 使用 **Task** 工具,`subagent_type: generalPurpose`,**readonly: false**,委派子 Agent:
19
- - 按需 **Read** `.apm/sessions/<会话ID>/docs/PRD.md`
20
- - 按 PRD 直接改代码;遵守本仓库构建与依赖约定(AGENTS.md)。
21
- - 完成后在返回中说明:改了哪些路径、如何对照验收、是否通过本地可执行的检查(若子 Agent 跑了 `rushx build` / 测试等则写明结果)。
10
+ - 父 Agent 已通过 **Read** 掌握 `PRD.md`;若启动新子 Agent,在委派提示中写明会话 ID、消息 ID、工作项路径、以及「实现须严格对照 PRD,改动范围最小化;完成后若有代码改动须单独 `git commit`」。
11
+ - 使用 **Task** 工具,`subagent_type: generalPurpose`,**readonly: false**,委派子 Agent:
12
+ - 按需 **Read** `.apm/sessions/<会话ID>/docs/PRD.md`
13
+ - 按 PRD 直接改代码;遵守本仓库构建与依赖约定(AGENTS.md)。
14
+ - **Git**:实现与自洽验收通过后,若有代码改动,**立即 `git add` + `git commit` 一次**(Quick 通常为单次交付,**一次实现 = 一个 commit**;勿拆成无意义碎 commit)。提交信息建议包含 `seesionId`(可从 `session.yaml` 或 PRD 获取)与 PRD 摘要或验收点简述。
15
+ - 完成后在返回中说明:改了哪些路径、如何对照验收、是否通过本地可执行的检查(若子 Agent 跑了 `rushx build` / 测试等则写明结果);若有 commit,写明 **short-sha** 与 **subject**,无代码改动则注明跳过 commit。
22
16
 
23
- 3. 如果为 **Spec 开发** (子Agent中)执行:
17
+ 3. 如果为 **Spec 开发** (子 Agent 中)执行:
24
18
 
25
19
  - 父 Agent **Read** **apm-propose**、**apm-apply-change** 的 `SKILL.md`(**仅** `.apm/skills/` 下路径,见上节)。
26
20
  - **子 Agent A(规划)**:Task `generalPurpose`,提示其自行 **Read** `.apm/skills/apm-propose/SKILL.md` 并完整遵循:在 `.apm/sessions/<sessionId>/` 生成 **proposal、design、specs、tasks** 等工件(顺序与依赖以 SKILL 为准)。
27
21
  - **子 Agent B(实现)**:待 A 成功落盘后,再 Task `generalPurpose`,提示其自行 **Read** `.apm/skills/apm-apply-change/SKILL.md` 并完整遵循:按 **`tasks.md`** 驱动实现与勾选;遵守该技能中的停止条件与 commit 约定。
28
22
  - 若 **apm-propose** 未产出可用 **`tasks.md`**,不得强行进入 **apm-apply-change**;表格中标记阻塞原因。
23
+
24
+ 4. 提交并 push 代码
25
+
26
+ - Quick / Spec 子 Agent 完成且本地已有 commit 时,父 Agent **立即 `git push`**(当前分支首次 push 用 `git push -u origin HEAD`)。
27
+ - 无本地 commit、无远程或未配置 upstream 时说明原因,勿强行 push。