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 +77 -27
- package/package.json +1 -1
- package/template/AGENTS.md +2 -1
- package/template/skills/apm-dev/SKILL.md +16 -17
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
|
|
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(
|
|
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
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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 =
|
|
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
|
|
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 (!
|
|
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 =
|
|
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
|
|
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 (
|
|
1281
|
-
return
|
|
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
|
|
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 (!
|
|
1558
|
+
if (!existsSync8(targetPath) || !statSync4(targetPath).isFile()) {
|
|
1509
1559
|
return {};
|
|
1510
1560
|
}
|
|
1511
|
-
const raw =
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
1870
|
+
if (!existsSync10(p)) {
|
|
1821
1871
|
return;
|
|
1822
1872
|
}
|
|
1823
1873
|
let text;
|
|
1824
1874
|
try {
|
|
1825
|
-
text =
|
|
1875
|
+
text = readFileSync9(p, "utf8");
|
|
1826
1876
|
} catch {
|
|
1827
1877
|
return;
|
|
1828
1878
|
}
|
package/package.json
CHANGED
package/template/AGENTS.md
CHANGED
|
@@ -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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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. 如果为
|
|
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。
|