ai-project-manage-cli 3.0.5 → 3.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 +155 -36
- package/package.json +1 -1
- package/template/skills/apm-deploy/SKILL.md +132 -0
- package/template/skills/apm-dev/SKILL.md +21 -5
- package/template/skills/apm-refine/SKILL.md +21 -16
- package/template/skills/apm-refine/apm-refine-template.md +10 -18
- package/template/skills/apm-review/SKILL.md +29 -12
- package/template/skills/apm-review/output-template.md +43 -0
- package/template/skills/apm-dev copy/SKILL.md +0 -142
- package/template/skills/apm-review/apm-review-reference.md +0 -89
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFileSync as
|
|
5
|
-
import { dirname as dirname2, join as
|
|
4
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
5
|
+
import { dirname as dirname2, join as join8 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
|
|
@@ -115,6 +115,24 @@ var requestConfig = {
|
|
|
115
115
|
method: "POST",
|
|
116
116
|
path: "/cli/requirements/update-dev-status"
|
|
117
117
|
})
|
|
118
|
+
},
|
|
119
|
+
requirementArtifact: {
|
|
120
|
+
list: defineEndpoint({
|
|
121
|
+
method: "GET",
|
|
122
|
+
path: "/requirement-artifacts/list"
|
|
123
|
+
}),
|
|
124
|
+
create: defineEndpoint({
|
|
125
|
+
method: "POST",
|
|
126
|
+
path: "/requirement-artifacts/create"
|
|
127
|
+
}),
|
|
128
|
+
update: defineEndpoint({
|
|
129
|
+
method: "POST",
|
|
130
|
+
path: "/requirement-artifacts/update"
|
|
131
|
+
}),
|
|
132
|
+
delete: defineEndpoint({
|
|
133
|
+
method: "POST",
|
|
134
|
+
path: "/requirement-artifacts/delete"
|
|
135
|
+
})
|
|
118
136
|
}
|
|
119
137
|
};
|
|
120
138
|
|
|
@@ -136,6 +154,9 @@ import { fileURLToPath } from "url";
|
|
|
136
154
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
137
155
|
var CLI_TEMPLATE_DIR = resolve(__dirname, "../template");
|
|
138
156
|
var WORKSPACE_APM_DIR = resolve(process.cwd(), ".apm");
|
|
157
|
+
function requirementWorkitemsDir(requirementId) {
|
|
158
|
+
return join2(WORKSPACE_APM_DIR, "workitems", requirementId);
|
|
159
|
+
}
|
|
139
160
|
async function ensureLoggedConfig() {
|
|
140
161
|
const cfg = await ensureApmConfig();
|
|
141
162
|
if (!cfg.token) {
|
|
@@ -499,7 +520,7 @@ async function runPull(requirementId) {
|
|
|
499
520
|
const cfg = await ensureLoggedConfig();
|
|
500
521
|
const api = createApmApiClient(cfg);
|
|
501
522
|
const data = await api.cliRequirements.pull({ requirementId });
|
|
502
|
-
const WORKITEMS_DIR =
|
|
523
|
+
const WORKITEMS_DIR = requirementWorkitemsDir(requirementId);
|
|
503
524
|
await ensureDirExists(WORKITEMS_DIR);
|
|
504
525
|
const req2 = data.requirement;
|
|
505
526
|
const statusYaml = yamlStringify(
|
|
@@ -584,23 +605,118 @@ async function runUpdateStatus(requirementId, status) {
|
|
|
584
605
|
console.log(JSON.stringify(data, null, 2));
|
|
585
606
|
}
|
|
586
607
|
|
|
608
|
+
// src/commands/upload-artifact.ts
|
|
609
|
+
import { existsSync as existsSync2, readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
610
|
+
import { join as join6, relative, sep } from "path";
|
|
611
|
+
var EXCLUDED_RELATIVE_PATHS = /* @__PURE__ */ new Set([
|
|
612
|
+
"defect.xml",
|
|
613
|
+
"prd.md",
|
|
614
|
+
"requirement-status.yaml",
|
|
615
|
+
"reviews.xml",
|
|
616
|
+
"testcase.xml"
|
|
617
|
+
]);
|
|
618
|
+
function toPosixRelative(root, absoluteFile) {
|
|
619
|
+
return relative(root, absoluteFile).split(sep).join("/");
|
|
620
|
+
}
|
|
621
|
+
function artifactTagFromRelPath(relPosix) {
|
|
622
|
+
const parts = relPosix.split("/");
|
|
623
|
+
const fileName = parts[parts.length - 1] ?? relPosix;
|
|
624
|
+
if (parts.length > 1) {
|
|
625
|
+
return parts[0] ?? fileName;
|
|
626
|
+
}
|
|
627
|
+
const dot = fileName.lastIndexOf(".");
|
|
628
|
+
return dot > 0 ? fileName.slice(0, dot) : fileName;
|
|
629
|
+
}
|
|
630
|
+
function* walkMarkdownFiles(dir) {
|
|
631
|
+
const names = readdirSync2(dir);
|
|
632
|
+
for (const name of names) {
|
|
633
|
+
if (name.startsWith(".")) continue;
|
|
634
|
+
const full = join6(dir, name);
|
|
635
|
+
const st = statSync2(full);
|
|
636
|
+
if (st.isDirectory()) {
|
|
637
|
+
yield* walkMarkdownFiles(full);
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
if (!st.isFile()) continue;
|
|
641
|
+
if (!name.toLowerCase().endsWith(".md")) continue;
|
|
642
|
+
yield full;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
async function deleteAllArtifactsForRequirement(api, requirementId) {
|
|
646
|
+
const pageSize = 100;
|
|
647
|
+
let page = 1;
|
|
648
|
+
const rows = [];
|
|
649
|
+
while (true) {
|
|
650
|
+
const batch = await api.requirementArtifact.list({
|
|
651
|
+
requirementId,
|
|
652
|
+
page,
|
|
653
|
+
pageSize
|
|
654
|
+
});
|
|
655
|
+
rows.push(...batch.items);
|
|
656
|
+
if (batch.total === 0 || rows.length >= batch.total) break;
|
|
657
|
+
page += 1;
|
|
658
|
+
}
|
|
659
|
+
for (const row of rows) {
|
|
660
|
+
await api.requirementArtifact.delete({ artifactId: row.id });
|
|
661
|
+
}
|
|
662
|
+
return rows.length;
|
|
663
|
+
}
|
|
664
|
+
async function runUploadArtifact(requirementId) {
|
|
665
|
+
const cfg = await ensureLoggedConfig();
|
|
666
|
+
const api = createApmApiClient(cfg);
|
|
667
|
+
const root = requirementWorkitemsDir(requirementId);
|
|
668
|
+
if (!existsSync2(root)) {
|
|
669
|
+
console.error(
|
|
670
|
+
`[apm] \u76EE\u5F55\u4E0D\u5B58\u5728: ${root}
|
|
671
|
+
\u8BF7\u5148\u6267\u884C: apm pull ${requirementId}`
|
|
672
|
+
);
|
|
673
|
+
process.exit(1);
|
|
674
|
+
}
|
|
675
|
+
const deleted = await deleteAllArtifactsForRequirement(api, requirementId);
|
|
676
|
+
console.log(`[apm] \u5DF2\u6E05\u7A7A\u9700\u6C42\u4EA7\u7269\u6587\u6863 ${deleted} \u6761`);
|
|
677
|
+
const paths = [...walkMarkdownFiles(root)];
|
|
678
|
+
let created = 0;
|
|
679
|
+
let skipped = 0;
|
|
680
|
+
for (const abs of paths) {
|
|
681
|
+
const relPosix = toPosixRelative(root, abs);
|
|
682
|
+
if (EXCLUDED_RELATIVE_PATHS.has(relPosix)) {
|
|
683
|
+
skipped += 1;
|
|
684
|
+
console.log(`[apm] \u8DF3\u8FC7\uFF08\u6392\u9664\u5217\u8868\uFF09: ${relPosix}`);
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
const content = readFileSync5(abs, "utf8");
|
|
688
|
+
const tag = artifactTagFromRelPath(relPosix);
|
|
689
|
+
await api.requirementArtifact.create({
|
|
690
|
+
requirementId,
|
|
691
|
+
tag,
|
|
692
|
+
fileName: relPosix,
|
|
693
|
+
content
|
|
694
|
+
});
|
|
695
|
+
created += 1;
|
|
696
|
+
console.log(`[apm] \u5DF2\u4E0A\u4F20\u4EA7\u7269: ${relPosix} (tag=${tag})`);
|
|
697
|
+
}
|
|
698
|
+
console.log(
|
|
699
|
+
`[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`
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
|
|
587
703
|
// src/commands/deploy/backend.ts
|
|
588
704
|
import path5 from "node:path";
|
|
589
705
|
|
|
590
706
|
// src/commands/deploy/lib/apm-config.ts
|
|
591
|
-
import { existsSync as
|
|
707
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6 } from "node:fs";
|
|
592
708
|
import { resolve as resolve3 } from "node:path";
|
|
593
709
|
function loadApmConfig(options) {
|
|
594
710
|
const p = resolve3(
|
|
595
711
|
process.cwd(),
|
|
596
712
|
options?.configPath ?? resolve3(WORKSPACE_APM_DIR, "apm.config.json")
|
|
597
713
|
);
|
|
598
|
-
if (!
|
|
714
|
+
if (!existsSync3(p)) {
|
|
599
715
|
console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
|
|
600
716
|
process.exit(1);
|
|
601
717
|
}
|
|
602
718
|
try {
|
|
603
|
-
const raw =
|
|
719
|
+
const raw = readFileSync6(p, "utf8");
|
|
604
720
|
return JSON.parse(raw);
|
|
605
721
|
} catch (e) {
|
|
606
722
|
console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
|
|
@@ -634,18 +750,16 @@ function resolveBackendDeployFromApmConfig(cfg) {
|
|
|
634
750
|
process.exit(1);
|
|
635
751
|
}
|
|
636
752
|
const remoteProtocol = protoRaw;
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
)
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
);
|
|
648
|
-
process.exit(1);
|
|
753
|
+
let mappings = [];
|
|
754
|
+
const rawPorts = b.containerPortsMappings;
|
|
755
|
+
if (rawPorts !== void 0 && rawPorts !== null) {
|
|
756
|
+
if (!Array.isArray(rawPorts)) {
|
|
757
|
+
console.error(
|
|
758
|
+
"apm.config.json \u4E2D backendDeploy.containerPortsMappings \u987B\u4E3A\u5B57\u7B26\u4E32\u6570\u7EC4\uFF08\u53EF\u4E3A\u7A7A\uFF0C\u7701\u7565\u5219\u4E0D\u52A0\u7AEF\u53E3\u6620\u5C04\uFF09"
|
|
759
|
+
);
|
|
760
|
+
process.exit(1);
|
|
761
|
+
}
|
|
762
|
+
mappings = rawPorts.map((x) => String(x).trim()).filter(Boolean);
|
|
649
763
|
}
|
|
650
764
|
return {
|
|
651
765
|
name: req(b.name, "name").trim(),
|
|
@@ -691,7 +805,7 @@ import path4 from "node:path";
|
|
|
691
805
|
import Docker from "dockerode";
|
|
692
806
|
|
|
693
807
|
// src/commands/deploy/lib/backend-deploy/dockerode-client/connection-options.ts
|
|
694
|
-
import { existsSync as
|
|
808
|
+
import { existsSync as existsSync4, readFileSync as readFileSync7 } from "node:fs";
|
|
695
809
|
import path from "node:path";
|
|
696
810
|
function asOptionalTlsBuffer(value) {
|
|
697
811
|
if (typeof value !== "string") {
|
|
@@ -703,8 +817,8 @@ function asOptionalTlsBuffer(value) {
|
|
|
703
817
|
if (normalized === "") {
|
|
704
818
|
return void 0;
|
|
705
819
|
}
|
|
706
|
-
if (
|
|
707
|
-
return
|
|
820
|
+
if (existsSync4(normalized)) {
|
|
821
|
+
return readFileSync7(normalized);
|
|
708
822
|
}
|
|
709
823
|
const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
|
|
710
824
|
if (looksLikePath) {
|
|
@@ -914,17 +1028,17 @@ var DockerodeClient = class {
|
|
|
914
1028
|
var createDockerodeClient = (config) => new DockerodeClient(config);
|
|
915
1029
|
|
|
916
1030
|
// src/commands/deploy/lib/backend-deploy/dockerode-client/env.ts
|
|
917
|
-
import { existsSync as
|
|
1031
|
+
import { existsSync as existsSync5, readFileSync as readFileSync8, statSync as statSync3 } from "node:fs";
|
|
918
1032
|
import path2 from "node:path";
|
|
919
1033
|
function loadEnvFromFile(envFilePath) {
|
|
920
1034
|
if (!envFilePath) {
|
|
921
1035
|
return {};
|
|
922
1036
|
}
|
|
923
1037
|
const targetPath = path2.resolve(envFilePath);
|
|
924
|
-
if (!
|
|
1038
|
+
if (!existsSync5(targetPath) || !statSync3(targetPath).isFile()) {
|
|
925
1039
|
return {};
|
|
926
1040
|
}
|
|
927
|
-
const raw =
|
|
1041
|
+
const raw = readFileSync8(targetPath, "utf-8");
|
|
928
1042
|
const result = {};
|
|
929
1043
|
for (const line of raw.split(/\r?\n/)) {
|
|
930
1044
|
const normalized = line.trim();
|
|
@@ -1095,12 +1209,12 @@ function dockerPushImage(params, cwd) {
|
|
|
1095
1209
|
}
|
|
1096
1210
|
|
|
1097
1211
|
// src/commands/deploy/lib/backend-deploy/resolve-dockerfile.ts
|
|
1098
|
-
import { existsSync as
|
|
1212
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
1099
1213
|
import path3 from "node:path";
|
|
1100
1214
|
function resolveDockerBuildPaths(cwd) {
|
|
1101
1215
|
const dockerfilePath = path3.join(cwd, "Dockerfile");
|
|
1102
1216
|
Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
|
|
1103
|
-
if (!
|
|
1217
|
+
if (!existsSync6(dockerfilePath)) {
|
|
1104
1218
|
throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
|
|
1105
1219
|
}
|
|
1106
1220
|
Logger.info("\u2713 Dockerfile \u5B58\u5728");
|
|
@@ -1229,16 +1343,16 @@ import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
|
|
|
1229
1343
|
import path7 from "node:path";
|
|
1230
1344
|
|
|
1231
1345
|
// src/commands/deploy/lib/load-apm-dotenv.ts
|
|
1232
|
-
import { existsSync as
|
|
1233
|
-
import { join as
|
|
1346
|
+
import { existsSync as existsSync7, readFileSync as readFileSync9 } from "node:fs";
|
|
1347
|
+
import { join as join7 } from "node:path";
|
|
1234
1348
|
function loadApmDotEnvIfPresent() {
|
|
1235
|
-
const p =
|
|
1236
|
-
if (!
|
|
1349
|
+
const p = join7(WORKSPACE_APM_DIR, ".env");
|
|
1350
|
+
if (!existsSync7(p)) {
|
|
1237
1351
|
return;
|
|
1238
1352
|
}
|
|
1239
1353
|
let text;
|
|
1240
1354
|
try {
|
|
1241
|
-
text =
|
|
1355
|
+
text = readFileSync9(p, "utf8");
|
|
1242
1356
|
} catch {
|
|
1243
1357
|
return;
|
|
1244
1358
|
}
|
|
@@ -1263,14 +1377,14 @@ function loadApmDotEnvIfPresent() {
|
|
|
1263
1377
|
}
|
|
1264
1378
|
|
|
1265
1379
|
// src/commands/deploy/lib/minio.ts
|
|
1266
|
-
import { statSync as
|
|
1380
|
+
import { statSync as statSync4 } from "node:fs";
|
|
1267
1381
|
import { readdir } from "node:fs/promises";
|
|
1268
1382
|
import path6 from "node:path";
|
|
1269
1383
|
import * as Minio from "minio";
|
|
1270
1384
|
var DEFAULT_MAX_FILE_SIZE_MB = 50;
|
|
1271
1385
|
async function isDirectoryPath(dir) {
|
|
1272
1386
|
try {
|
|
1273
|
-
const st =
|
|
1387
|
+
const st = statSync4(dir);
|
|
1274
1388
|
return st.isDirectory();
|
|
1275
1389
|
} catch {
|
|
1276
1390
|
return false;
|
|
@@ -1300,7 +1414,7 @@ async function collectFiles(root) {
|
|
|
1300
1414
|
if (e.isDirectory()) {
|
|
1301
1415
|
await walk(abs, rel);
|
|
1302
1416
|
} else if (e.isFile()) {
|
|
1303
|
-
const st =
|
|
1417
|
+
const st = statSync4(abs);
|
|
1304
1418
|
out.push({
|
|
1305
1419
|
absPath: abs,
|
|
1306
1420
|
relativePath: rel.replace(/\\/g, "/"),
|
|
@@ -1597,8 +1711,8 @@ function registerDeployCommands(program) {
|
|
|
1597
1711
|
function readCliVersion() {
|
|
1598
1712
|
try {
|
|
1599
1713
|
const dir = dirname2(fileURLToPath2(import.meta.url));
|
|
1600
|
-
const pkgPath =
|
|
1601
|
-
const pkg = JSON.parse(
|
|
1714
|
+
const pkgPath = join8(dir, "..", "package.json");
|
|
1715
|
+
const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
1602
1716
|
return pkg.version ?? "0.0.0";
|
|
1603
1717
|
} catch {
|
|
1604
1718
|
return "0.0.0";
|
|
@@ -1626,6 +1740,11 @@ function buildProgram() {
|
|
|
1626
1740
|
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) => {
|
|
1627
1741
|
await runPull(requirementId);
|
|
1628
1742
|
});
|
|
1743
|
+
program.command("upload-artifact").description(
|
|
1744
|
+
"\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"
|
|
1745
|
+
).argument("<requirementId>", "\u9700\u6C42 ID").action(async (requirementId) => {
|
|
1746
|
+
await runUploadArtifact(requirementId);
|
|
1747
|
+
});
|
|
1629
1748
|
program.command("branch").description(
|
|
1630
1749
|
"\u5207\u6362\u6216\u521B\u5EFA\u9700\u6C42\u5206\u652F feat/req-<ID>\uFF1A\u8FDC\u7AEF\u5B58\u5728\u5219\u62C9\u53D6\u6700\u65B0\uFF1B\u8FDC\u7AEF\u5C1A\u65E0\u8BE5\u5206\u652F\u4E14\u672C\u5730\u4E5F\u65E0\u540C\u540D\u5206\u652F\u65F6\uFF0C\u9700\u5DF2 login\uFF0C\u5E76\u7531\u5E73\u53F0\u6839\u636E\u5F53\u524D\u76EE\u5F55\u8DEF\u5F84\u89E3\u6790\u4ED3\u5E93\u57FA\u7EBF\u5206\u652F\u540E\u4ECE origin \u68C0\u51FA\u518D\u63A8\u9001\uFF1B\u6709\u672C\u5730\u672A\u63D0\u4EA4\u6539\u52A8\u65F6\u5728\u975E\u76EE\u6807\u5206\u652F\u5148 stash\uFF08\u4E0D\u81EA\u52A8\u6062\u590D\uFF09\uFF0C\u5728\u76EE\u6807\u5206\u652F\u5219\u5148 commit"
|
|
1631
1750
|
).argument("<requirementId>", "\u9700\u6C42 ID").option(
|
package/package.json
CHANGED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: apm-deploy
|
|
3
|
+
description: 路径相对仓库根 `<repoRoot>`;`.apm` 常被 gitignore,须直接 Read 或用 Shell 验磁盘,勿仅靠索引/搜索。用户仅提供需求 ID,读 deploy README 与 workitems(常用 requirement-status.yaml 的 env)后按 README 执行并汇总;不足则终止;@ 本技能、自动部署或「apm-deploy」时使用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# APM 自动部署(按 `.apm/deploy/README.md`)
|
|
7
|
+
|
|
8
|
+
- **部署怎么做**:**唯一**以仓库 **`.apm/deploy/README.md`** 为准(用户/团队维护)。文档有问题时**不得**猜着部署或绕过文档擅自执行生产级危险操作。
|
|
9
|
+
- **参数从哪来**:用户**只需提供需求 ID**(`requirementId`);其余上下文从 **`.apm/workitems/<requirementId>/`** 内按需 **Read**,**不向用户追问**环境名、分支等部署参数。
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 路径基准:项目(仓库)根目录 **`<repoRoot>`**
|
|
14
|
+
|
|
15
|
+
本技能中出现的 **`.apm/...` 路径一律相对于 `<repoRoot>`**,**不得**相对于当前打开的文件、编辑器所在子目录、终端当前工作目录或其它隐含目录拼接。**找错文件视为执行错误**:若首次 Read 失败且路径明显可能在子目录下被误解析,**允许**仅因「路径基准错误」纠正一次——改为从 `<repoRoot>` 起算后重试该 Read。
|
|
16
|
+
|
|
17
|
+
**如何确定 `<repoRoot>`**
|
|
18
|
+
|
|
19
|
+
1. **优先**:当前 Cursor **工作区根**(Workspace Folder)即为本 Rush/Git 仓库根(通常含 **`rush.json`**、**`.apm/`**、**`apps/`** 等同级结构的那一层)。
|
|
20
|
+
2. **若工作区误开在子目录**:向上查找同时存在 **`rush.json`**(或根 **`package.json`** + 单仓约定)且存在 **`.apm/`** 的目录,该目录即为 `<repoRoot>`;**不得**把仅有嵌套 `.apm` 副本的深层目录当作根(除非 README 明确声明)。
|
|
21
|
+
3. **工具调用**:**Read / 编辑 / 执行 Shell** 时,凡本技能写明的路径均写成 **从 `<repoRoot>` 起的相对路径**(例如 `.apm/deploy/README.md`、`.apm/workitems/<requirementId>/requirement-status.yaml`)。需要绝对路径时,用 `<repoRoot>` 拼接,**不要**使用 `./apps/.../.apm/...` 这类错误前缀。
|
|
22
|
+
4. **Shell**:默认 **`cwd` = `<repoRoot>`**;仅当 README **明确**要求 `cd` 到子目录时再切换,执行完若需继续本技能后续步骤应回到 `<repoRoot>` 或严格按 README 指定目录执行下一步。
|
|
23
|
+
|
|
24
|
+
### `.gitignore`:被忽略的目录仍可能在磁盘上
|
|
25
|
+
|
|
26
|
+
**`.apm/`**(及其中 `deploy`、`workitems` 等)在多数仓库里会被 **`.gitignore`**,因此:
|
|
27
|
+
|
|
28
|
+
- **语义搜索、代码索引、默认排除 gitignore 的 ripgrep/grep** 往往**扫不到**这些路径 → **绝不能**因为「搜不到」就认定文件不存在或跳过本技能。
|
|
29
|
+
- **必须以磁盘为准**:对 `.apm/deploy/README.md`、`.apm/workitems/<requirementId>/...` 使用 **Read 工具并给出从 `<repoRoot>` 起的完整相对路径**(不要先依赖「在全仓搜索 `.apm`」来发现路径)。
|
|
30
|
+
- 若 **Read** 报错且不确定是真不存在还是被工具策略拦截:在仓库根执行 Shell(**`cwd`** = `<repoRoot>`),例如 **`test -e .apm/deploy/README.md`**、**`ls .apm/workitems/<requirementId>`**(读磁盘,**不依赖** `git ls-files`)。
|
|
31
|
+
- **`git status` 不列出未跟踪/被 ignore 的文件是正常的**,不代表本地没有该文件。
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 输入与前置
|
|
36
|
+
|
|
37
|
+
| 字段 | 规则 |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| **`requirementId`** | **必填**。与 **`<repoRoot>/.apm/workitems/<requirementId>/`** 目录名一致;**缺则索要,不猜测**。 |
|
|
40
|
+
| **`<repoRoot>`** | 见上节「路径基准」;所有 `.apm` 路径均锚定于此。 |
|
|
41
|
+
| **工作项上下文** | 路径 **`<repoRoot>/.apm/workitems/<requirementId>/`**(工具里写相对路径则为 `.apm/workitems/<requirementId>/`)。README 需要何种信息(目标环境、命名空间、标签等),**优先在该目录下找对应文件**;**一般**阅读 **`requirement-status.yaml`** 中的 **`env`** 字段即可与 README 对齐(具体键名以仓库内实际 YAML 为准)。若 README 指向其它文件名(如任务状态、发布说明),在同一目录 **Read** 即可。 |
|
|
42
|
+
| **参数仍不足** | 若 README 要求某信息且在工作项目录与 README 附属文档中**仍无法取得**:**不得**向用户口头凑参数;**终止**并列出「README 要什么 / 已查了哪些路径 / 缺什么」。 |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 流程总览
|
|
47
|
+
|
|
48
|
+
| 序号 | 步骤 | 说明 |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| 1 | **阅读部署文档** | **Read** 全文:`.apm/deploy/README.md`(及 README 明确要求阅读的附属文档)。 |
|
|
51
|
+
| 2 | **文档验收(门禁)** | 判定 README 是否足以执行;不足则**终止**(见「文档验收」)。 |
|
|
52
|
+
| 3 | **加载工作项上下文** | 门禁通过后:按 README 所需,**Read** `.apm/workitems/<requirementId>/` 下文件(**优先** `requirement-status.yaml` → **`env`**);仍缺参数则**终止**(见「输入与前置」)。 |
|
|
53
|
+
| 4 | **按文档执行** | **严格**按 README 的步骤、顺序与命令执行,并用上一步解析出的参数(如替换占位符、导出环境变量);不添加 README 未要求的步骤。 |
|
|
54
|
+
| 5 | **对用户回复** | 用** Markdown 表格**汇总各步与命令结果(见文末模板);简述阻塞或跳过原因。 |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 步骤 1:阅读 `.apm/deploy/README.md`
|
|
59
|
+
|
|
60
|
+
1. 在 **`<repoRoot>`** 下 **Read**(相对路径):`.apm/deploy/README.md`。路径**必须**以仓库根为前缀,**禁止**从子项目目录推导同名路径。**禁止**仅用 codebase 搜索来找该文件(易被 gitignore 漏掉);**直接 Read** 上述路径。
|
|
61
|
+
2. **文件不存在或不可读**:先用上节「`.gitignore`」方式区分是真不存在还是路径基准错误;确认路径正确且磁盘上仍无文件时**立即停止**本流程。在对话中明确写出:路径、错误(如 ENOENT、权限),**不要**尝试默认部署命令或猜测流程。
|
|
62
|
+
3. **存在则读全文**(含其指向的同级/子路径文档时:若 README 写明「须同时阅读某文件」,则 **Read** 该文件;若链断裂或文件缺失,按「文档验收 → 文档缺陷」处理)。
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 步骤 2:文档验收(门禁,失败即终止)
|
|
67
|
+
|
|
68
|
+
在阅读完成后、加载工作项上下文与执行任何部署命令前,必须自检下列项。**任一条不满足**:**不得执行部署**;在对话中**逐条列出问题**(引用 README 片段或章节标题即可),然后**终止本技能流程**。
|
|
69
|
+
|
|
70
|
+
| 验收项 | 不满足时的表现(示例) |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| **可执行性** | 缺少前置条件(账号、CLI、密钥、目标环境)、缺少具体命令或顺序混乱到无法唯一确定下一步。 |
|
|
73
|
+
| **一致性** | 前后矛盾(例如同一环境两套冲突命令)、版本/工具要求互相打架。 |
|
|
74
|
+
| **完整性** | 引用不存在的脚本/配置文件、Broken link、关键占位符未替换说明。 |
|
|
75
|
+
| **清晰性** | 读完仍**不知道**该如何在本仓库完成一次部署(步骤含糊到无法落地)。 |
|
|
76
|
+
|
|
77
|
+
**特别约定(用户维护文档)**
|
|
78
|
+
|
|
79
|
+
- 文档若有错别字、过时链接、危险指令描述不清等:**直接报出问题**,**不**替用户「纠正文档后继续」。
|
|
80
|
+
- **阅读之后仍然不知道如何部署**:视为「清晰性」不满足,**终止**,说明「依据当前 README 无法确定可执行步骤」及缺失项。
|
|
81
|
+
- **不要**因文档差而自行检索互联网或套用其它项目的部署习惯来代替 README。
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 步骤 3:加载工作项上下文
|
|
86
|
+
|
|
87
|
+
仅在「文档验收」**全部通过**后执行。
|
|
88
|
+
|
|
89
|
+
1. 确认目录 **`<repoRoot>/.apm/workitems/<requirementId>/`**(工具相对路径:`.apm/workitems/<requirementId>/`)存在;不存在则**终止**(不要假想 ID)。
|
|
90
|
+
2. 根据 README 描述的占位符或变量(如「目标环境」「部署 env」),**Read** 该目录内相应文件(路径同样锚定 `<repoRoot>`);**默认优先** **`requirement-status.yaml`**,提取 **`env`**(及 README 明确要求的其它字段)。
|
|
91
|
+
3. 若 README 需要的某项信息在工作项内**不存在或未填写**:**终止**,说明缺失字段与文件路径;**不**请用户在对话里补充(用户约定仅提供需求 ID)。
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 步骤 4:按文档执行
|
|
96
|
+
|
|
97
|
+
仅在「工作项上下文」**已满足 README 对参数的要求**后执行。
|
|
98
|
+
|
|
99
|
+
1. **严格对照** README:顺序、工作目录(若文档指定 `cd`)、环境变量、所用 CLI(如 `rush`、`docker`、`kubectl` 等)均以文档为准;将步骤 3 得到的 **`env` 等**按 README 约定注入命令或环境(禁止凭猜测填值)。**默认在 `<repoRoot>` 执行**;遵守本仓库 **AGENTS.md**(如 Rush 安装/构建约定),但**不**执行 README **未写出**的额外构建/发布步骤。
|
|
100
|
+
2. **每条命令**记录:是否成功、退出码或关键输出摘要、失败时的 stderr(可截断至可读长度)。
|
|
101
|
+
3. **失败处理**:与 **Guardrails** 一致——默认**失败即终止**后续部署步骤,在表格与摘要中写明失败命令与原因;**不因网络抖动等做无文档依据的多轮重试**(除非 README **明确**要求重试策略)。
|
|
102
|
+
4. **安全**:若 README 要求确认交互(如 `Are you sure?`),按文档处理;文档要求人工审批而 Agent 无法完成时,执行到该步即停止,并标明「需人工完成」。
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 步骤 5:对用户回复(状态汇总)
|
|
107
|
+
|
|
108
|
+
回复中**必须包含**一张汇总表,建议结构:
|
|
109
|
+
|
|
110
|
+
| 阶段 | 内容 | 结果 |
|
|
111
|
+
| --- | --- | --- |
|
|
112
|
+
| 阅读 | `.apm/deploy/README.md`(及 README 要求阅读的附属文档) | 成功 / 失败(原因) |
|
|
113
|
+
| 验收 | 文档是否足以执行 | 通过 / **未通过**(列具体问题) |
|
|
114
|
+
| 上下文 | `.apm/workitems/<requirementId>/`(含 `requirement-status.yaml` / `env` 等) | 成功 / **失败**(缺文件或缺字段) |
|
|
115
|
+
| 执行 | 按 README 执行的命令或步骤摘要(可按行拆分) | 每步 成功 / 失败 / 跳过 / 需人工 |
|
|
116
|
+
| 结论 | 部署是否完成 README 所述目标 | 是 / 否 / 未执行(门禁或上下文未过) |
|
|
117
|
+
|
|
118
|
+
若门禁或上下文未通过:**不要**出现「已尝试部署」的误导性表述;表格中「执行」阶段填 **未执行(原因)**。
|
|
119
|
+
|
|
120
|
+
**可选**:表格外一两句话概括当前阻塞点或已成功完成的范围。
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Guardrails
|
|
125
|
+
|
|
126
|
+
- **gitignore 不等于不存在**:`.apm` 下文件可能不入 Git 索引;**禁止**用「搜索无结果」代替 **Read** 或磁盘校验。
|
|
127
|
+
- **路径锚定 `<repoRoot>`**:凡 `.apm/deploy`、`.apm/workitems` 等路径**仅**相对仓库根;**禁止**因当前焦点在 monorepo 内某一子目录而在错误基底上拼路径。
|
|
128
|
+
- **文档优先**:无合格 `.apm/deploy/README.md` 解读结果,**不部署**。
|
|
129
|
+
- **用户只给 ID**:除 **`requirementId`** 外,**不依赖**用户在对话中口头提供环境、密钥说明等;这些信息须来自 **工作项目录** 或 README 已写明的本地/CI 约定。缺数据则**终止并写明缺口**,而非让用户「现场补一句」。
|
|
130
|
+
- **失败即终止**:在文档与上下文明确的前提下,任一步骤失败 → 停止后续步骤,仅记录状态;**例外**仅允许 Agent **自身**错误(如错目录)纠正后对**同一步**再试一次,仍失败则终止。
|
|
131
|
+
- **不臆造**:README 未写的命令、环境、目标集群/命名空间,**不补充**;工作项 YAML **未提供**的字段**不得**编造。
|
|
132
|
+
- **不报假成功**:命令失败或未完成 README 目标时,结论必须为「否」或等价表述。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: apm-dev
|
|
3
|
-
description: 按需求 ID 全自动开发:切分支、读 PRD、按改动规模选择 Quick 或 Spec
|
|
3
|
+
description: 按需求 ID 全自动开发:切分支、读 PRD、按改动规模选择 Quick 或 Spec 路径实现代码、提交并推送、同步产物;子 Agent 承担编码与规划落地;当用户 @ 本技能、提及全自动开发或「apm-dev」时使用。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# APM 全自动开发(按需求 ID)
|
|
@@ -38,7 +38,8 @@ instruction 子文件随该目录类推(如 `.apm/skills/apm-propose/propose-i
|
|
|
38
38
|
| 3 | **Quick 开发** | 仅当判定为「改动成本较小」时执行;由 **子 Agent** 写代码 |
|
|
39
39
|
| 4 | **Spec 开发** | 仅当判定为「改动成本较大」时执行;先 **apm-propose** 再 **apm-apply-change**,均由 **子 Agent** 按对应技能执行 |
|
|
40
40
|
| 5 | **提交与推送** | `git` 提交并 `push`,工作区干净 |
|
|
41
|
-
| 6 |
|
|
41
|
+
| 6 | **同步产物** | `apm upload-artifact <requirementId>`,将工作项目录内 Markdown 产物同步到平台 |
|
|
42
|
+
| 7 | **对用户回复** | **一张 Markdown 表格**汇总各步执行结果(见文末模板) |
|
|
42
43
|
|
|
43
44
|
---
|
|
44
45
|
|
|
@@ -118,9 +119,23 @@ instruction 子文件随该目录类推(如 `.apm/skills/apm-propose/propose-i
|
|
|
118
119
|
|
|
119
120
|
---
|
|
120
121
|
|
|
121
|
-
## 步骤 6
|
|
122
|
+
## 步骤 6:同步产物
|
|
122
123
|
|
|
123
|
-
|
|
124
|
+
在**仓库/工作区根目录**(与步骤 1、5 一致)执行:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
apm upload-artifact <requirementId>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
1. 该命令会**先清空**平台上该需求的产物文档,再将 `.apm/workitems/<requirementId>/` 下符合条件的 Markdown 重新上传(排除 `prd.md`、状态 YAML、reviews/defect/testcase 等 pull 系统文件;详见 CLI 行为)。
|
|
131
|
+
2. 若本地未放置需同步的 `.md` 产物,仍会清空平台侧产物;若有文档仅在仓库其它路径,须先复制或生成到工作项目录再执行。
|
|
132
|
+
3. 将命令是否成功、上传条数或错误摘要写入表格。
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 步骤 7:对用户回复(表格)
|
|
137
|
+
|
|
138
|
+
对用户回复 **必须包含一张 Markdown 表格**,汇总 **步骤 1~6**(步骤 7 为呈现表格本身,可不单独成行)。表头建议:
|
|
124
139
|
|
|
125
140
|
| 步骤 | 内容 | 结果 |
|
|
126
141
|
| --- | --- | --- |
|
|
@@ -129,6 +144,7 @@ instruction 子文件随该目录类推(如 `.apm/skills/apm-propose/propose-i
|
|
|
129
144
|
| 3 | Quick 开发(子 Agent) | 成功 / 失败 / **跳过** |
|
|
130
145
|
| 4 | Spec:apm-propose → apm-apply-change(子 Agent) | 成功 / 失败 / **跳过**;可注明子步骤 |
|
|
131
146
|
| 5 | commit & push;工作区干净 | 成功 / 失败(原因) |
|
|
147
|
+
| 6 | `apm upload-artifact <requirementId>` | 成功 / 失败(原因) |
|
|
132
148
|
|
|
133
149
|
**可选**:在表格外增加**简短**一句话摘要(例如当前分支名、阻塞点);若用户此前约定「仅表格」,则可仅输出表格。
|
|
134
150
|
|
|
@@ -136,7 +152,7 @@ instruction 子文件随该目录类推(如 `.apm/skills/apm-propose/propose-i
|
|
|
136
152
|
|
|
137
153
|
## Guardrails
|
|
138
154
|
|
|
139
|
-
- **失败即终止**:在用户提供的 **`requirementId`** 等参数合法、命令与路径按本技能书写的前提下,任一步骤(含 `apm branch`、`apm pull`、读文件、子 Agent、`git`)**一旦失败**:**停止后续所有步骤**,仅在表格中记录失败步骤与原因;**不要**为登录、依赖、网络、命令结果等做**多次**或「轮番」重试。
|
|
155
|
+
- **失败即终止**:在用户提供的 **`requirementId`** 等参数合法、命令与路径按本技能书写的前提下,任一步骤(含 `apm branch`、`apm pull`、`apm upload-artifact`、读文件、子 Agent、`git`)**一旦失败**:**停止后续所有步骤**,仅在表格中记录失败步骤与原因;**不要**为登录、依赖、网络、命令结果等做**多次**或「轮番」重试。
|
|
140
156
|
- **例外(Agent 自身失误)**:若失败明显由执行 Agent **用错工作目录、读错/漏写路径** 等导致,**允许**纠正 `cwd` 或路径后**仅对该失败步骤再执行一次**;纠正后仍失败则**立即终止**,不再扩展尝试。
|
|
141
157
|
- **不要**在无 `prd.md`(且 `apm pull` 后仍无)的情况下编造需求实现。
|
|
142
158
|
- **子 Agent** 提示中须带 **`requirementId`** 与仓库根路径意识,避免改错工作树。
|
|
@@ -3,10 +3,20 @@ name: apm-refine
|
|
|
3
3
|
description: 根据需求 ID 读取工作项 prd.md 与 reviews.xml,按「修订稿」标准结构(修订说明、背景与目标、范围、需求点、非功能、待确认/问题与局限等)润色并回写,再执行 apm refine 同步平台;当用户在对话中 @ 本技能或提出需求润色时使用。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# APM
|
|
6
|
+
# APM 需求润色
|
|
7
7
|
|
|
8
8
|
用户仅提供 **需求 ID**(workitem id)。**缺 ID 时索要,不猜测。**
|
|
9
9
|
|
|
10
|
+
## 合并原则
|
|
11
|
+
|
|
12
|
+
1. **正文依据 = 需求原文 + 补充信息**:只把用户在补充信息中**明确**要体现的内容(补充、修改、删除、拍板口径)合并进修订稿;用户没说到的,**保持原文或不写**,**不自作主张**补需求、不替用户「落实」评审建议。
|
|
13
|
+
2. **未回应的评审**:不列入「待确认」、不改成待办、不推断「仍有问题」;视为用户未要求在本次修订中处理(可能无此问题、暂不改、或评审误解)。
|
|
14
|
+
3. **评审的定位**:仅辅助理解补充信息在回应什么;补充信息与评审不一致时,**以补充信息为准**。
|
|
15
|
+
4. **消除重复**:同一议题在原文与补充信息中多处出现时,合并为**一处**表述。
|
|
16
|
+
5. **可追溯(轻量)**:可选「修订说明」概括相对原文的变化,且**只写补充信息实际带来的变化**,不罗列未采纳的评审。
|
|
17
|
+
6. **与仓库一致**:对照 `AGENTS.md`、`.apm/product-capability-inventory` 等,避免修订稿与用户已确认表述冲突;**禁止**用代码路径当需求论据。
|
|
18
|
+
7. **图片引用保持原样**:若原文含图片(如 `<img ...>`),在修订稿中直接保留对应 `img` 标签原文;不要改写成“图片见原始文档”等占位说明。
|
|
19
|
+
|
|
10
20
|
## 强制执行顺序(四步)
|
|
11
21
|
|
|
12
22
|
### 步骤 1:读取 `prd.md` 与 `reviews.xml`
|
|
@@ -29,8 +39,9 @@ description: 根据需求 ID 读取工作项 prd.md 与 reviews.xml,按「修
|
|
|
29
39
|
2. **落盘结构:标准「修订稿」模板(写入 `prd.md` 须遵循)**
|
|
30
40
|
使用 **Read** 读取与 `SKILL.md` 同目录的 **[apm-refine-template.md](./apm-refine-template.md)**,按其中**修订稿骨架**与章节说明组织正文(默认全文润色形态)。执行本技能时**须**读取该文件,不以记忆代替。
|
|
31
41
|
|
|
32
|
-
**改动与边界(仍须满足)**
|
|
33
|
-
|
|
42
|
+
**改动与边界(仍须满足)**
|
|
43
|
+
|
|
44
|
+
- **修订说明**中的「本次合并的要点」须**分点**写清本次相对原文的变更与**范围边界**(影响面、不做的事)。若改动项多于 3 条,可在「修订说明」后续追加小节 **「相对原文的变更」** 继续分点罗列。
|
|
34
45
|
- 全文润色时**不要机械留空标题**:无内容的章节可删除(如无非功能则不写该章),但**背景与目标 / 范围 / 需求说明**一般应完整可读。
|
|
35
46
|
|
|
36
47
|
**局部替换(不必强行铺满模板)**
|
|
@@ -55,21 +66,15 @@ apm refine <需求ID>
|
|
|
55
66
|
|
|
56
67
|
### 步骤 5:回复用户
|
|
57
68
|
|
|
58
|
-
对用户可见回复 **仅限一张 Markdown 表格**,**仅填写步骤 1~4 每步的执行状态**。**不得**在表格外输出任何其他内容(不粘贴 prd 正文、不另写摘要或提示)。
|
|
69
|
+
对用户可见回复 **仅限一张 Markdown 表格**,**仅填写步骤 1 ~ 4 每步的执行状态**。**不得**在表格外输出任何其他内容(不粘贴 prd 正文、不另写摘要或提示)。
|
|
59
70
|
|
|
60
71
|
建议表头(每行「状态」仅填 **成功** / **失败** / **跳过**;失败时可加简短原因,如 `失败:文件不存在`):
|
|
61
72
|
|
|
62
|
-
| 步骤
|
|
63
|
-
|
|
64
|
-
| 1. 读取 `prd.md` 与 `reviews.xml` |
|
|
65
|
-
| 2. 润色为「标准需求文档」
|
|
66
|
-
| 3. 回写 `prd.md`
|
|
67
|
-
| 4. 执行 `apm refine <需求ID>`
|
|
73
|
+
| 步骤 | 状态 |
|
|
74
|
+
| --------------------------------- | ---- |
|
|
75
|
+
| 1. 读取 `prd.md` 与 `reviews.xml` | |
|
|
76
|
+
| 2. 润色为「标准需求文档」 | |
|
|
77
|
+
| 3. 回写 `prd.md` | |
|
|
78
|
+
| 4. 执行 `apm refine <需求ID>` | |
|
|
68
79
|
|
|
69
80
|
---
|
|
70
|
-
|
|
71
|
-
## 内部编排要点
|
|
72
|
-
|
|
73
|
-
**通读 prd → Read [apm-refine-template.md](./apm-refine-template.md) →(若有)解析 reviews 与 reply → 按修订稿骨架写入 → 写回 `prd.md` → `apm refine`。**
|
|
74
|
-
|
|
75
|
-
唯 **对用户可见** 的回复遵守上文「步骤 5」的表格约束。
|
|
@@ -1,28 +1,20 @@
|
|
|
1
|
-
# APM 需求「修订稿」落盘模板
|
|
2
|
-
|
|
3
|
-
由 **`apm-refine`** 技能引用:润色并写回 `.apm/workitems/<需求ID>/prd.md` 时,按下列骨架组织正文。
|
|
4
|
-
|
|
5
|
-
文中 **「补充信息」** 指 **`reviews.xml`** 中的评审意见与 `reply`(及评审记录中已写明的事实),不单指对话里的用户发言。
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
1
|
# [需求标题](修订稿)
|
|
10
2
|
|
|
11
|
-
|
|
3
|
+
**修订说明**(可选)
|
|
12
4
|
|
|
13
|
-
- 基于评审日期 /
|
|
14
|
-
- 本次合并的要点:1~3 条 bullet
|
|
5
|
+
- 基于评审日期 / 评审记录:简要一句
|
|
6
|
+
- 本次合并的要点:1~3 条 bullet
|
|
15
7
|
|
|
16
8
|
---
|
|
17
9
|
|
|
18
10
|
## 背景与目标
|
|
19
11
|
|
|
20
|
-
|
|
12
|
+
(合并原文与用户补充信息中明确补充/修正的表述)
|
|
21
13
|
|
|
22
14
|
## 范围
|
|
23
15
|
|
|
24
16
|
- **包含**:
|
|
25
|
-
-
|
|
17
|
+
- **不包含**:(若**补充信息**或原文中明确收窄/排除)
|
|
26
18
|
|
|
27
19
|
## 需求说明
|
|
28
20
|
|
|
@@ -36,12 +28,12 @@
|
|
|
36
28
|
|
|
37
29
|
(性能、权限、兼容、埋点等——仅当原文或补充信息涉及)
|
|
38
30
|
|
|
39
|
-
##
|
|
31
|
+
## 待确认
|
|
40
32
|
|
|
41
|
-
|
|
33
|
+
仅当**用户自己在补充信息里**留下未拍板事项、或写明「待定」「再议」等时列出(可选章节,可整节省略):
|
|
42
34
|
|
|
43
|
-
- [ ]
|
|
35
|
+
- [ ] (用户未决口径摘要)— 用户原话或简要归纳
|
|
44
36
|
|
|
45
|
-
|
|
37
|
+
---
|
|
46
38
|
|
|
47
|
-
|
|
39
|
+
若用户只需要「替换某几段」而非全文,可仅在回复中给出**修改后的完整段落** + 简短「相对原文的变更摘要」,不必机械填满所有章节。
|
|
@@ -1,27 +1,46 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: apm-review
|
|
3
|
-
description:
|
|
3
|
+
description: 结合本仓库上下文对需求做结构化评审,只有当用户主动提及该技能时才可被使用,该技能调用依赖需求ID。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# 需求评审
|
|
7
7
|
|
|
8
8
|
用户仅提供 **需求 ID**(workitem id)。缺 ID 时索要,不猜测。
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
## 评审原则
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
1. **产品视角优先**:用户侧影响、业务闭环、风险与待确认点;避免晦涩技术术语堆砌。
|
|
13
|
+
2. **与仓库对齐(能力边界)**:阅读 `AGENTS.md`、子项目说明、`.apm/product-capability-inventory` 等与需求相关的部分,判断需求是否与既有能力/术语冲突、是否超出现有边界。**禁止**用「某文件某行」证明 PRD 对错;**禁止**根据代码**猜测** PRD 未写清的口径——口径不清应归入「问题」待产品澄清。
|
|
14
|
+
3. **业务合理性(按需)**:结合仓库可读的业务/产品上下文,判断需求是否想清楚、方案是否当下较优、是否与业务目标或流程冲突。**仅当**不合理、需再商榷或非当下较优时,才写 `- **业务合理性:**:`;合理则**不写**该字段。
|
|
15
|
+
4. **信息完备与落地(互斥规则)**:
|
|
16
|
+
- 仅当 PRD/需求信息**不足以**形成开发可执行描述(关键落点、口径、范围、汇总规则等无法确定)时,写 `- **问题:**`,此时**不写**可行性与风险点。
|
|
17
|
+
- 若已足以判断「可以实现」(非关键细节不阻塞落地),写 `- **可行性:**`(成本低/中/高 + 精简说明);影响面大时再追加 `- **风险点:**`。
|
|
18
|
+
5. **澄清优先但不泛问**:不阻塞落地则不提问;不问可推断的琐碎问题。
|
|
19
|
+
6. **隐性知识沉淀可执行**:对用户单独输出的每条沉淀建议应能回答「在仓库里补什么、能消除哪类隐性歧义」;若本次未发现值得沉淀的差异点,仍须给出一句结论(例如「未发现与通用假设显著偏离、需单独成文的隐性规则」)。
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## 执行步骤
|
|
13
23
|
|
|
14
24
|
### 步骤 1:读取 `prd.md`
|
|
15
25
|
|
|
16
26
|
1. 使用 **Read** 读取 `.apm/workitems/<需求ID>/prd.md`。
|
|
17
27
|
2. 若 Read **失败**(文件不存在或无法读取):本步骤记为失败,**终止**,不执行步骤 2、3。
|
|
18
28
|
|
|
19
|
-
### 步骤 2
|
|
29
|
+
### 步骤 2:读代码
|
|
30
|
+
阅读仓库源码以及 `.apm/product-capability-inventory` 中与需求相关的条目;不展开无关模块代码。
|
|
31
|
+
|
|
32
|
+
### 步骤 3:评审
|
|
33
|
+
1. **拆条**:多条诉求时拆成 `### 需求点 1..N`,每条独立走完「业务合理性(可选)→ 问题 或 可行性(+风险)」逻辑。
|
|
34
|
+
|
|
35
|
+
2. **成文(仅评审模板)**:按 `output-template.md` 拼出写入 comment 的 Markdown(顶格可加 `### 评审人` + 当前模型名);**不含**「隐性知识沉淀」段落;
|
|
20
36
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
3. 使用 **Write** 将正文写入**临时文件**,路径建议使用**绝对路径**,例如 `/tmp/apm-review-<需求ID>.md`。**Markdown 评审正文**:**只写确实存在的问题**;用「需求背景 / 需求范围 / 交互与功能要求第 X 节 / 非目标」等文档自有结构**点名条款**(半句锚定即可),不先单独铺一节「对用户意图的理解」,**不写与 `prd-review` 类似的整条「需求描述」复述**。撰写前须完成代码检索并锁定本轮**评审立场**(见 reference 中「评审立场」),正文内容与措辞须与该立场一致,**不得超越可见范围下断定**。
|
|
38
|
+
|
|
39
|
+
4. **自检**:每条是否都有「需求描述」;「问题」与「可行性/风险」是否互斥符合 `output-template.md`;是否误引代码路径作论据;comment 全文是否**未混入**隐性知识段落。
|
|
40
|
+
|
|
41
|
+
5. **提交评审记录**:在项目根目录下执行:`apm comment <需求ID> --file=<临时文件绝对路径> --model=<评论使用的模型名称>`。
|
|
42
|
+
|
|
43
|
+
6. 命令结束后 **删除临时文件**(**Delete** 工具或 `rm`),无论命令成功或失败都尽量清理(失败时保留文件仅供用户排错——技能默认仍删除,若需保留应在表格备注中说明)。
|
|
25
44
|
|
|
26
45
|
### 步骤 3:回复用户
|
|
27
46
|
|
|
@@ -33,6 +52,4 @@ description: 根据需求 ID 读取工作项 prd.md,对照代码做需求评
|
|
|
33
52
|
|------|------|------|
|
|
34
53
|
| 1. 读取 prd.md | 成功 / 失败 | 例如实际读取路径;失败时写错误原因 |
|
|
35
54
|
| 2. 评审与 comment | 成功 / 失败 | 临时文件路径(已删可写「已清理」);`apm comment` 退出情况或 API 返回摘要;**建议**注明本轮评审立场(仅前台 / 仅后台 / 全栈) |
|
|
36
|
-
| 3. 清理临时文件 | 成功 / 失败 | — |
|
|
37
|
-
|
|
38
|
-
内部编排:**通读 prd → 检索并对照与需求相关的代码 → 判定本轮可见范围(仅前台 / 仅后台 / 全栈)并选定评审立场 → 将技术发现改写为业务白话 → 只输出有问题之处**;唯**对用户输出**遵守上表约束。
|
|
55
|
+
| 3. 清理临时文件 | 成功 / 失败 | — |
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
### 需求点 1
|
|
2
|
+
|
|
3
|
+
- **需求描述:**: (一句话概括要做什么,产品视角)
|
|
4
|
+
- (关键点 1)
|
|
5
|
+
- (关键点 2)
|
|
6
|
+
|
|
7
|
+
### 需求点 2
|
|
8
|
+
|
|
9
|
+
- **需求描述:**: (一句话概括要做什么,产品视角)
|
|
10
|
+
|
|
11
|
+
- **问题:**
|
|
12
|
+
- (问题 1:必须澄清“页面/表/口径/范围/汇总规则”等)
|
|
13
|
+
- (问题 2)
|
|
14
|
+
- (问题 3)
|
|
15
|
+
|
|
16
|
+
### 需求点 3
|
|
17
|
+
|
|
18
|
+
- **需求描述:**: (一句话概括要做什么,产品视角)
|
|
19
|
+
|
|
20
|
+
- **可行性:**
|
|
21
|
+
- 成本:低/中/高
|
|
22
|
+
- (在不引入猜测的前提下,说明成本对应的业务/口径/环节调整)
|
|
23
|
+
|
|
24
|
+
### 需求点 4
|
|
25
|
+
|
|
26
|
+
- **需求描述:**: (一句话概括要做什么,产品视角)
|
|
27
|
+
- **可行性:**
|
|
28
|
+
- 成本:中/高
|
|
29
|
+
- (在不引入猜测的前提下,说明成本对应的业务/口径/环节调整)
|
|
30
|
+
- **风险点:**
|
|
31
|
+
- (风险 1:可能影响的不止是展示文案,例如统计口径/汇总逻辑/跨模块联动等)
|
|
32
|
+
- (风险 2)
|
|
33
|
+
|
|
34
|
+
### 需求点 5(业务合理性:仅不合理时出现)
|
|
35
|
+
|
|
36
|
+
- **需求描述:**: (一句话概括要做什么,产品视角)
|
|
37
|
+
- **业务合理性:**
|
|
38
|
+
- (为何不合理/与用户目标或现有业务冲突/用户方案非当下较优;可写更可取的替代思路或需产品先拍板的点)
|
|
39
|
+
- **可行性:**
|
|
40
|
+
- 成本:低/中/高
|
|
41
|
+
- (仍可在指出业务问题的同时评估落地成本;若信息仍不足以落地,则改用「需求点 2」结构:业务合理性 + **问题**,不出现可行性/风险点)
|
|
42
|
+
|
|
43
|
+
字段顺序与并存:`需求描述` 始终第一;`业务合理性` 仅在不合理时出现在 `需求描述` 之后。其后仍遵循「有问题则只写问题,无问题则写可行性(可加风险点)」。
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: apm-dev
|
|
3
|
-
description: 按需求 ID 全自动开发:切分支、读 PRD、按改动规模选择 Quick 或 Spec 路径实现代码、提交并推送;子 Agent 承担编码与规划落地;当用户 @ 本技能、提及全自动开发或「apm-dev」时使用。
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# APM 全自动开发(按需求 ID)
|
|
7
|
-
|
|
8
|
-
用户给出 **需求 ID**(`requirementId`,与工作项目录名一致)。**缺 ID 时索要,不猜测。**
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## 输入
|
|
12
|
-
|
|
13
|
-
| 字段 | 规则 |
|
|
14
|
-
| --- | --- |
|
|
15
|
-
| **`requirementId`** | **必填**。与 `.apm/workitems/<requirementId>/` 目录名一致。 |
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## 技能文件定位(apm-propose / apm-apply-change)
|
|
20
|
-
|
|
21
|
-
Spec 路径依赖的技能**仅**来自 **`.apm/skills/`**。执行前父 Agent **须 Read**:
|
|
22
|
-
|
|
23
|
-
| 技能 | 路径 |
|
|
24
|
-
| --- | --- |
|
|
25
|
-
| **apm-propose** | `.apm/skills/apm-propose/SKILL.md` |
|
|
26
|
-
| **apm-apply-change** | `.apm/skills/apm-apply-change/SKILL.md` |
|
|
27
|
-
|
|
28
|
-
instruction 子文件随该目录类推(如 `.apm/skills/apm-propose/propose-instruction.md`)。若上述 `SKILL.md` **不存在或不可读**:**不得**进入 Spec 子流程;在表格中标记失败原因(例如需先 `apm init` 或同步 `.apm/skills`),并停止步骤 4。
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## 流程总览
|
|
33
|
-
|
|
34
|
-
| 序号 | 步骤 | 说明 |
|
|
35
|
-
| --- | --- | --- |
|
|
36
|
-
| 1 | **切分支** | 在仓库根目录执行 `apm branch <requirementId>` |
|
|
37
|
-
| 2 | **读 PRD + 成本评估** | **Read** `.apm/workitems/<requirementId>/prd.md`,判定 Quick / Spec |
|
|
38
|
-
| 3 | **Quick 开发** | 仅当判定为「改动成本较小」时执行;由 **子 Agent** 写代码 |
|
|
39
|
-
| 4 | **Spec 开发** | 仅当判定为「改动成本较大」时执行;先 **apm-propose** 再 **apm-apply-change**,均由 **子 Agent** 按对应技能执行 |
|
|
40
|
-
| 5 | **提交与推送** | `git` 提交并 `push`,工作区干净 |
|
|
41
|
-
| 6 | **对用户回复** | **一张 Markdown 表格**汇总各步执行结果(见文末模板) |
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
## 步骤 1:`apm branch`
|
|
46
|
-
|
|
47
|
-
1. 在**仓库/工作区根目录**执行:
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
apm branch <requirementId>
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
2. 记录:命令是否成功、简要输出或错误信息(写入最终表格)。**失败则按「Guardrails → 失败即终止」处理**,不为此命令做多轮重试(除非属 Agent 用错目录等可纠正失误)。
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
## 步骤 2:读取 PRD 并评估改动成本
|
|
58
|
-
|
|
59
|
-
1. **Read** 全文:`.apm/workitems/<requirementId>/prd.md`。
|
|
60
|
-
2. 若无法读取:在仓库根目录执行 **`apm pull <requirementId>`**(同步工作项),再 **Read** 一次;仍失败则**停止后续实现**,仅在表格中标记失败原因。
|
|
61
|
-
3. **不**为评估向用户发起追问;信息不足时倾向 **Spec**(保守)。
|
|
62
|
-
|
|
63
|
-
### Quick(较小)与 Spec(较大)判定参考
|
|
64
|
-
|
|
65
|
-
**倾向 Quick**(满足越多越适用):
|
|
66
|
-
|
|
67
|
-
- 影响范围局部:少量文件或单一层次(例如仅前端组件、或仅一个后端模块小改)。
|
|
68
|
-
- 无新表结构/大规模迁移/权限模型变更。
|
|
69
|
-
- PRD 验收点清晰且数量少(经验上 **≤3** 条独立验收维度)。
|
|
70
|
-
- 不需要跨多服务的架构裁定即可开工。
|
|
71
|
-
|
|
72
|
-
**倾向 Spec**(命中任一条即可):
|
|
73
|
-
|
|
74
|
-
- 前后端联动、多包改造或新公共抽象。
|
|
75
|
-
- 新数据模型、迁移、或安全/审计/权限相关。
|
|
76
|
-
- PRD 范围大、条款多,或存在明显「待确认/多方案」需先规划。
|
|
77
|
-
- 评估认为不先产出 **proposal / design / specs / tasks** 则难以保证实现与验收对齐。
|
|
78
|
-
|
|
79
|
-
在表格「步骤 2」中写明结论:**Quick** 或 **Spec**,以及**一行内**理由(关键词即可)。
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## 步骤 3:Quick 开发模式(子 Agent)
|
|
84
|
-
|
|
85
|
-
**条件**:步骤 2 判定为 **Quick**。
|
|
86
|
-
|
|
87
|
-
1. 父 Agent 已通过 **Read** 掌握 `prd.md`;若启动新子 Agent,在委派提示中写明 **`requirementId`**、工作项路径、以及「实现须严格对照 PRD,改动范围最小化」。
|
|
88
|
-
2. 使用 **Task** 工具,`subagent_type: generalPurpose`,**readonly: false**,委派子 Agent:
|
|
89
|
-
- 自行 **Read** `.apm/workitems/<requirementId>/prd.md`(若会话未带全文)。
|
|
90
|
-
- 按 PRD 直接改代码;遵守本仓库构建与依赖约定(AGENTS.md)。
|
|
91
|
-
- 完成后在返回中说明:改了哪些路径、如何对照验收、是否通过本地可执行的检查(若子 Agent 跑了 `rushx build` / 测试等则写明结果)。
|
|
92
|
-
3. 父 Agent 根据子 Agent 返回在表格中填写步骤 3 **状态**;本模式下步骤 4 填 **跳过**。
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
## 步骤 4:Spec 开发模式(子 Agent)
|
|
97
|
-
|
|
98
|
-
**条件**:步骤 2 判定为 **Spec**。
|
|
99
|
-
|
|
100
|
-
1. 父 Agent **Read** **apm-propose**、**apm-apply-change** 的 `SKILL.md`(**仅** `.apm/skills/` 下路径,见上节)。
|
|
101
|
-
2. **子 Agent A(规划)**:Task `generalPurpose`,提示其自行 **Read** `.apm/skills/apm-propose/SKILL.md` 并完整遵循:在 `.apm/workitems/<requirementId>/` 生成 **proposal、design、specs、tasks** 等工件(顺序与依赖以 SKILL 为准)。
|
|
102
|
-
3. **子 Agent B(实现)**:待 A 成功落盘后,再 Task `generalPurpose`,提示其自行 **Read** `.apm/skills/apm-apply-change/SKILL.md` 并完整遵循:按 **`tasks.md`** 驱动实现与勾选;遵守该技能中的停止条件与 commit 约定。
|
|
103
|
-
4. 若 **apm-propose** 未产出可用 **`tasks.md`**,不得强行进入 **apm-apply-change**;表格中标记阻塞原因。
|
|
104
|
-
5. 表格中步骤 3 填 **跳过**;步骤 4 分两行或合并一行写清 propose / apply 状态(见表格模板)。
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
## 步骤 5:提交并推送
|
|
109
|
-
|
|
110
|
-
在仓库根目录执行(可用一条复合命令或分步;以实际仓库远程为准):
|
|
111
|
-
|
|
112
|
-
1. `git status`:确认变更范围。
|
|
113
|
-
2. 若有未提交变更:`git add -A`,然后 `git commit -m "feat(req-<requirementId>): <简短说明>"`(说明应概括本轮需求实现;若子 Agent 已按任务多次 commit,则可能无需新 commit,以 `status` 为准)。
|
|
114
|
-
3. `git push`:推送到当前分支对应远程(首次必要时 `-u origin <branch>`)。
|
|
115
|
-
4. 再次 `git status`:**须为干净工作区**(无未提交、未跟踪的重要残留;若有应记录为失败或说明例外)。
|
|
116
|
-
|
|
117
|
-
将命令结果、最终分支名、是否已 push、工作区是否干净写入表格。
|
|
118
|
-
|
|
119
|
-
---
|
|
120
|
-
|
|
121
|
-
## 步骤 6:对用户回复(表格)
|
|
122
|
-
|
|
123
|
-
对用户回复 **必须包含一张 Markdown 表格**,汇总 **步骤 1~5**(步骤 6 为呈现表格本身,可不单独成行)。表头建议:
|
|
124
|
-
|
|
125
|
-
| 步骤 | 内容 | 结果 |
|
|
126
|
-
| --- | --- | --- |
|
|
127
|
-
| 1 | `apm branch <requirementId>` | 成功 / 失败(原因) |
|
|
128
|
-
| 2 | 读 PRD + 成本评估 | Quick 或 Spec;一行理由 |
|
|
129
|
-
| 3 | Quick 开发(子 Agent) | 成功 / 失败 / **跳过** |
|
|
130
|
-
| 4 | Spec:apm-propose → apm-apply-change(子 Agent) | 成功 / 失败 / **跳过**;可注明子步骤 |
|
|
131
|
-
| 5 | commit & push;工作区干净 | 成功 / 失败(原因) |
|
|
132
|
-
|
|
133
|
-
**可选**:在表格外增加**简短**一句话摘要(例如当前分支名、阻塞点);若用户此前约定「仅表格」,则可仅输出表格。
|
|
134
|
-
|
|
135
|
-
---
|
|
136
|
-
|
|
137
|
-
## Guardrails
|
|
138
|
-
|
|
139
|
-
- **失败即终止**:在用户提供的 **`requirementId`** 等参数合法、命令与路径按本技能书写的前提下,任一步骤(含 `apm branch`、`apm pull`、读文件、子 Agent、`git`)**一旦失败**:**停止后续所有步骤**,仅在表格中记录失败步骤与原因;**不要**为登录、依赖、网络、命令结果等做**多次**或「轮番」重试。
|
|
140
|
-
- **例外(Agent 自身失误)**:若失败明显由执行 Agent **用错工作目录、读错/漏写路径** 等导致,**允许**纠正 `cwd` 或路径后**仅对该失败步骤再执行一次**;纠正后仍失败则**立即终止**,不再扩展尝试。
|
|
141
|
-
- **不要**在无 `prd.md`(且 `apm pull` 后仍无)的情况下编造需求实现。
|
|
142
|
-
- **子 Agent** 提示中须带 **`requirementId`** 与仓库根路径意识,避免改错工作树。
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
# APM 需求评审规范与模板
|
|
2
|
-
|
|
3
|
-
> 供 `apm-review` 技能使用:撰写并落盘到临时文件的评审正文时,遵守本节全文。
|
|
4
|
-
|
|
5
|
-
## 评论受众与用语(写入 `apm comment` 的正文)
|
|
6
|
-
|
|
7
|
-
- **读者**:产品经理与业务方为主;评论会作为协作记录,应**全程可读、可转发**,不假设读者会写代码或熟悉工程名词。
|
|
8
|
-
- **禁止出现在评论正文里**:代码片段、文件路径、类名/函数名/接口名、库名、数据库表字段名、配置项名、命令行等与实现绑定的符号;也避免堆叠「中间件、DTO、ORM、幂等、熔断」等纯工程术语(除非已在 `prd.md` 里作为约定用语出现且别无说法)。
|
|
9
|
-
- **推荐写法**:用日常中文说明「用户会看到什么」「缺了哪条规则大家会各猜各的」「和现有能力会不会打架」等;若必须指向实现,只说「与当前后台/前台的既有能力有关」或「需要和技术同事确认某某板块是否已有」,**不把符号留给 PM 去对号入座**。
|
|
10
|
-
- **立场声明(业务白话,一两句即可)**:若本轮仅为**单侧**对照(只看清浏览器端或只看清服务端),正文**开头**用不加术语的一句话标明**评审视角**(例如「本轮主要对照当前浏览器里可见的界面与流程」或「本轮主要对照当前服务端已暴露的能力与数据边界」),让读者知道评论**不是**全链路结论;**两侧均已实质核对**时,可写一句「前后台实现均已对照」或省略。**禁止**用「从前端角度」「站在后端」等角色标签式套话堆砌,一句说清范围即可。
|
|
11
|
-
- **内部核对**:Agent 读代码时仍可自用路径与符号做推理;**落盘到临时文件、提交评论前**须把「代码依据」改写成上述白话,不复制粘贴技术标识符。
|
|
12
|
-
|
|
13
|
-
## 正文形态与语气(写入临时文件的评审)
|
|
14
|
-
|
|
15
|
-
- **像真人写的**:自然段落或简短条目均可;**禁止**行政腔、教程腔套话,例如「请先确认」「建议补一句」「会上拍板」「建议各方对齐」等指向「写作动作」的提示——只陈述**文档里哪里不顺、会导致什么后果**。
|
|
16
|
-
- **直入问题**:不写「对用户意图的理解」这类总起段;意图若与某条缺陷强相关,**并入该条一句带过**即可。
|
|
17
|
-
- **只写有内容的条目**:某类问题不存在则**整段不写**;**禁止**用「未发现矛盾」「未见明显问题」「在已对照范围内无……」等否定句凑篇幅。若通读后没有可写问题:正文可仅为一两句说明「按当前正文暂无新增评审意见」,**仍不写**空洞分类标题。
|
|
18
|
-
- **Markdown 结构与可读性(写入评论的正文)**:平台讨论区按 Markdown 渲染。正文宜用 **`##` 二级标题**按条拆分(标题里点明文档位置),条内可用 **「要点 / 后果」** 或等价两项列表;**关键短语加粗**,必要时用 `---` 分隔大段,避免「一整块纯叙述」难以扫读。结构服从内容:**没有问题则不硬凑条目**。
|
|
19
|
-
|
|
20
|
-
## 评审立场(依可见代码)
|
|
21
|
-
|
|
22
|
-
对照需求完成检索后,根据**本轮实际读过、且作为依据写入评论的代码范围**选定立场;**评论写什么、写到哪一层,以该可见范围为上限**。
|
|
23
|
-
|
|
24
|
-
| 可见范围 | 评审立场 | 写入评论时的要求 |
|
|
25
|
-
| -------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
26
|
-
| 主要只见 **浏览器端 / 前台界面与交互** | **前台视角** | 只评界面入口、流程、状态与交互缺口;涉及持久化、权限、接口形态时,用白话写成「须与服务端/后台约定」「单凭当前界面看不出是否已有支撑」,**不下「后台一定如何」的断定**。 |
|
|
27
|
-
| 主要只见 **服务端 / API 与数据层** | **后台视角** | 只评服务能力、数据含义、权限与边界;涉及页面怎么摆、按钮文案时,用「展示层须另侧对齐」一类表述,**不代替产品写 UI 细则**。 |
|
|
28
|
-
| **前台与后台均已就本需求相关链路核对** | **全栈视角** | 可写前后衔接断层、端到端验收风险(仍用业务白话、禁止符号)。 |
|
|
29
|
-
|
|
30
|
-
**禁止**:只读过一侧却在评论中写超出该侧可见范围的**全称断定**(例如仅对照前台却断言「库里不会有某类数据」)。单侧视角下若发现需求显然依赖另一侧而未写明,应写「文档未约定与后台/前台的衔接点,易导致联调分歧」,而非编造另一侧实现细节。
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## 立场:专业评审,而非复述原文
|
|
35
|
-
|
|
36
|
-
- `prd.md` 可能口语化、不完整;正文用**清晰、可决策**的语言(业务名词与文档对齐),**对准具体条款**写缺口或风险。
|
|
37
|
-
- **禁止**空洞表态(例如「技术上都能做」);谈可行性须**建立在已读相关代码与模块边界之上**(且不超过上文「评审立场」准许的范围),说明与**现有能力划分、数据含义、产品约定**的关系及**后续改版成本**,用语落在「需求若坚持某种表述会带来何种**产品规则或协作上的代价**」,而非教人怎么写代码。
|
|
38
|
-
|
|
39
|
-
## 评审范围
|
|
40
|
-
|
|
41
|
-
### 要做(需求侧 + 基于代码的落地风险)
|
|
42
|
-
|
|
43
|
-
| 类别 | 说明 |
|
|
44
|
-
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
45
|
-
| **意图与表述** | 业务目标、约束、成功画面是否可从文中唯一推断;关键名词是否需统一 |
|
|
46
|
-
| **歧义** | 多种合理解读并存 |
|
|
47
|
-
| **遗漏 / 不完整** | 边界、异常、权限、状态组合未写清;代码已覆盖场景文档未提 |
|
|
48
|
-
| **矛盾** | 章节或枚举互斥 |
|
|
49
|
-
| **不可验收** | 缺可观测判据或主语不明 |
|
|
50
|
-
| **与现状冲突 / 迭代风险** | **须在读完相关代码后**再写,且**不超出**上文「评审立场」准许的范围:与现有能力划分、数据含义、权限或流程约定抵触,或大牵连、难演进——**需求层取舍提醒**;正文只用**白话概括依据**,不写路径与符号(见「评论受众与用语」),非代码好坏评判 |
|
|
51
|
-
|
|
52
|
-
### 不要做
|
|
53
|
-
|
|
54
|
-
代码风格与微重构、与需求清晰度无关的性能闲话、替写完整正式 PRD(除非用户明确要求)、长篇复述 `prd.md` 全文。
|
|
55
|
-
|
|
56
|
-
若「实现与文档不一致」,表述落在**需求如何写清或与现状对齐**,不指责实现错误。
|
|
57
|
-
|
|
58
|
-
**与代码交付面脱节的「空头边界」**:对照代码后若已能判断**实际交付仅为 Web**(仓库无独立 App、小程序等其它端实现),且 `prd.md` **已写明**某条范围或非目标(例如「本期不考虑移动端 ×× 适配」),则**不再**单独写「未界定移动端是否包含窄屏桌面浏览器、平板横竖屏」等与**当前单一 Web 交付**无实质冲突、也不会改变验收判据的抠字条款——除非文档与「仅 Web」或验收环境约定**明显矛盾**,或 PRD 自己承诺了多终端却与代码不符。
|
|
59
|
-
|
|
60
|
-
## 书写约束(针对写入文件的评审正文)
|
|
61
|
-
|
|
62
|
-
- 每条问题须说清「指向文档哪一句 / 哪一节」+「会卡在哪」。篇幅上**优先简洁**:段落式宜控制在**两三句话量级**;若采用 **Markdown 分条**,每条下的「要点 / 后果」也各自保持简短,**禁止**为套模板而重复空话。
|
|
63
|
-
- 引用需求文档用章节名、小节编号或口语转述条款;**不写**代码或工程标识符,技术边界用「评论受众与用语」中的白话改写。
|
|
64
|
-
- **简洁优先**:宁可少写几条,也不要为显得「全面」而重复或空话。
|
|
65
|
-
- 不对「业务价值高低」做主观评判;可说明「需求未定义清楚会导致何种决策瘫痪或返工风险」。
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
## 评审模板(写入临时文件的正文示例,结构仅供参考)
|
|
70
|
-
|
|
71
|
-
按问题组织,**每条尽量点明文档位置**(章节名、小节编号、列表要点均可)。可读性要求高时优先采用 **Markdown 分条 + 要点/后果**(见上文「正文形态与语气」)。
|
|
72
|
-
|
|
73
|
-
**示例(标题 + 要点/后果 + 加粗):**
|
|
74
|
-
|
|
75
|
-
```markdown
|
|
76
|
-
## 「需求范围」第三条:主链路落在哪一屏没说死
|
|
77
|
-
|
|
78
|
-
- **要点**:……
|
|
79
|
-
- **后果**:……
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## 「交互与功能要求」第 4 节:类型认定规则缺失
|
|
84
|
-
|
|
85
|
-
- **要点**:……
|
|
86
|
-
- **后果**:……
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
(以上为示例:实际只保留**本轮确有依据**的条目;没有问题则不硬写。)
|