ai-project-manage-cli 7.1.9 → 7.1.11
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 +2 -2
- package/dist/webide-message-worker.js +251 -23
- package/package.json +1 -1
- package/template/AGENTS.md +5 -3
- package/template/rules/reply.md +2 -2
- package/template/rules/webide_git_commit.md +13 -0
- package/template/rules/webide_testcase.md +47 -0
- package/template/skills/apm-dev/SKILL.md +1 -1
- package/template/skills/apm-diff-review/SKILL.md +2 -2
- package/template/skills/apm-update-plan/SKILL.md +1 -1
- package/template/skills/apm-write-checklist/SKILL.md +1 -1
- package/template/skills/apm-write-plan/SKILL.md +2 -2
package/dist/index.js
CHANGED
|
@@ -6700,7 +6700,7 @@ async function syncCursorMessageLog(cfg, ctx, events) {
|
|
|
6700
6700
|
function createAppendMessageCustomTools(cfg, messageId, appendContent) {
|
|
6701
6701
|
const doAppend = appendContent ?? ((content) => appendMessageContent(cfg, messageId, content));
|
|
6702
6702
|
return {
|
|
6703
|
-
|
|
6703
|
+
AppendMessage: {
|
|
6704
6704
|
description: "\u5411\u5F53\u524D\u4F1A\u8BDD\u6D88\u606F\u8FFD\u52A0\u56DE\u590D\u5185\u5BB9\u3002\u53EF\u591A\u6B21\u8C03\u7528\u8865\u5145\u8FDB\u5C55\uFF1B\u88AB @ \u65F6\u6536\u5230\u540E\u5E94\u5148\u7B80\u77ED\u786E\u8BA4\u518D\u6267\u884C\u4EFB\u52A1\u3002",
|
|
6705
6705
|
inputSchema: {
|
|
6706
6706
|
type: "object",
|
|
@@ -6722,7 +6722,7 @@ function createAppendMessageCustomTools(cfg, messageId, appendContent) {
|
|
|
6722
6722
|
}
|
|
6723
6723
|
try {
|
|
6724
6724
|
await doAppend(content);
|
|
6725
|
-
console.log(`[apm]
|
|
6725
|
+
console.log(`[apm] AppendMessage \u5DF2\u8FFD\u52A0: messageId=${messageId}`);
|
|
6726
6726
|
return "\u5DF2\u8FFD\u52A0\u6D88\u606F\u5185\u5BB9";
|
|
6727
6727
|
} catch (err) {
|
|
6728
6728
|
const detail = err instanceof Error ? err.message : String(err);
|
|
@@ -839,6 +839,21 @@ async function commitWorkingTreeIfDirty(cwd, message) {
|
|
|
839
839
|
console.log(`[apm] \u5DF2\u63D0\u4EA4\u5DE5\u4F5C\u533A\u53D8\u66F4: ${commitMessage}`);
|
|
840
840
|
return true;
|
|
841
841
|
}
|
|
842
|
+
async function commitWorkspaceReposIfDirty(workdir, message) {
|
|
843
|
+
const workdirPath = resolveWorkdirPath(workdir);
|
|
844
|
+
const manifest = loadWorkspaceReposCache(workdirPath);
|
|
845
|
+
const repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
|
|
846
|
+
let committed = 0;
|
|
847
|
+
for (const gitRoot of repoRoots) {
|
|
848
|
+
const label = formatRepoLabel(workdirPath, gitRoot);
|
|
849
|
+
const did = await commitWorkingTreeIfDirty(gitRoot, message);
|
|
850
|
+
if (did) {
|
|
851
|
+
committed += 1;
|
|
852
|
+
console.log(`[apm] WebIDE \u515C\u5E95\u63D0\u4EA4 @ ${label}`);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
return committed;
|
|
856
|
+
}
|
|
842
857
|
function branchNameForTask(taskId) {
|
|
843
858
|
const id = taskId.trim();
|
|
844
859
|
if (!id) {
|
|
@@ -1420,7 +1435,7 @@ async function appendMessageContent(cfg, messageId, content) {
|
|
|
1420
1435
|
function createAppendMessageCustomTools(cfg, messageId, appendContent2) {
|
|
1421
1436
|
const doAppend = appendContent2 ?? ((content) => appendMessageContent(cfg, messageId, content));
|
|
1422
1437
|
return {
|
|
1423
|
-
|
|
1438
|
+
AppendMessage: {
|
|
1424
1439
|
description: "\u5411\u5F53\u524D\u4F1A\u8BDD\u6D88\u606F\u8FFD\u52A0\u56DE\u590D\u5185\u5BB9\u3002\u53EF\u591A\u6B21\u8C03\u7528\u8865\u5145\u8FDB\u5C55\uFF1B\u88AB @ \u65F6\u6536\u5230\u540E\u5E94\u5148\u7B80\u77ED\u786E\u8BA4\u518D\u6267\u884C\u4EFB\u52A1\u3002",
|
|
1425
1440
|
inputSchema: {
|
|
1426
1441
|
type: "object",
|
|
@@ -1442,7 +1457,7 @@ function createAppendMessageCustomTools(cfg, messageId, appendContent2) {
|
|
|
1442
1457
|
}
|
|
1443
1458
|
try {
|
|
1444
1459
|
await doAppend(content);
|
|
1445
|
-
console.log(`[apm]
|
|
1460
|
+
console.log(`[apm] AppendMessage \u5DF2\u8FFD\u52A0: messageId=${messageId}`);
|
|
1446
1461
|
return "\u5DF2\u8FFD\u52A0\u6D88\u606F\u5185\u5BB9";
|
|
1447
1462
|
} catch (err) {
|
|
1448
1463
|
const detail = err instanceof Error ? err.message : String(err);
|
|
@@ -2386,13 +2401,13 @@ function normalizeLocalDocumentPath(path) {
|
|
|
2386
2401
|
return segments.join("/");
|
|
2387
2402
|
}
|
|
2388
2403
|
function readLocalManifest(apmRoot) {
|
|
2389
|
-
const
|
|
2390
|
-
if (!existsSync5(
|
|
2404
|
+
const manifestPath3 = join6(projectDocumentsDir(apmRoot), MANIFEST_FILE);
|
|
2405
|
+
if (!existsSync5(manifestPath3)) {
|
|
2391
2406
|
return null;
|
|
2392
2407
|
}
|
|
2393
2408
|
try {
|
|
2394
2409
|
return JSON.parse(
|
|
2395
|
-
readFileSync6(
|
|
2410
|
+
readFileSync6(manifestPath3, "utf8")
|
|
2396
2411
|
);
|
|
2397
2412
|
} catch {
|
|
2398
2413
|
return null;
|
|
@@ -2599,26 +2614,221 @@ async function ensureWebIdeDraftPullRequests(cfg, taskId, workdir) {
|
|
|
2599
2614
|
}
|
|
2600
2615
|
}
|
|
2601
2616
|
|
|
2602
|
-
// src/
|
|
2603
|
-
import {
|
|
2604
|
-
import { join as join8 } from "path";
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2617
|
+
// src/version.ts
|
|
2618
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
2619
|
+
import { dirname as dirname5, join as join8 } from "path";
|
|
2620
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2621
|
+
function readCliVersion() {
|
|
2622
|
+
try {
|
|
2623
|
+
const dir = dirname5(fileURLToPath2(import.meta.url));
|
|
2624
|
+
const pkgPath = join8(dir, "..", "package.json");
|
|
2625
|
+
const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
2626
|
+
return pkg.version ?? "0.0.0";
|
|
2627
|
+
} catch {
|
|
2628
|
+
return "0.0.0";
|
|
2609
2629
|
}
|
|
2610
2630
|
}
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2631
|
+
|
|
2632
|
+
// src/commands/update-skills.ts
|
|
2633
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync8, statSync as statSync4 } from "fs";
|
|
2634
|
+
import { join as join10 } from "path";
|
|
2635
|
+
|
|
2636
|
+
// src/skills-sync.ts
|
|
2637
|
+
import {
|
|
2638
|
+
copyFileSync as copyFileSync2,
|
|
2639
|
+
cpSync,
|
|
2640
|
+
existsSync as existsSync6,
|
|
2641
|
+
mkdirSync as mkdirSync7,
|
|
2642
|
+
readdirSync as readdirSync4,
|
|
2643
|
+
rmSync as rmSync2,
|
|
2644
|
+
statSync as statSync3,
|
|
2645
|
+
writeFileSync as writeFileSync9
|
|
2646
|
+
} from "fs";
|
|
2647
|
+
import { join as join9 } from "path";
|
|
2648
|
+
var AGENTS_TEMPLATE_PATH = join9(CLI_TEMPLATE_DIR, "AGENTS.md");
|
|
2649
|
+
var BASE_SKILLS_TEMPLATE_DIR = join9(CLI_TEMPLATE_DIR, "skills");
|
|
2650
|
+
var BASE_RULES_TEMPLATE_DIR = join9(CLI_TEMPLATE_DIR, "rules");
|
|
2651
|
+
function sanitizeSkillDirName(name) {
|
|
2652
|
+
const trimmed = name.trim();
|
|
2653
|
+
if (!trimmed) return "skill";
|
|
2654
|
+
return trimmed.replace(/[/\\:*?"<>|]/g, "_");
|
|
2655
|
+
}
|
|
2656
|
+
function listBaseSkillDirNames() {
|
|
2657
|
+
if (!existsSync6(BASE_SKILLS_TEMPLATE_DIR)) return [];
|
|
2658
|
+
return readdirSync4(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
|
|
2659
|
+
const path = join9(BASE_SKILLS_TEMPLATE_DIR, name);
|
|
2660
|
+
return statSync3(path).isDirectory();
|
|
2661
|
+
});
|
|
2662
|
+
}
|
|
2663
|
+
function syncAgentsGuide(apmDir) {
|
|
2664
|
+
if (!existsSync6(AGENTS_TEMPLATE_PATH)) return false;
|
|
2665
|
+
mkdirSync7(apmDir, { recursive: true });
|
|
2666
|
+
copyFileSync2(AGENTS_TEMPLATE_PATH, join9(apmDir, "AGENTS.md"));
|
|
2667
|
+
return true;
|
|
2668
|
+
}
|
|
2669
|
+
function listBaseRuleFileNames() {
|
|
2670
|
+
if (!existsSync6(BASE_RULES_TEMPLATE_DIR)) return [];
|
|
2671
|
+
return readdirSync4(BASE_RULES_TEMPLATE_DIR).filter((name) => {
|
|
2672
|
+
const path = join9(BASE_RULES_TEMPLATE_DIR, name);
|
|
2673
|
+
return statSync3(path).isFile();
|
|
2674
|
+
});
|
|
2675
|
+
}
|
|
2676
|
+
function syncBaseRules(rulesDir) {
|
|
2677
|
+
mkdirSync7(rulesDir, { recursive: true });
|
|
2678
|
+
const names = listBaseRuleFileNames();
|
|
2679
|
+
for (const name of names) {
|
|
2680
|
+
const src = join9(BASE_RULES_TEMPLATE_DIR, name);
|
|
2681
|
+
const dest = join9(rulesDir, name);
|
|
2682
|
+
copyFileSync2(src, dest);
|
|
2683
|
+
}
|
|
2684
|
+
return names;
|
|
2685
|
+
}
|
|
2686
|
+
function syncBaseSkills(skillsDir) {
|
|
2687
|
+
mkdirSync7(skillsDir, { recursive: true });
|
|
2688
|
+
const names = listBaseSkillDirNames();
|
|
2689
|
+
for (const name of names) {
|
|
2690
|
+
const src = join9(BASE_SKILLS_TEMPLATE_DIR, name);
|
|
2691
|
+
const dest = join9(skillsDir, name);
|
|
2692
|
+
cpSync(src, dest, { recursive: true, force: true });
|
|
2693
|
+
}
|
|
2694
|
+
return names;
|
|
2695
|
+
}
|
|
2696
|
+
function syncSupplementarySkills(skillsDir, list) {
|
|
2697
|
+
const baseNames = new Set(listBaseSkillDirNames());
|
|
2698
|
+
const apiDirNames = /* @__PURE__ */ new Set();
|
|
2699
|
+
const written = [];
|
|
2700
|
+
const skipped = [];
|
|
2701
|
+
for (const skill of list) {
|
|
2702
|
+
const dirName = sanitizeSkillDirName(skill.name);
|
|
2703
|
+
apiDirNames.add(dirName);
|
|
2704
|
+
if (baseNames.has(dirName)) {
|
|
2705
|
+
skipped.push(dirName);
|
|
2706
|
+
continue;
|
|
2707
|
+
}
|
|
2708
|
+
const skillDir = join9(skillsDir, dirName);
|
|
2709
|
+
mkdirSync7(skillDir, { recursive: true });
|
|
2710
|
+
writeFileSync9(join9(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
|
|
2711
|
+
written.push(dirName);
|
|
2712
|
+
}
|
|
2713
|
+
const removed = [];
|
|
2714
|
+
if (!existsSync6(skillsDir)) return { written, skipped, removed };
|
|
2715
|
+
for (const entry of readdirSync4(skillsDir)) {
|
|
2716
|
+
const full = join9(skillsDir, entry);
|
|
2717
|
+
if (!statSync3(full).isDirectory()) continue;
|
|
2718
|
+
if (baseNames.has(entry)) continue;
|
|
2719
|
+
if (apiDirNames.has(entry)) continue;
|
|
2720
|
+
rmSync2(full, { recursive: true, force: true });
|
|
2721
|
+
removed.push(entry);
|
|
2722
|
+
}
|
|
2723
|
+
return { written, skipped, removed };
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
// src/commands/update-skills.ts
|
|
2727
|
+
async function syncWorkspaceSkills(cfg, workdir) {
|
|
2728
|
+
const apmDir = workspaceApmDir(workdir);
|
|
2729
|
+
const fsApmDir = toFsPath(apmDir);
|
|
2730
|
+
if (!existsSync7(fsApmDir)) {
|
|
2731
|
+
throw new Error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
|
|
2732
|
+
}
|
|
2733
|
+
const apmStat = statSync4(fsApmDir);
|
|
2734
|
+
if (!apmStat.isDirectory()) {
|
|
2735
|
+
throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
|
|
2736
|
+
}
|
|
2737
|
+
const api = createApmApiClient(cfg);
|
|
2738
|
+
const { list } = await api.cli.listSkills({});
|
|
2739
|
+
if (syncAgentsGuide(apmDir)) {
|
|
2740
|
+
console.log("[apm] \u5DF2\u540C\u6B65 APM \u6307\u5357: .apm/AGENTS.md");
|
|
2741
|
+
}
|
|
2742
|
+
const rulesDir = join10(apmDir, "rules");
|
|
2743
|
+
const ruleNames = syncBaseRules(rulesDir);
|
|
2744
|
+
for (const name of ruleNames) {
|
|
2745
|
+
console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u89C4\u5219: rules/${name}`);
|
|
2746
|
+
}
|
|
2747
|
+
const skillsDir = join10(apmDir, "skills");
|
|
2748
|
+
mkdirSync8(toFsPath(skillsDir), { recursive: true });
|
|
2749
|
+
const baseNames = syncBaseSkills(skillsDir);
|
|
2750
|
+
for (const name of baseNames) {
|
|
2751
|
+
console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u6280\u80FD: skills/${name}/`);
|
|
2752
|
+
}
|
|
2753
|
+
const { written, skipped, removed } = syncSupplementarySkills(
|
|
2754
|
+
skillsDir,
|
|
2755
|
+
list
|
|
2756
|
+
);
|
|
2757
|
+
for (const name of written) {
|
|
2758
|
+
console.log(`[apm] \u5DF2\u5199\u5165\u8865\u5145\u6280\u80FD: skills/${name}/SKILL.md`);
|
|
2759
|
+
}
|
|
2760
|
+
for (const name of skipped) {
|
|
2761
|
+
console.log(
|
|
2762
|
+
`[apm] \u5DF2\u8DF3\u8FC7\u4E0E\u57FA\u7840\u6280\u80FD\u540C\u540D\u7684\u8865\u5145\u6280\u80FD: ${name}\uFF08\u4FDD\u7559\u6A21\u677F\u7248\u672C\uFF09`
|
|
2763
|
+
);
|
|
2764
|
+
}
|
|
2765
|
+
for (const name of removed) {
|
|
2766
|
+
console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u8865\u5145\u6280\u80FD: skills/${name}/`);
|
|
2767
|
+
}
|
|
2768
|
+
console.log(
|
|
2769
|
+
`[apm] \u540C\u6B65\u5B8C\u6210\uFF1A${ruleNames.length} \u4E2A\u57FA\u7840\u89C4\u5219\uFF0C${baseNames.length} \u4E2A\u57FA\u7840\u6280\u80FD\uFF0C${written.length} \u4E2A\u8865\u5145\u6280\u80FD`
|
|
2770
|
+
);
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
// src/commands/connect/cli-version-sync.ts
|
|
2774
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync10 } from "fs";
|
|
2775
|
+
import { join as join11 } from "path";
|
|
2776
|
+
var CLI_VERSION_FILE = ".cli-version.json";
|
|
2777
|
+
function manifestPath2(apmDir) {
|
|
2778
|
+
return join11(apmDir, CLI_VERSION_FILE);
|
|
2779
|
+
}
|
|
2780
|
+
function loadManifest(apmDir) {
|
|
2781
|
+
const path = toFsPath(manifestPath2(apmDir));
|
|
2782
|
+
if (!existsSync8(path)) {
|
|
2783
|
+
return null;
|
|
2784
|
+
}
|
|
2785
|
+
try {
|
|
2786
|
+
const parsed = JSON.parse(
|
|
2787
|
+
readFileSync9(path, "utf8")
|
|
2788
|
+
);
|
|
2789
|
+
if (parsed?.version === 1 && typeof parsed.cliVersion === "string" && parsed.cliVersion.trim()) {
|
|
2790
|
+
return parsed;
|
|
2791
|
+
}
|
|
2792
|
+
} catch {
|
|
2793
|
+
}
|
|
2794
|
+
return null;
|
|
2795
|
+
}
|
|
2796
|
+
function saveManifest(apmDir, cliVersion) {
|
|
2797
|
+
const manifest = { version: 1, cliVersion };
|
|
2798
|
+
writeFileSync10(
|
|
2799
|
+
toFsPath(manifestPath2(apmDir)),
|
|
2800
|
+
`${JSON.stringify(manifest, null, 2)}
|
|
2801
|
+
`,
|
|
2802
|
+
"utf8"
|
|
2803
|
+
);
|
|
2804
|
+
}
|
|
2805
|
+
var syncedInSession = /* @__PURE__ */ new Map();
|
|
2806
|
+
function shouldSyncSkillsForCliVersion(workdir, currentVersion) {
|
|
2807
|
+
const cached = syncedInSession.get(workdir);
|
|
2808
|
+
if (cached === currentVersion) {
|
|
2809
|
+
return false;
|
|
2810
|
+
}
|
|
2811
|
+
const stored = loadManifest(workspaceApmDir(workdir));
|
|
2812
|
+
if (stored?.cliVersion === currentVersion) {
|
|
2813
|
+
syncedInSession.set(workdir, currentVersion);
|
|
2814
|
+
return false;
|
|
2815
|
+
}
|
|
2816
|
+
return true;
|
|
2817
|
+
}
|
|
2818
|
+
function markSkillsSyncedForCliVersion(workdir, cliVersion) {
|
|
2819
|
+
saveManifest(workspaceApmDir(workdir), cliVersion);
|
|
2820
|
+
syncedInSession.set(workdir, cliVersion);
|
|
2619
2821
|
}
|
|
2620
2822
|
|
|
2621
2823
|
// src/commands/connect/handle-webide-message.ts
|
|
2824
|
+
var WEBIDE_CODE_CHANGE_ACTIONS = /* @__PURE__ */ new Set([
|
|
2825
|
+
"skip-plan",
|
|
2826
|
+
"start-develop",
|
|
2827
|
+
"fix-and-retest"
|
|
2828
|
+
]);
|
|
2829
|
+
function shouldCommitAfterWebIdeMessage(action) {
|
|
2830
|
+
return WEBIDE_CODE_CHANGE_ACTIONS.has(action);
|
|
2831
|
+
}
|
|
2622
2832
|
async function updateStatus(cfg, messageId, status) {
|
|
2623
2833
|
const api = createApmApiClient(cfg);
|
|
2624
2834
|
await api.cli.webideUpdateMessageStatus({ id: messageId, status });
|
|
@@ -2646,7 +2856,15 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
|
|
|
2646
2856
|
if (!didInit) {
|
|
2647
2857
|
assertApmGitignoredInRepo(workdir);
|
|
2648
2858
|
}
|
|
2649
|
-
|
|
2859
|
+
const cliVersion = readCliVersion();
|
|
2860
|
+
if (shouldSyncSkillsForCliVersion(workdir, cliVersion)) {
|
|
2861
|
+
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
2862
|
+
console.log(
|
|
2863
|
+
`[apm] CLI \u7248\u672C ${cliVersion} \u4E0E\u5DE5\u4F5C\u533A\u8BB0\u5F55\u4E0D\u4E00\u81F4\uFF0C\u6267\u884C update-skills`
|
|
2864
|
+
);
|
|
2865
|
+
await syncWorkspaceSkills(cfg, workdir);
|
|
2866
|
+
markSkillsSyncedForCliVersion(workdir, cliVersion);
|
|
2867
|
+
}
|
|
2650
2868
|
resolveWorkspaceRepos(workdir);
|
|
2651
2869
|
if (shouldRunBranch(branchCacheKey, workdir)) {
|
|
2652
2870
|
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
@@ -2687,8 +2905,8 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
|
|
|
2687
2905
|
signal
|
|
2688
2906
|
}),
|
|
2689
2907
|
enableWebIdePlanTools: true,
|
|
2690
|
-
// sandbox
|
|
2691
|
-
enableSandbox:
|
|
2908
|
+
// 本地 SDK sandbox 会拦截 custom-user-tools MCP,WebIDE 先关闭
|
|
2909
|
+
enableSandbox: false,
|
|
2692
2910
|
taskId,
|
|
2693
2911
|
createRemoteLogSync: (agentId) => {
|
|
2694
2912
|
saveWebIdeAgentId(workdir, taskId, agentId);
|
|
@@ -2747,6 +2965,16 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
|
|
|
2747
2965
|
);
|
|
2748
2966
|
}
|
|
2749
2967
|
}
|
|
2968
|
+
if (shouldCommitAfterWebIdeMessage(msg.action)) {
|
|
2969
|
+
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
2970
|
+
const committed = await commitWorkspaceReposIfDirty(
|
|
2971
|
+
workdir,
|
|
2972
|
+
`chore(webide): sync after ${msg.action} (${taskId})`
|
|
2973
|
+
);
|
|
2974
|
+
console.log(
|
|
2975
|
+
`[apm] webide \u515C\u5E95 commit action=${msg.action} repos=${committed}`
|
|
2976
|
+
);
|
|
2977
|
+
}
|
|
2750
2978
|
await logSyncRef.current?.markRun(outcome.runId, "finished");
|
|
2751
2979
|
await updateStatus(cfg, messageId, "SUCCESS");
|
|
2752
2980
|
console.log(
|
package/package.json
CHANGED
package/template/AGENTS.md
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
1. 读取 `.apm/sessions/<会话ID>/session.yaml`
|
|
10
10
|
2. 读取 `.apm/rules/reply.md`
|
|
11
|
-
3. **立即**用 `
|
|
12
|
-
4. 按需阅读 `docs/` 下的文档,有进展继续调用 `
|
|
11
|
+
3. **立即**用 `AppendMessage` 工具回复(可先简短确认,再补充)
|
|
12
|
+
4. 按需阅读 `docs/` 下的文档,有进展继续调用 `AppendMessage`
|
|
13
13
|
|
|
14
14
|
#### 重任务(开发、写方案、评审)
|
|
15
15
|
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
4. 任务有阶段性进展或者任务完成后必须回复消息(具体见规则 `reply.md`)
|
|
20
20
|
5. 写工作日志(具体见规则 `write_doc.md`)
|
|
21
21
|
|
|
22
|
-
**禁止**在未回复前先读完所有 docs。有进展就先调用 `
|
|
22
|
+
**禁止**在未回复前先读完所有 docs。有进展就先调用 `AppendMessage`。
|
|
23
23
|
|
|
24
24
|
### 目录指引
|
|
25
25
|
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
- 规则: `.apm/rules/*`
|
|
30
30
|
- reply.md:当需要回复消息时需要读取这个文档,记住回复规则
|
|
31
31
|
- write_doc.md:当需要写文档时需要读取这个文档,记住写文档的规则
|
|
32
|
+
- webide_git_commit.md:WebIDE 开发/修码时读取,按规范分轮 commit
|
|
33
|
+
- webide_testcase.md:WebIDE 生成测试用例时读取,按规范编写并提交用例
|
|
32
34
|
|
|
33
35
|
- 仓库项目上下文(`.apm/project/`):
|
|
34
36
|
|
package/template/rules/reply.md
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
## WebIDE Git 提交规范
|
|
2
|
+
|
|
3
|
+
适用场景:WebIDE 开发阶段(`skip-plan` / `start-develop`)及修码复测(`fix-and-retest`)中涉及代码改动时。
|
|
4
|
+
|
|
5
|
+
### 分轮提交
|
|
6
|
+
|
|
7
|
+
1. 按需求点分轮实现:每完成一个可独立验收的需求点,立即 `git add` 并 commit,不要把多轮改动攒成一次提交。
|
|
8
|
+
2. commit message 用简短中文说明本轮改动(例:`feat: 实现登录表单校验`)。
|
|
9
|
+
3. 多仓工作区时,只在实际改动过的仓库提交。
|
|
10
|
+
|
|
11
|
+
### 结束前检查
|
|
12
|
+
|
|
13
|
+
本轮全部工作结束前,确认工作区干净(`git status` 无未提交变更);若仍有残留改动,补一次 commit。
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
## WebIDE 测试用例编写规范
|
|
2
|
+
|
|
3
|
+
适用场景:WebIDE `generate-cases` 阶段,根据任务需求与本地代码改动生成自动化测试用例。
|
|
4
|
+
|
|
5
|
+
### 产出方式
|
|
6
|
+
|
|
7
|
+
1. 仅在当前工作区 cwd 内阅读/搜索代码,不要访问工作区外路径。
|
|
8
|
+
2. 用中文经 `AppendMessage` 简要说明用例覆盖范围。
|
|
9
|
+
3. **必须**调用 `UpsertWebIdeTestCases`,一次提交完整用例列表;工具会写入平台并进入待测阶段。
|
|
10
|
+
4. 不要只在对话里列出用例而不调用工具。
|
|
11
|
+
|
|
12
|
+
### 用例字段
|
|
13
|
+
|
|
14
|
+
每条用例包含:
|
|
15
|
+
|
|
16
|
+
| 字段 | 要求 |
|
|
17
|
+
| ------------- | ---------------------------------------------------------------- |
|
|
18
|
+
| `name` | 简短稳定的用例标识,可用中文或英文短句,避免空泛(如「测试 1」) |
|
|
19
|
+
| `description` | 说明本用例验证什么(中文),一两句即可 |
|
|
20
|
+
| `steps` | 有序、可执行的步骤数组;每步一句,写清操作与预期结果 |
|
|
21
|
+
| `sortOrder` | 可选,展示顺序(从 0 起) |
|
|
22
|
+
|
|
23
|
+
### 编写原则
|
|
24
|
+
|
|
25
|
+
1. **对齐需求与改动**:覆盖本任务实现的核心路径;对照本地 diff / 关键代码,不要凭空编造未实现的功能。
|
|
26
|
+
2. **主路径优先**:至少覆盖成功主流程;再按风险补边界、校验失败、权限拒绝等必要负面场景。
|
|
27
|
+
3. **步骤可执行**:步骤应能被人或自动化按字面执行(打开哪页、点哪个按钮、输入什么、看到什么),禁止「验证功能正常」这类空话。
|
|
28
|
+
4. **一条一焦点**:每条用例聚焦一个可验收点,不要把无关场景塞进同一步骤列表。
|
|
29
|
+
5. **适量即可**:宁缺毋滥;通常数条到十余条足够,避免大量重复或琐碎用例。
|
|
30
|
+
|
|
31
|
+
### 调用示例
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
UpsertWebIdeTestCases({
|
|
35
|
+
cases: [
|
|
36
|
+
{
|
|
37
|
+
name: "登录成功进入首页",
|
|
38
|
+
description: "使用合法账号密码登录后应进入系统首页",
|
|
39
|
+
steps: [
|
|
40
|
+
"打开登录页",
|
|
41
|
+
"输入正确的用户名与密码,点击登录",
|
|
42
|
+
"应跳转到首页,并显示当前用户名"
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
})
|
|
47
|
+
```
|
|
@@ -44,13 +44,13 @@ apm create-pr --session <会话ID> --title "<需求摘要>" --content "<计划
|
|
|
44
44
|
|
|
45
45
|
### 步骤 4:输出结论
|
|
46
46
|
|
|
47
|
-
**通过**:用 `
|
|
47
|
+
**通过**:用 `AppendMessage` 回复评审结论,并 `@项目经理` 交人验收,回复中必须包含:
|
|
48
48
|
|
|
49
49
|
1. 「diff 评审通过」+ 一句话改动概述;
|
|
50
50
|
2. **PR 链接**(上一步 `apm create-pr` 输出;创建失败则注明原因);
|
|
51
51
|
3. 提示按 `CHECKLIST.md` 逐条验收(部署与回归验收由人工或平台完成)。
|
|
52
52
|
|
|
53
|
-
**不通过**:用 `
|
|
53
|
+
**不通过**:用 `AppendMessage` 输出问题清单(每条注明文件 + 问题 + 依据哪条计划/需求),`@` 对应工程师打回修改。**禁止自己动手改。**
|
|
54
54
|
|
|
55
55
|
---
|
|
56
56
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
- 影响实现范围时 → 修订「实现步骤 / 改动文件白名单」;后端涉及接口则同步 `docs/API.md`。
|
|
15
15
|
5. 重写 PLAN §1「需求理解」(融合 PM 确认口径)。
|
|
16
16
|
6. **`apm sync-document`** 同步 PLAN(及 API 文档)。
|
|
17
|
-
7. **`
|
|
17
|
+
7. **`AppendMessage`**:本端需求理解摘要 + @项目经理,请其回复「理解正确,可进入开发」。
|
|
18
18
|
8. **Read/Update** `TODO.md`:勾选相关待办。
|
|
19
19
|
|
|
20
20
|
**禁止** `apm assumptions --mark-backfilled`(已废弃)。
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
逐条对照需求原文与计划:
|
|
21
21
|
|
|
22
|
-
- **计划与需求矛盾、或需求关键点在计划中没有覆盖**:不要写清单,先用 `
|
|
22
|
+
- **计划与需求矛盾、或需求关键点在计划中没有覆盖**:不要写清单,先用 `AppendMessage` 指出问题并 `@` 对应工程师打回,等计划修订后再写。
|
|
23
23
|
- **开发门禁未通过**(`apm dev-gate` 返回 `readyForDev: false`):**不要写清单**,先提醒 PM / 执行 `apm-update-plan`。
|
|
24
24
|
- 计划覆盖完整:进入步骤 3。
|
|
25
25
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
**Read** `.apm/sessions/<会话ID>/TASK.md`。
|
|
20
20
|
|
|
21
|
-
- **不涉及本端改动**:`
|
|
21
|
+
- **不涉及本端改动**:`AppendMessage` 回复理由后结束。
|
|
22
22
|
- **涉及本端改动**:进入步骤 2。
|
|
23
23
|
|
|
24
24
|
### 步骤 2:有限调研
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
|
|
37
37
|
1. `apm sync-document --file FRONTEND-PLAN.md`(或 BACKEND)。
|
|
38
38
|
2. **有假设且 ASSUMPTIONS 已成功 sync**:@项目经理 请其在 Web **「待您确认」** Panel 点选;说明「以上假设确认前不开始开发」。
|
|
39
|
-
3. **无假设**:`
|
|
39
|
+
3. **无假设**:`AppendMessage` 发送「本端需求理解」摘要并 @项目经理 请确认。
|
|
40
40
|
|
|
41
41
|
> **禁止**在 ASSUMPTIONS sync 未成功时 @ 项目经理确认假设;PLAN 中「待确认 N 项」由服务端 sync ASSUMPTIONS 后自动更新,AI 不要手写具体数字。
|
|
42
42
|
|