ai-project-manage-cli 3.0.16 → 3.0.18
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 +243 -235
- package/package.json +1 -1
- package/template/skills/apm-dev/SKILL.md +5 -23
package/dist/index.js
CHANGED
|
@@ -158,8 +158,8 @@ import { fileURLToPath } from "url";
|
|
|
158
158
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
159
159
|
var CLI_TEMPLATE_DIR = resolve(__dirname, "../template");
|
|
160
160
|
var WORKSPACE_APM_DIR = resolve(process.cwd(), ".apm");
|
|
161
|
-
function requirementWorkitemsDir(requirementId) {
|
|
162
|
-
return join2(
|
|
161
|
+
function requirementWorkitemsDir(requirementId, workspaceDir = WORKSPACE_APM_DIR) {
|
|
162
|
+
return join2(workspaceDir, "workitems", requirementId);
|
|
163
163
|
}
|
|
164
164
|
async function ensureLoggedConfig() {
|
|
165
165
|
const cfg = await ensureApmConfig();
|
|
@@ -227,7 +227,6 @@ async function runComment(requirementId, file, model) {
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
// src/commands/connect.ts
|
|
230
|
-
import { execSync } from "child_process";
|
|
231
230
|
import { randomUUID } from "crypto";
|
|
232
231
|
import WebSocket from "ws";
|
|
233
232
|
import { Agent } from "@cursor/sdk";
|
|
@@ -329,6 +328,228 @@ var EventSession = class {
|
|
|
329
328
|
}
|
|
330
329
|
};
|
|
331
330
|
|
|
331
|
+
// src/commands/upload-artifact.ts
|
|
332
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
333
|
+
import { join as join3, relative, sep } from "path";
|
|
334
|
+
var EXCLUDED_RELATIVE_PATHS = /* @__PURE__ */ new Set([
|
|
335
|
+
"defect.xml",
|
|
336
|
+
"prd.md",
|
|
337
|
+
"requirement-status.yaml",
|
|
338
|
+
"reviews.xml",
|
|
339
|
+
"testcase.xml"
|
|
340
|
+
]);
|
|
341
|
+
function toPosixRelative(root, absoluteFile) {
|
|
342
|
+
return relative(root, absoluteFile).split(sep).join("/");
|
|
343
|
+
}
|
|
344
|
+
function artifactTagFromRelPath(relPosix) {
|
|
345
|
+
const parts = relPosix.split("/");
|
|
346
|
+
const fileName = parts[parts.length - 1] ?? relPosix;
|
|
347
|
+
if (parts.length > 1) {
|
|
348
|
+
return parts[0] ?? fileName;
|
|
349
|
+
}
|
|
350
|
+
const dot = fileName.lastIndexOf(".");
|
|
351
|
+
return dot > 0 ? fileName.slice(0, dot) : fileName;
|
|
352
|
+
}
|
|
353
|
+
function* walkMarkdownFiles(dir) {
|
|
354
|
+
const names = readdirSync2(dir);
|
|
355
|
+
for (const name of names) {
|
|
356
|
+
if (name.startsWith(".")) continue;
|
|
357
|
+
const full = join3(dir, name);
|
|
358
|
+
const st = statSync2(full);
|
|
359
|
+
if (st.isDirectory()) {
|
|
360
|
+
yield* walkMarkdownFiles(full);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
if (!st.isFile()) continue;
|
|
364
|
+
if (!name.toLowerCase().endsWith(".md")) continue;
|
|
365
|
+
yield full;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async function deleteAllArtifactsForRequirement(api, requirementId) {
|
|
369
|
+
const pageSize = 100;
|
|
370
|
+
let page = 1;
|
|
371
|
+
const rows = [];
|
|
372
|
+
while (true) {
|
|
373
|
+
const batch = await api.requirementArtifact.list({
|
|
374
|
+
requirementId,
|
|
375
|
+
page,
|
|
376
|
+
pageSize
|
|
377
|
+
});
|
|
378
|
+
rows.push(...batch.items);
|
|
379
|
+
if (batch.total === 0 || rows.length >= batch.total) break;
|
|
380
|
+
page += 1;
|
|
381
|
+
}
|
|
382
|
+
for (const row of rows) {
|
|
383
|
+
await api.requirementArtifact.delete({ artifactId: row.id });
|
|
384
|
+
}
|
|
385
|
+
return rows.length;
|
|
386
|
+
}
|
|
387
|
+
async function runUploadArtifact(requirementId, workspaceDir) {
|
|
388
|
+
const cfg = await ensureLoggedConfig();
|
|
389
|
+
const api = createApmApiClient(cfg);
|
|
390
|
+
const root = requirementWorkitemsDir(requirementId, workspaceDir);
|
|
391
|
+
if (!existsSync2(root)) {
|
|
392
|
+
console.error(
|
|
393
|
+
`[apm] \u76EE\u5F55\u4E0D\u5B58\u5728: ${root}
|
|
394
|
+
\u8BF7\u5148\u6267\u884C: apm pull ${requirementId}`
|
|
395
|
+
);
|
|
396
|
+
process.exit(1);
|
|
397
|
+
}
|
|
398
|
+
const deleted = await deleteAllArtifactsForRequirement(api, requirementId);
|
|
399
|
+
console.log(`[apm] \u5DF2\u6E05\u7A7A\u9700\u6C42\u4EA7\u7269\u6587\u6863 ${deleted} \u6761`);
|
|
400
|
+
const paths = [...walkMarkdownFiles(root)];
|
|
401
|
+
let created = 0;
|
|
402
|
+
let skipped = 0;
|
|
403
|
+
for (const abs of paths) {
|
|
404
|
+
const relPosix = toPosixRelative(root, abs);
|
|
405
|
+
if (EXCLUDED_RELATIVE_PATHS.has(relPosix)) {
|
|
406
|
+
skipped += 1;
|
|
407
|
+
console.log(`[apm] \u8DF3\u8FC7\uFF08\u6392\u9664\u5217\u8868\uFF09: ${relPosix}`);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
const content = readFileSync3(abs, "utf8");
|
|
411
|
+
const tag = artifactTagFromRelPath(relPosix);
|
|
412
|
+
await api.requirementArtifact.create({
|
|
413
|
+
requirementId,
|
|
414
|
+
tag,
|
|
415
|
+
fileName: relPosix,
|
|
416
|
+
content
|
|
417
|
+
});
|
|
418
|
+
created += 1;
|
|
419
|
+
console.log(`[apm] \u5DF2\u4E0A\u4F20\u4EA7\u7269: ${relPosix} (tag=${tag})`);
|
|
420
|
+
}
|
|
421
|
+
console.log(
|
|
422
|
+
`[apm] \u5B8C\u6210\uFF1A\u5220\u9664 ${deleted}\uFF0C\u65B0\u5EFA ${created}\uFF0C\u8DF3\u8FC7\uFF08\u6392\u9664\uFF09 ${skipped}\uFF0C\u5171\u626B\u63CF ${paths.length} \u4E2A Markdown \u6587\u4EF6`
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// src/commands/pull.ts
|
|
427
|
+
import { writeFileSync as writeFileSync2 } from "fs";
|
|
428
|
+
import { join as join4 } from "path";
|
|
429
|
+
import { stringify as yamlStringify } from "yaml";
|
|
430
|
+
function valueToXmlContent(value) {
|
|
431
|
+
if (value === null || value === void 0) return "";
|
|
432
|
+
if (typeof value === "string") return xmlEscape(value);
|
|
433
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
434
|
+
return xmlEscape(String(value));
|
|
435
|
+
}
|
|
436
|
+
return `<![CDATA[${JSON.stringify(value)}]]>`;
|
|
437
|
+
}
|
|
438
|
+
function recordToXmlLines(record, indent) {
|
|
439
|
+
const lines = [];
|
|
440
|
+
for (const [rawKey, val] of Object.entries(record)) {
|
|
441
|
+
const key = /^[a-zA-Z_][\w.-]*$/.test(rawKey) ? rawKey : "field";
|
|
442
|
+
lines.push(`${indent}<${key}>${valueToXmlContent(val)}</${key}>`);
|
|
443
|
+
}
|
|
444
|
+
return lines;
|
|
445
|
+
}
|
|
446
|
+
function unknownArrayToXml(rootName, itemName, items) {
|
|
447
|
+
const lines = [`<${rootName}>`];
|
|
448
|
+
items.forEach((item, index) => {
|
|
449
|
+
if (item !== null && typeof item === "object" && !Array.isArray(item)) {
|
|
450
|
+
lines.push(` <${itemName} index="${index}">`);
|
|
451
|
+
lines.push(...recordToXmlLines(item, " "));
|
|
452
|
+
lines.push(` </${itemName}>`);
|
|
453
|
+
} else {
|
|
454
|
+
lines.push(
|
|
455
|
+
` <${itemName} index="${index}">${valueToXmlContent(
|
|
456
|
+
item
|
|
457
|
+
)}</${itemName}>`
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
lines.push(`</${rootName}>`, "");
|
|
462
|
+
return lines.join("\n");
|
|
463
|
+
}
|
|
464
|
+
function tasksForStatusYaml(tasks) {
|
|
465
|
+
return tasks.map((t) => ({
|
|
466
|
+
id: t.id,
|
|
467
|
+
title: t.title,
|
|
468
|
+
status: t.status,
|
|
469
|
+
executor: t.executorType
|
|
470
|
+
}));
|
|
471
|
+
}
|
|
472
|
+
function escapeForCdata(text) {
|
|
473
|
+
return text.replace(/\]\]>/g, "]]]]><![CDATA[>");
|
|
474
|
+
}
|
|
475
|
+
function defectsToXml(defects) {
|
|
476
|
+
const sorted = [...defects].sort(
|
|
477
|
+
(a, b) => new Date(a.createdAt ?? 0).getTime() - new Date(b.createdAt ?? 0).getTime()
|
|
478
|
+
);
|
|
479
|
+
const lines = ["<defects>"];
|
|
480
|
+
for (const d of sorted) {
|
|
481
|
+
lines.push(` <defect id="${xmlEscape(d.id)}">`);
|
|
482
|
+
lines.push(` <status>${xmlEscape(d.status)}</status>`);
|
|
483
|
+
lines.push(
|
|
484
|
+
` <current><![CDATA[${escapeForCdata(
|
|
485
|
+
d.currentState ?? ""
|
|
486
|
+
)}]]></current>`
|
|
487
|
+
);
|
|
488
|
+
lines.push(
|
|
489
|
+
` <expected><![CDATA[${escapeForCdata(
|
|
490
|
+
d.expectedEffect ?? ""
|
|
491
|
+
)}]]></expected>`
|
|
492
|
+
);
|
|
493
|
+
lines.push(` </defect>`);
|
|
494
|
+
}
|
|
495
|
+
lines.push("</defects>", "");
|
|
496
|
+
return lines.join("\n");
|
|
497
|
+
}
|
|
498
|
+
async function runPull(requirementId, workspaceDir) {
|
|
499
|
+
const cfg = await ensureLoggedConfig();
|
|
500
|
+
const api = createApmApiClient(cfg);
|
|
501
|
+
const data = await api.cliRequirements.pull({ requirementId });
|
|
502
|
+
const WORKITEMS_DIR = requirementWorkitemsDir(requirementId, workspaceDir);
|
|
503
|
+
await ensureDirExists(WORKITEMS_DIR);
|
|
504
|
+
const req2 = data.requirement;
|
|
505
|
+
const statusYaml = yamlStringify(
|
|
506
|
+
{
|
|
507
|
+
id: req2.id,
|
|
508
|
+
status: req2.status,
|
|
509
|
+
title: req2.title,
|
|
510
|
+
env: req2.envName || "",
|
|
511
|
+
tasks: tasksForStatusYaml(data.tasks ?? [])
|
|
512
|
+
},
|
|
513
|
+
{ lineWidth: 0 }
|
|
514
|
+
);
|
|
515
|
+
writeFileSync2(
|
|
516
|
+
join4(WORKITEMS_DIR, "requirement-status.yaml"),
|
|
517
|
+
statusYaml.endsWith("\n") ? statusYaml : `${statusYaml}
|
|
518
|
+
`,
|
|
519
|
+
"utf8"
|
|
520
|
+
);
|
|
521
|
+
writeFileSync2(join4(WORKITEMS_DIR, "prd.md"), req2.content || "", "utf8");
|
|
522
|
+
const reviews = data.reviews ?? [];
|
|
523
|
+
const reviewsXml = [
|
|
524
|
+
"<reviews>",
|
|
525
|
+
...reviews.map((r) => {
|
|
526
|
+
return [
|
|
527
|
+
` <review id="${xmlEscape(r.id)}">`,
|
|
528
|
+
` <model>${xmlEscape(r.model ?? "")}</model>`,
|
|
529
|
+
` <content>`,
|
|
530
|
+
`${xmlEscape(r.content ?? "")}`,
|
|
531
|
+
` </content>`,
|
|
532
|
+
` <reply>`,
|
|
533
|
+
`${xmlEscape(r.reply ?? "")}`,
|
|
534
|
+
` </reply>`,
|
|
535
|
+
" </review>"
|
|
536
|
+
].join("\n");
|
|
537
|
+
}),
|
|
538
|
+
"</reviews>",
|
|
539
|
+
""
|
|
540
|
+
].join("\n");
|
|
541
|
+
writeFileSync2(join4(WORKITEMS_DIR, "reviews.xml"), reviewsXml, "utf8");
|
|
542
|
+
const defectsXml = defectsToXml(data.defects ?? []);
|
|
543
|
+
writeFileSync2(join4(WORKITEMS_DIR, "defect.xml"), defectsXml, "utf8");
|
|
544
|
+
const testCasesXml = unknownArrayToXml(
|
|
545
|
+
"testcases",
|
|
546
|
+
"case",
|
|
547
|
+
data.testCases ?? []
|
|
548
|
+
);
|
|
549
|
+
writeFileSync2(join4(WORKITEMS_DIR, "testcase.xml"), testCasesXml, "utf8");
|
|
550
|
+
return WORKITEMS_DIR;
|
|
551
|
+
}
|
|
552
|
+
|
|
332
553
|
// src/commands/connect.ts
|
|
333
554
|
function runConnect(opts) {
|
|
334
555
|
void (async () => {
|
|
@@ -373,10 +594,7 @@ function runConnect(opts) {
|
|
|
373
594
|
}
|
|
374
595
|
const payload = msg.payload;
|
|
375
596
|
try {
|
|
376
|
-
|
|
377
|
-
cwd: payload.cwd,
|
|
378
|
-
encoding: "utf8"
|
|
379
|
-
});
|
|
597
|
+
await runPull(payload.requirementId, payload.cwd);
|
|
380
598
|
} catch (pullErr) {
|
|
381
599
|
console.error("[apm] apm pull \u5931\u8D25:", pullErr);
|
|
382
600
|
throw pullErr;
|
|
@@ -454,6 +672,12 @@ function runConnect(opts) {
|
|
|
454
672
|
});
|
|
455
673
|
console.log("[Done]");
|
|
456
674
|
session.writeToFile(payload.cwd, payload.requirementId, run.agentId);
|
|
675
|
+
try {
|
|
676
|
+
await runUploadArtifact(payload.requirementId, payload.cwd);
|
|
677
|
+
} catch (pullErr) {
|
|
678
|
+
console.error("[apm] apm upload-artifact \u5931\u8D25:", pullErr);
|
|
679
|
+
throw pullErr;
|
|
680
|
+
}
|
|
457
681
|
} catch {
|
|
458
682
|
console.error("[apm] \u65E0\u6CD5\u89E3\u6790 WebSocket \u6D88\u606F:", text);
|
|
459
683
|
}
|
|
@@ -469,17 +693,17 @@ function runConnect(opts) {
|
|
|
469
693
|
}
|
|
470
694
|
|
|
471
695
|
// src/commands/init.ts
|
|
472
|
-
import { join as
|
|
473
|
-
import { readFileSync as
|
|
696
|
+
import { join as join5 } from "path";
|
|
697
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
474
698
|
async function runInit(name) {
|
|
475
699
|
await ensureWorkspaceApmDirForInit();
|
|
476
700
|
await copyTemplateFiles(WORKSPACE_APM_DIR);
|
|
477
701
|
if (name) {
|
|
478
|
-
const apmConfigPath =
|
|
479
|
-
const config =
|
|
702
|
+
const apmConfigPath = join5(WORKSPACE_APM_DIR, "apm.config.json");
|
|
703
|
+
const config = readFileSync4(apmConfigPath, "utf8");
|
|
480
704
|
const configJson = JSON.parse(config);
|
|
481
705
|
configJson.name = name;
|
|
482
|
-
|
|
706
|
+
writeFileSync3(apmConfigPath, JSON.stringify(configJson, null, 2), "utf8");
|
|
483
707
|
}
|
|
484
708
|
}
|
|
485
709
|
|
|
@@ -666,134 +890,13 @@ async function runBranch(requirementId, options = {}) {
|
|
|
666
890
|
return branch;
|
|
667
891
|
}
|
|
668
892
|
|
|
669
|
-
// src/commands/pull.ts
|
|
670
|
-
import { writeFileSync as writeFileSync3 } from "fs";
|
|
671
|
-
import { join as join4 } from "path";
|
|
672
|
-
import { stringify as yamlStringify } from "yaml";
|
|
673
|
-
function valueToXmlContent(value) {
|
|
674
|
-
if (value === null || value === void 0) return "";
|
|
675
|
-
if (typeof value === "string") return xmlEscape(value);
|
|
676
|
-
if (typeof value === "number" || typeof value === "boolean") {
|
|
677
|
-
return xmlEscape(String(value));
|
|
678
|
-
}
|
|
679
|
-
return `<![CDATA[${JSON.stringify(value)}]]>`;
|
|
680
|
-
}
|
|
681
|
-
function recordToXmlLines(record, indent) {
|
|
682
|
-
const lines = [];
|
|
683
|
-
for (const [rawKey, val] of Object.entries(record)) {
|
|
684
|
-
const key = /^[a-zA-Z_][\w.-]*$/.test(rawKey) ? rawKey : "field";
|
|
685
|
-
lines.push(`${indent}<${key}>${valueToXmlContent(val)}</${key}>`);
|
|
686
|
-
}
|
|
687
|
-
return lines;
|
|
688
|
-
}
|
|
689
|
-
function unknownArrayToXml(rootName, itemName, items) {
|
|
690
|
-
const lines = [`<${rootName}>`];
|
|
691
|
-
items.forEach((item, index) => {
|
|
692
|
-
if (item !== null && typeof item === "object" && !Array.isArray(item)) {
|
|
693
|
-
lines.push(` <${itemName} index="${index}">`);
|
|
694
|
-
lines.push(...recordToXmlLines(item, " "));
|
|
695
|
-
lines.push(` </${itemName}>`);
|
|
696
|
-
} else {
|
|
697
|
-
lines.push(
|
|
698
|
-
` <${itemName} index="${index}">${valueToXmlContent(
|
|
699
|
-
item
|
|
700
|
-
)}</${itemName}>`
|
|
701
|
-
);
|
|
702
|
-
}
|
|
703
|
-
});
|
|
704
|
-
lines.push(`</${rootName}>`, "");
|
|
705
|
-
return lines.join("\n");
|
|
706
|
-
}
|
|
707
|
-
function tasksForStatusYaml(tasks) {
|
|
708
|
-
return tasks.map((t) => ({
|
|
709
|
-
id: t.id,
|
|
710
|
-
title: t.title,
|
|
711
|
-
status: t.status,
|
|
712
|
-
executor: t.executorType
|
|
713
|
-
}));
|
|
714
|
-
}
|
|
715
|
-
function escapeForCdata(text) {
|
|
716
|
-
return text.replace(/\]\]>/g, "]]]]><![CDATA[>");
|
|
717
|
-
}
|
|
718
|
-
function defectsToXml(defects) {
|
|
719
|
-
const sorted = [...defects].sort(
|
|
720
|
-
(a, b) => new Date(a.createdAt ?? 0).getTime() - new Date(b.createdAt ?? 0).getTime()
|
|
721
|
-
);
|
|
722
|
-
const lines = ["<defects>"];
|
|
723
|
-
for (const d of sorted) {
|
|
724
|
-
lines.push(` <defect id="${xmlEscape(d.id)}">`);
|
|
725
|
-
lines.push(` <status>${xmlEscape(d.status)}</status>`);
|
|
726
|
-
lines.push(` <current><![CDATA[${escapeForCdata(d.currentState ?? "")}]]></current>`);
|
|
727
|
-
lines.push(
|
|
728
|
-
` <expected><![CDATA[${escapeForCdata(d.expectedEffect ?? "")}]]></expected>`
|
|
729
|
-
);
|
|
730
|
-
lines.push(` </defect>`);
|
|
731
|
-
}
|
|
732
|
-
lines.push("</defects>", "");
|
|
733
|
-
return lines.join("\n");
|
|
734
|
-
}
|
|
735
|
-
async function runPull(requirementId) {
|
|
736
|
-
const cfg = await ensureLoggedConfig();
|
|
737
|
-
const api = createApmApiClient(cfg);
|
|
738
|
-
const data = await api.cliRequirements.pull({ requirementId });
|
|
739
|
-
const WORKITEMS_DIR = requirementWorkitemsDir(requirementId);
|
|
740
|
-
await ensureDirExists(WORKITEMS_DIR);
|
|
741
|
-
const req2 = data.requirement;
|
|
742
|
-
const statusYaml = yamlStringify(
|
|
743
|
-
{
|
|
744
|
-
id: req2.id,
|
|
745
|
-
status: req2.status,
|
|
746
|
-
title: req2.title,
|
|
747
|
-
env: req2.envName || "",
|
|
748
|
-
tasks: tasksForStatusYaml(data.tasks ?? [])
|
|
749
|
-
},
|
|
750
|
-
{ lineWidth: 0 }
|
|
751
|
-
);
|
|
752
|
-
writeFileSync3(
|
|
753
|
-
join4(WORKITEMS_DIR, "requirement-status.yaml"),
|
|
754
|
-
statusYaml.endsWith("\n") ? statusYaml : `${statusYaml}
|
|
755
|
-
`,
|
|
756
|
-
"utf8"
|
|
757
|
-
);
|
|
758
|
-
writeFileSync3(join4(WORKITEMS_DIR, "prd.md"), req2.content || "", "utf8");
|
|
759
|
-
const reviews = data.reviews ?? [];
|
|
760
|
-
const reviewsXml = [
|
|
761
|
-
"<reviews>",
|
|
762
|
-
...reviews.map((r) => {
|
|
763
|
-
return [
|
|
764
|
-
` <review id="${xmlEscape(r.id)}">`,
|
|
765
|
-
` <model>${xmlEscape(r.model ?? "")}</model>`,
|
|
766
|
-
` <content>`,
|
|
767
|
-
`${xmlEscape(r.content ?? "")}`,
|
|
768
|
-
` </content>`,
|
|
769
|
-
` <reply>`,
|
|
770
|
-
`${xmlEscape(r.reply ?? "")}`,
|
|
771
|
-
` </reply>`,
|
|
772
|
-
" </review>"
|
|
773
|
-
].join("\n");
|
|
774
|
-
}),
|
|
775
|
-
"</reviews>",
|
|
776
|
-
""
|
|
777
|
-
].join("\n");
|
|
778
|
-
writeFileSync3(join4(WORKITEMS_DIR, "reviews.xml"), reviewsXml, "utf8");
|
|
779
|
-
const defectsXml = defectsToXml(data.defects ?? []);
|
|
780
|
-
writeFileSync3(join4(WORKITEMS_DIR, "defect.xml"), defectsXml, "utf8");
|
|
781
|
-
const testCasesXml = unknownArrayToXml(
|
|
782
|
-
"testcases",
|
|
783
|
-
"case",
|
|
784
|
-
data.testCases ?? []
|
|
785
|
-
);
|
|
786
|
-
writeFileSync3(join4(WORKITEMS_DIR, "testcase.xml"), testCasesXml, "utf8");
|
|
787
|
-
return WORKITEMS_DIR;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
893
|
// src/commands/refine.ts
|
|
791
|
-
import { readFileSync as
|
|
792
|
-
import { join as
|
|
894
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
895
|
+
import { join as join6 } from "path";
|
|
793
896
|
async function runRefine(requirementId) {
|
|
794
897
|
const cfg = await ensureLoggedConfig();
|
|
795
|
-
const filePath =
|
|
796
|
-
const content =
|
|
898
|
+
const filePath = join6(WORKSPACE_APM_DIR, "workitems", requirementId, "prd.md");
|
|
899
|
+
const content = readFileSync5(filePath, "utf8");
|
|
797
900
|
const api = createApmApiClient(cfg);
|
|
798
901
|
const data = await api.cliRequirements.refine({ requirementId, content });
|
|
799
902
|
console.log(JSON.stringify(data, null, 2));
|
|
@@ -821,101 +924,6 @@ async function runUpdateStatus(requirementId, status) {
|
|
|
821
924
|
console.log(JSON.stringify(data, null, 2));
|
|
822
925
|
}
|
|
823
926
|
|
|
824
|
-
// src/commands/upload-artifact.ts
|
|
825
|
-
import { existsSync as existsSync2, readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
826
|
-
import { join as join6, relative, sep } from "path";
|
|
827
|
-
var EXCLUDED_RELATIVE_PATHS = /* @__PURE__ */ new Set([
|
|
828
|
-
"defect.xml",
|
|
829
|
-
"prd.md",
|
|
830
|
-
"requirement-status.yaml",
|
|
831
|
-
"reviews.xml",
|
|
832
|
-
"testcase.xml"
|
|
833
|
-
]);
|
|
834
|
-
function toPosixRelative(root, absoluteFile) {
|
|
835
|
-
return relative(root, absoluteFile).split(sep).join("/");
|
|
836
|
-
}
|
|
837
|
-
function artifactTagFromRelPath(relPosix) {
|
|
838
|
-
const parts = relPosix.split("/");
|
|
839
|
-
const fileName = parts[parts.length - 1] ?? relPosix;
|
|
840
|
-
if (parts.length > 1) {
|
|
841
|
-
return parts[0] ?? fileName;
|
|
842
|
-
}
|
|
843
|
-
const dot = fileName.lastIndexOf(".");
|
|
844
|
-
return dot > 0 ? fileName.slice(0, dot) : fileName;
|
|
845
|
-
}
|
|
846
|
-
function* walkMarkdownFiles(dir) {
|
|
847
|
-
const names = readdirSync2(dir);
|
|
848
|
-
for (const name of names) {
|
|
849
|
-
if (name.startsWith(".")) continue;
|
|
850
|
-
const full = join6(dir, name);
|
|
851
|
-
const st = statSync2(full);
|
|
852
|
-
if (st.isDirectory()) {
|
|
853
|
-
yield* walkMarkdownFiles(full);
|
|
854
|
-
continue;
|
|
855
|
-
}
|
|
856
|
-
if (!st.isFile()) continue;
|
|
857
|
-
if (!name.toLowerCase().endsWith(".md")) continue;
|
|
858
|
-
yield full;
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
async function deleteAllArtifactsForRequirement(api, requirementId) {
|
|
862
|
-
const pageSize = 100;
|
|
863
|
-
let page = 1;
|
|
864
|
-
const rows = [];
|
|
865
|
-
while (true) {
|
|
866
|
-
const batch = await api.requirementArtifact.list({
|
|
867
|
-
requirementId,
|
|
868
|
-
page,
|
|
869
|
-
pageSize
|
|
870
|
-
});
|
|
871
|
-
rows.push(...batch.items);
|
|
872
|
-
if (batch.total === 0 || rows.length >= batch.total) break;
|
|
873
|
-
page += 1;
|
|
874
|
-
}
|
|
875
|
-
for (const row of rows) {
|
|
876
|
-
await api.requirementArtifact.delete({ artifactId: row.id });
|
|
877
|
-
}
|
|
878
|
-
return rows.length;
|
|
879
|
-
}
|
|
880
|
-
async function runUploadArtifact(requirementId) {
|
|
881
|
-
const cfg = await ensureLoggedConfig();
|
|
882
|
-
const api = createApmApiClient(cfg);
|
|
883
|
-
const root = requirementWorkitemsDir(requirementId);
|
|
884
|
-
if (!existsSync2(root)) {
|
|
885
|
-
console.error(
|
|
886
|
-
`[apm] \u76EE\u5F55\u4E0D\u5B58\u5728: ${root}
|
|
887
|
-
\u8BF7\u5148\u6267\u884C: apm pull ${requirementId}`
|
|
888
|
-
);
|
|
889
|
-
process.exit(1);
|
|
890
|
-
}
|
|
891
|
-
const deleted = await deleteAllArtifactsForRequirement(api, requirementId);
|
|
892
|
-
console.log(`[apm] \u5DF2\u6E05\u7A7A\u9700\u6C42\u4EA7\u7269\u6587\u6863 ${deleted} \u6761`);
|
|
893
|
-
const paths = [...walkMarkdownFiles(root)];
|
|
894
|
-
let created = 0;
|
|
895
|
-
let skipped = 0;
|
|
896
|
-
for (const abs of paths) {
|
|
897
|
-
const relPosix = toPosixRelative(root, abs);
|
|
898
|
-
if (EXCLUDED_RELATIVE_PATHS.has(relPosix)) {
|
|
899
|
-
skipped += 1;
|
|
900
|
-
console.log(`[apm] \u8DF3\u8FC7\uFF08\u6392\u9664\u5217\u8868\uFF09: ${relPosix}`);
|
|
901
|
-
continue;
|
|
902
|
-
}
|
|
903
|
-
const content = readFileSync5(abs, "utf8");
|
|
904
|
-
const tag = artifactTagFromRelPath(relPosix);
|
|
905
|
-
await api.requirementArtifact.create({
|
|
906
|
-
requirementId,
|
|
907
|
-
tag,
|
|
908
|
-
fileName: relPosix,
|
|
909
|
-
content
|
|
910
|
-
});
|
|
911
|
-
created += 1;
|
|
912
|
-
console.log(`[apm] \u5DF2\u4E0A\u4F20\u4EA7\u7269: ${relPosix} (tag=${tag})`);
|
|
913
|
-
}
|
|
914
|
-
console.log(
|
|
915
|
-
`[apm] \u5B8C\u6210\uFF1A\u5220\u9664 ${deleted}\uFF0C\u65B0\u5EFA ${created}\uFF0C\u8DF3\u8FC7\uFF08\u6392\u9664\uFF09 ${skipped}\uFF0C\u5171\u626B\u63CF ${paths.length} \u4E2A Markdown \u6587\u4EF6`
|
|
916
|
-
);
|
|
917
|
-
}
|
|
918
|
-
|
|
919
927
|
// src/commands/deploy/backend.ts
|
|
920
928
|
import path5 from "node:path";
|
|
921
929
|
|
|
@@ -1332,7 +1340,7 @@ function assertDeployImageTag(tag) {
|
|
|
1332
1340
|
import { platform } from "node:os";
|
|
1333
1341
|
|
|
1334
1342
|
// src/commands/deploy/lib/backend-deploy/command-runner.ts
|
|
1335
|
-
import { execSync
|
|
1343
|
+
import { execSync } from "child_process";
|
|
1336
1344
|
|
|
1337
1345
|
// src/commands/deploy/lib/backend-deploy/logger.ts
|
|
1338
1346
|
var Logger = class {
|
|
@@ -1358,7 +1366,7 @@ var CommandRunner = class {
|
|
|
1358
1366
|
static exec(command, cwd) {
|
|
1359
1367
|
try {
|
|
1360
1368
|
Logger.info(`\u6267\u884C\u547D\u4EE4: ${command}`);
|
|
1361
|
-
const result =
|
|
1369
|
+
const result = execSync(command, {
|
|
1362
1370
|
cwd,
|
|
1363
1371
|
encoding: "utf8",
|
|
1364
1372
|
stdio: "pipe"
|
|
@@ -1376,7 +1384,7 @@ var CommandRunner = class {
|
|
|
1376
1384
|
static execWithOutput(command, cwd) {
|
|
1377
1385
|
try {
|
|
1378
1386
|
Logger.info(`\u6267\u884C\u547D\u4EE4: ${command}`);
|
|
1379
|
-
|
|
1387
|
+
execSync(command, {
|
|
1380
1388
|
cwd,
|
|
1381
1389
|
stdio: "inherit"
|
|
1382
1390
|
});
|
package/package.json
CHANGED
|
@@ -59,8 +59,7 @@ instruction 子文件随该目录类推(如 `.apm/skills/apm-propose/propose-i
|
|
|
59
59
|
## 步骤 2:读取缺陷清单(defect.xml)
|
|
60
60
|
|
|
61
61
|
1. 父 Agent 使用 **Read** 工具读取:`.apm/workitems/<requirementId>/defect.xml`(缺陷列表,格式以平台同步为准)。
|
|
62
|
-
2.
|
|
63
|
-
3. **分流(非失败)**:
|
|
62
|
+
2. **分流(非失败)**:
|
|
64
63
|
- **无文件、仍读不到、或文件无有效内容**(空文件、仅空白、无实质缺陷条目等;具体结构以同步格式为准):**不视为步骤失败**。表格步骤 2 填 **跳过(无 defect 内容 → 新功能)**,**直接进入步骤 3**。
|
|
65
64
|
- **文件存在且有有效内容**:视为本轮为 **修复 Bug 流程**(与步骤 4 同属 Quick 形态,对照 **defect.xml + 必要时 prd.md**,目标是消除所列缺陷):
|
|
66
65
|
- 父 Agent 已通过 **Read** 掌握 `defect.xml`;若启动子 Agent,在委派提示中写明 **`requirementId`**、工作项路径、`defect.xml` 路径,以及「逐项对照缺陷列表修复,改动范围最小化」。
|
|
@@ -78,7 +77,7 @@ instruction 子文件随该目录类推(如 `.apm/skills/apm-propose/propose-i
|
|
|
78
77
|
**条件**:仅当步骤 2 **未**以「`defect.xml` **有内容**且 Quick 修 Bug **成功** → 短路至步骤 6」结束时执行(含:`defect.xml` 缺失、拉取后仍无、或**无有效内容**而走新功能流程)。
|
|
79
78
|
|
|
80
79
|
1. **Read** 全文:`.apm/workitems/<requirementId>/prd.md`。
|
|
81
|
-
2.
|
|
80
|
+
2. 若失败则**停止后续实现**,仅在表格中标记失败原因。
|
|
82
81
|
3. **不**为评估向用户发起追问;信息不足时倾向 **Spec**(保守)。
|
|
83
82
|
|
|
84
83
|
### Quick(较小)与 Spec(较大)判定参考
|
|
@@ -137,23 +136,7 @@ instruction 子文件随该目录类推(如 `.apm/skills/apm-propose/propose-i
|
|
|
137
136
|
|
|
138
137
|
将命令结果、最终分支名、是否已 push、工作区是否干净写入表格。
|
|
139
138
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
## 步骤 7:同步产物
|
|
143
|
-
|
|
144
|
-
在**仓库/工作区根目录**(与步骤 1、6 一致)执行:
|
|
145
|
-
|
|
146
|
-
```bash
|
|
147
|
-
apm upload-artifact <requirementId>
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
1. 该命令会**先清空**平台上该需求的产物文档,再将 `.apm/workitems/<requirementId>/` 下符合条件的 Markdown 重新上传(排除 `prd.md`、状态 YAML、reviews/defect/testcase 等 pull 系统文件;详见 CLI 行为)。
|
|
151
|
-
2. 若本地未放置需同步的 `.md` 产物,仍会清空平台侧产物;若有文档仅在仓库其它路径,须先复制或生成到工作项目录再执行。
|
|
152
|
-
3. 将命令是否成功、上传条数或错误摘要写入表格。
|
|
153
|
-
|
|
154
|
-
---
|
|
155
|
-
|
|
156
|
-
## 步骤 8:对用户回复(表格)
|
|
139
|
+
## 步骤 7:对用户回复(表格)
|
|
157
140
|
|
|
158
141
|
对用户回复 **必须包含一张 Markdown 表格**,汇总 **步骤 1~7**(步骤 8 为呈现表格本身,可不单独成行)。表头建议:
|
|
159
142
|
|
|
@@ -165,7 +148,6 @@ apm upload-artifact <requirementId>
|
|
|
165
148
|
| 4 | Quick 开发(子 Agent) | 成功 / 失败 / **跳过** |
|
|
166
149
|
| 5 | Spec:apm-propose → apm-apply-change(子 Agent) | 成功 / 失败 / **跳过**;可注明子步骤 |
|
|
167
150
|
| 6 | commit & push;工作区干净 | 成功 / 失败(原因) |
|
|
168
|
-
| 7 | `apm upload-artifact <requirementId>` | 成功 / 失败(原因) |
|
|
169
151
|
|
|
170
152
|
**可选**:在表格外增加**简短**一句话摘要(例如当前分支名、阻塞点);若用户此前约定「仅表格」,则可仅输出表格。
|
|
171
153
|
|
|
@@ -173,7 +155,7 @@ apm upload-artifact <requirementId>
|
|
|
173
155
|
|
|
174
156
|
## Guardrails
|
|
175
157
|
|
|
176
|
-
- **失败即终止**:在用户提供的 **`requirementId`** 等参数合法、命令与路径按本技能书写的前提下,任一步骤(含 `apm branch
|
|
158
|
+
- **失败即终止**:在用户提供的 **`requirementId`** 等参数合法、命令与路径按本技能书写的前提下,任一步骤(含 `apm branch`、读文件、子 Agent、`git`)**一旦失败**:**停止后续所有步骤**,仅在表格中记录失败步骤与原因;**不要**为登录、依赖、网络、命令结果等做**多次**或「轮番」重试。
|
|
177
159
|
- **例外(Agent 自身失误)**:若失败明显由执行 Agent **用错工作目录、读错/漏写路径** 等导致,**允许**纠正 `cwd` 或路径后**仅对该失败步骤再执行一次**;纠正后仍失败则**立即终止**,不再扩展尝试。
|
|
178
|
-
- **不要**在无 `prd.md
|
|
160
|
+
- **不要**在无 `prd.md`的情况下编造需求实现。
|
|
179
161
|
- **子 Agent** 提示中须带 **`requirementId`** 与仓库根路径意识,避免改错工作树。
|