@wozhuzaisi/prdd-cli 0.1.0
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/README.md +60 -0
- package/bin/prdd-cli.js +128 -0
- package/examples/sample-PRD.md +24 -0
- package/lib/fs-utils.js +46 -0
- package/lib/index.js +6 -0
- package/lib/install.js +188 -0
- package/lib/paths.js +12 -0
- package/package.json +39 -0
- package/templates/.claude/skills/prdd-implement-story/SKILL.md +6 -0
- package/templates/.claude/skills/prdd-implement-story/prdd-skill-manifest.yaml +1 -0
- package/templates/.claude/skills/prdd-implement-story/workflow.md +40 -0
- package/templates/.claude/skills/prdd-ingest/SKILL.md +6 -0
- package/templates/.claude/skills/prdd-ingest/prdd-skill-manifest.yaml +1 -0
- package/templates/.claude/skills/prdd-ingest/workflow.md +42 -0
- package/templates/.claude/skills/prdd-review/SKILL.md +6 -0
- package/templates/.claude/skills/prdd-review/prdd-skill-manifest.yaml +1 -0
- package/templates/.claude/skills/prdd-review/workflow.md +39 -0
- package/templates/.claude/skills/prdd-spec-from-prd/SKILL.md +6 -0
- package/templates/.claude/skills/prdd-spec-from-prd/prdd-skill-manifest.yaml +1 -0
- package/templates/.claude/skills/prdd-spec-from-prd/workflow.md +42 -0
- package/templates/.claude/skills/prdd-story-split/SKILL.md +6 -0
- package/templates/.claude/skills/prdd-story-split/prdd-skill-manifest.yaml +1 -0
- package/templates/.claude/skills/prdd-story-split/workflow.md +61 -0
- package/templates/_xiaoma-dd/CHEATSHEET.md +37 -0
- package/templates/_xiaoma-dd/README.md +10 -0
- package/templates/_xiaoma-dd/agents/architect.md +4 -0
- package/templates/_xiaoma-dd/agents/developer.md +4 -0
- package/templates/_xiaoma-dd/agents/pm.md +5 -0
- package/templates/_xiaoma-dd/config.yaml +14 -0
- package/templates/_xiaoma-dd/workflows/mvp-pipeline.md +9 -0
- package/templates/_xiaoma-dd-output/implementation-artifacts/README.md +3 -0
- package/templates/_xiaoma-dd-output/implementation-artifacts/change-log.md +7 -0
- package/templates/_xiaoma-dd-output/planning-artifacts/README.md +5 -0
- package/templates/_xiaoma-dd-output/planning-artifacts/stories/README.md +3 -0
- package/templates/_xiaoma-dd-output/project-context.md +21 -0
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# @wozhuzaisi/prdd-cli
|
|
2
|
+
|
|
3
|
+
面向 **Claude Code** 的轻量安装器:把 **PRD 驱动开发** 所需的目录约定与 **5 个 Skills** 写入目标仓库。
|
|
4
|
+
|
|
5
|
+
- **源码仓库:** [wozhuzaisi/xiaoma-dd](https://github.com/wozhuzaisi/xiaoma-dd)(可为私有,不影响 npm 安装)
|
|
6
|
+
|
|
7
|
+
## 功能
|
|
8
|
+
|
|
9
|
+
- `install`:落盘 `_xiaoma-dd/`、`_xiaoma-dd-output/`(合并拷贝,不覆盖已有 `project-context.md` 等)、`.claude/skills/prdd-*`,并可选在 `CLAUDE.md` 追加说明块。
|
|
10
|
+
- `status`:读取 `_xiaoma-dd/.prdd-install.json`。
|
|
11
|
+
- `uninstall`:移除 `_xiaoma-dd` 与 `prdd-*` Skills;`--remove-output` 可一并删除 `_xiaoma-dd-output`(慎用)。
|
|
12
|
+
|
|
13
|
+
## 要求
|
|
14
|
+
|
|
15
|
+
- Node.js **≥ 18**
|
|
16
|
+
|
|
17
|
+
## 使用
|
|
18
|
+
|
|
19
|
+
在项目根目录执行:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 开发阶段可直接使用本仓库内脚本
|
|
23
|
+
node Projects/06-XiaoMa-dd/prdd-cli/bin/prdd-cli.js install --yes
|
|
24
|
+
|
|
25
|
+
# 全局安装后
|
|
26
|
+
npm install -g @wozhuzaisi/prdd-cli
|
|
27
|
+
prdd-cli install --yes
|
|
28
|
+
|
|
29
|
+
# 或使用 npx(推荐先试)
|
|
30
|
+
npx @wozhuzaisi/prdd-cli install --yes
|
|
31
|
+
|
|
32
|
+
# 指定目录
|
|
33
|
+
prdd-cli install --yes --directory /path/to/repo
|
|
34
|
+
|
|
35
|
+
# 覆盖已有 _xiaoma-dd(会重写 Skills 与配置目录)
|
|
36
|
+
prdd-cli install --yes --force
|
|
37
|
+
|
|
38
|
+
# CI 可用环境变量代替 --yes
|
|
39
|
+
PRDD_CLI_YES=1 prdd-cli install
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 安装后的 Skills(Claude Code)
|
|
43
|
+
|
|
44
|
+
| Skill | 作用 |
|
|
45
|
+
|--------|------|
|
|
46
|
+
| `prdd-ingest` | PRD 校验、摘要、需求表与缺口清单 |
|
|
47
|
+
| `prdd-spec-from-prd` | 技术规格 `tech-spec-from-prd.md` |
|
|
48
|
+
| `prdd-story-split` | 拆分 `stories/story-*.md` 与 `story-index.md` |
|
|
49
|
+
| `prdd-implement-story` | 按 Story 实现代码与测试 |
|
|
50
|
+
| `prdd-review` | 对照 PRD/Story 的审查报告 |
|
|
51
|
+
|
|
52
|
+
详细口令与顺序见安装后的 **`_xiaoma-dd/CHEATSHEET.md`**。
|
|
53
|
+
|
|
54
|
+
## 示例 PRD
|
|
55
|
+
|
|
56
|
+
见本包 `examples/sample-PRD.md`,可复制到 `planning-artifacts/` 后跑 `prdd-ingest`。
|
|
57
|
+
|
|
58
|
+
## 协议
|
|
59
|
+
|
|
60
|
+
MIT
|
package/bin/prdd-cli.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
import {
|
|
6
|
+
installProject,
|
|
7
|
+
readManifest,
|
|
8
|
+
uninstallProject,
|
|
9
|
+
} from "../lib/install.js";
|
|
10
|
+
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
|
|
13
|
+
async function readPackageVersion() {
|
|
14
|
+
const pkg = path.join(__dirname, "..", "package.json");
|
|
15
|
+
const raw = await fs.readFile(pkg, "utf8");
|
|
16
|
+
return JSON.parse(raw).version;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parseArgs(argv) {
|
|
20
|
+
const args = { _: [], command: null };
|
|
21
|
+
const rest = [...argv];
|
|
22
|
+
while (rest.length) {
|
|
23
|
+
const a = rest.shift();
|
|
24
|
+
if (a === "--directory" || a === "-d") {
|
|
25
|
+
args.directory = rest.shift();
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (a === "--yes" || a === "-y") {
|
|
29
|
+
args.yes = true;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (a === "--force" || a === "-f") {
|
|
33
|
+
args.force = true;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (a === "--remove-output") {
|
|
37
|
+
args.removeOutput = true;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (a.startsWith("-")) {
|
|
41
|
+
args.unknown = a;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
args._.push(a);
|
|
45
|
+
}
|
|
46
|
+
args.command = args._[0] || null;
|
|
47
|
+
return args;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function main() {
|
|
51
|
+
const argv = process.argv.slice(2);
|
|
52
|
+
const args = parseArgs(argv);
|
|
53
|
+
const version = await readPackageVersion();
|
|
54
|
+
|
|
55
|
+
if (args._[0] === "version" || args._[0] === "-v" || args._[0] === "--version") {
|
|
56
|
+
console.log(version);
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const projectRoot = path.resolve(args.directory || process.cwd());
|
|
61
|
+
|
|
62
|
+
switch (args.command) {
|
|
63
|
+
case "install": {
|
|
64
|
+
if (!args.yes && !process.env.PRDD_CLI_YES) {
|
|
65
|
+
console.error(
|
|
66
|
+
"即将安装到:",
|
|
67
|
+
projectRoot,
|
|
68
|
+
"\n若确认无误,请使用: prdd-cli install --yes\n或设置环境变量 PRDD_CLI_YES=1"
|
|
69
|
+
);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
const r = await installProject({
|
|
73
|
+
projectRoot,
|
|
74
|
+
force: Boolean(args.force),
|
|
75
|
+
version,
|
|
76
|
+
});
|
|
77
|
+
console.log("prdd-cli 安装完成");
|
|
78
|
+
console.log("- 配置与文档:", r.destXiaoma);
|
|
79
|
+
console.log("- 产出物目录:", r.destOut);
|
|
80
|
+
console.log("- Claude Skills:", r.destClaudeSkills);
|
|
81
|
+
console.log("- 版本:", r.manifest.version);
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case "status": {
|
|
85
|
+
const m = await readManifest(projectRoot);
|
|
86
|
+
if (!m) {
|
|
87
|
+
console.log("未安装 prdd-cli(未找到 _xiaoma-dd/.prdd-install.json)");
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
console.log(JSON.stringify({ ...m, projectRoot }, null, 2));
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
case "uninstall": {
|
|
94
|
+
if (!args.yes && !process.env.PRDD_CLI_YES) {
|
|
95
|
+
console.error(
|
|
96
|
+
"将移除 _xiaoma-dd 与 prdd-* Skills" +
|
|
97
|
+
(args.removeOutput ? " 以及 _xiaoma-dd-output" : "") +
|
|
98
|
+
"。\n确认请使用: prdd-cli uninstall --yes" +
|
|
99
|
+
(args.removeOutput ? " --remove-output" : "")
|
|
100
|
+
);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
await uninstallProject({
|
|
104
|
+
projectRoot,
|
|
105
|
+
removeOutput: Boolean(args.removeOutput),
|
|
106
|
+
});
|
|
107
|
+
console.log("已卸载 prdd-cli 资产");
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
default:
|
|
111
|
+
console.log(`prdd-cli ${version}
|
|
112
|
+
|
|
113
|
+
用法:
|
|
114
|
+
prdd-cli install --yes [--directory <path>] [--force]
|
|
115
|
+
prdd-cli status [--directory <path>]
|
|
116
|
+
prdd-cli uninstall --yes [--remove-output] [--directory <path>]
|
|
117
|
+
|
|
118
|
+
环境变量:
|
|
119
|
+
PRDD_CLI_YES=1 等价于 --yes(便于 CI)
|
|
120
|
+
`);
|
|
121
|
+
process.exit(args.command ? 1 : 0);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
main().catch((err) => {
|
|
126
|
+
console.error(err.message || err);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# 示例 PRD:待办列表最小功能
|
|
2
|
+
|
|
3
|
+
**版本:** 0.1
|
|
4
|
+
**日期:** 2026-04-14
|
|
5
|
+
|
|
6
|
+
## 背景
|
|
7
|
+
|
|
8
|
+
为演示 prdd-cli 流程,实现一个仅本地的待办增删改查。
|
|
9
|
+
|
|
10
|
+
## 需求
|
|
11
|
+
|
|
12
|
+
| ID | 描述 | 优先级 | 验收标准 |
|
|
13
|
+
|----|------|--------|----------|
|
|
14
|
+
| REQ-001 | 用户可新增一条待办标题 | P0 | 提交后列表中可见该条 |
|
|
15
|
+
| REQ-002 | 用户可将待办标为完成 | P0 | 状态持久化,刷新后仍存在 |
|
|
16
|
+
| REQ-003 | 用户可删除一条待办 | P1 | 删除后不再出现 |
|
|
17
|
+
|
|
18
|
+
## 范围外
|
|
19
|
+
|
|
20
|
+
- 多用户、登录、云端同步。
|
|
21
|
+
|
|
22
|
+
## 非功能
|
|
23
|
+
|
|
24
|
+
- 单测覆盖核心逻辑;启动命令见 `project-context.md`。
|
package/lib/fs-utils.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export async function exists(p) {
|
|
5
|
+
try {
|
|
6
|
+
await fs.access(p);
|
|
7
|
+
return true;
|
|
8
|
+
} catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function ensureDir(dir) {
|
|
14
|
+
await fs.mkdir(dir, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function copyDir(src, dest, { filter } = {}) {
|
|
18
|
+
await ensureDir(dest);
|
|
19
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
20
|
+
for (const ent of entries) {
|
|
21
|
+
const from = path.join(src, ent.name);
|
|
22
|
+
const to = path.join(dest, ent.name);
|
|
23
|
+
const rel = path.relative(src, from);
|
|
24
|
+
if (filter && !filter(rel, ent)) continue;
|
|
25
|
+
if (ent.isDirectory()) {
|
|
26
|
+
await copyDir(from, to, { filter });
|
|
27
|
+
} else if (ent.isFile()) {
|
|
28
|
+
await ensureDir(path.dirname(to));
|
|
29
|
+
await fs.copyFile(from, to);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function readJsonSafe(file, fallback) {
|
|
35
|
+
try {
|
|
36
|
+
const raw = await fs.readFile(file, "utf8");
|
|
37
|
+
return JSON.parse(raw);
|
|
38
|
+
} catch {
|
|
39
|
+
return fallback;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function writeJson(file, obj) {
|
|
44
|
+
await ensureDir(path.dirname(file));
|
|
45
|
+
await fs.writeFile(file, `${JSON.stringify(obj, null, 2)}\n`, "utf8");
|
|
46
|
+
}
|
package/lib/index.js
ADDED
package/lib/install.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { copyDir, ensureDir, exists, readJsonSafe, writeJson } from "./fs-utils.js";
|
|
4
|
+
import { templatePath } from "./paths.js";
|
|
5
|
+
|
|
6
|
+
/** 合并拷贝:目标已存在且非 force 时跳过该文件 */
|
|
7
|
+
async function copyDirMergeSkipExisting(src, dest, { force }) {
|
|
8
|
+
await ensureDir(dest);
|
|
9
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
10
|
+
for (const ent of entries) {
|
|
11
|
+
const from = path.join(src, ent.name);
|
|
12
|
+
const to = path.join(dest, ent.name);
|
|
13
|
+
if (ent.isDirectory()) {
|
|
14
|
+
await copyDirMergeSkipExisting(from, to, { force });
|
|
15
|
+
} else if (ent.isFile()) {
|
|
16
|
+
if (!force && (await exists(to))) continue;
|
|
17
|
+
await ensureDir(path.dirname(to));
|
|
18
|
+
await fs.copyFile(from, to);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const MANIFEST_NAME = ".prdd-install.json";
|
|
24
|
+
const SKILL_PREFIX = "prdd-";
|
|
25
|
+
|
|
26
|
+
const BUNDLED_SKILLS = [
|
|
27
|
+
"prdd-ingest",
|
|
28
|
+
"prdd-spec-from-prd",
|
|
29
|
+
"prdd-story-split",
|
|
30
|
+
"prdd-implement-story",
|
|
31
|
+
"prdd-review",
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export function manifestPath(projectRoot) {
|
|
35
|
+
return path.join(projectRoot, "_xiaoma-dd", MANIFEST_NAME);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function readManifest(projectRoot) {
|
|
39
|
+
return readJsonSafe(manifestPath(projectRoot), null);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function backupIfExists(file) {
|
|
43
|
+
if (!(await exists(file))) return;
|
|
44
|
+
const bak = `${file}.bak`;
|
|
45
|
+
let n = 0;
|
|
46
|
+
let target = bak;
|
|
47
|
+
while (await exists(target)) {
|
|
48
|
+
n += 1;
|
|
49
|
+
target = `${file}.bak.${n}`;
|
|
50
|
+
}
|
|
51
|
+
await fs.rename(file, target);
|
|
52
|
+
return target;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {object} opts
|
|
57
|
+
* @param {string} opts.projectRoot - absolute path
|
|
58
|
+
* @param {boolean} opts.force - overwrite _xiaoma-dd non-merge files
|
|
59
|
+
* @param {string} opts.version - cli package version
|
|
60
|
+
*/
|
|
61
|
+
export async function installProject({ projectRoot, force, version }) {
|
|
62
|
+
const abs = path.resolve(projectRoot);
|
|
63
|
+
const tplXiaoma = templatePath("_xiaoma-dd");
|
|
64
|
+
const tplOut = templatePath("_xiaoma-dd-output");
|
|
65
|
+
const tplSkills = templatePath(".claude", "skills");
|
|
66
|
+
|
|
67
|
+
if (!(await exists(tplXiaoma))) {
|
|
68
|
+
throw new Error(`Missing templates: ${tplXiaoma}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const destXiaoma = path.join(abs, "_xiaoma-dd");
|
|
72
|
+
const destOut = path.join(abs, "_xiaoma-dd-output");
|
|
73
|
+
const destClaudeSkills = path.join(abs, ".claude", "skills");
|
|
74
|
+
|
|
75
|
+
if ((await exists(destXiaoma)) && !force) {
|
|
76
|
+
const m = await readManifest(abs);
|
|
77
|
+
if (m?.version) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`已有安装(版本 ${m.version})。使用 --force 覆盖 _xiaoma-dd,或先 prdd-cli uninstall`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
throw new Error(
|
|
83
|
+
"目录 _xiaoma-dd 已存在但无 prdd-cli 安装记录。请改用 --force 覆盖,或先手动处理该目录。"
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (force && (await exists(path.join(destXiaoma, MANIFEST_NAME)))) {
|
|
88
|
+
await backupIfExists(path.join(destXiaoma, MANIFEST_NAME));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
await ensureDir(destXiaoma);
|
|
92
|
+
await copyDir(tplXiaoma, destXiaoma);
|
|
93
|
+
|
|
94
|
+
await ensureDir(destOut);
|
|
95
|
+
await copyDirMergeSkipExisting(tplOut, destOut, { force });
|
|
96
|
+
|
|
97
|
+
await ensureDir(destClaudeSkills);
|
|
98
|
+
for (const name of BUNDLED_SKILLS) {
|
|
99
|
+
const src = path.join(tplSkills, name);
|
|
100
|
+
const dest = path.join(destClaudeSkills, name);
|
|
101
|
+
if (!(await exists(src))) continue;
|
|
102
|
+
if ((await exists(dest)) && !force) {
|
|
103
|
+
// merge: skip existing skill dir unless force
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (force && (await exists(dest))) {
|
|
107
|
+
await fs.rm(dest, { recursive: true });
|
|
108
|
+
}
|
|
109
|
+
await copyDir(src, dest);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const manifest = {
|
|
113
|
+
package: "prdd-cli",
|
|
114
|
+
version,
|
|
115
|
+
installedAt: new Date().toISOString(),
|
|
116
|
+
skills: BUNDLED_SKILLS,
|
|
117
|
+
outputRoot: "_xiaoma-dd-output",
|
|
118
|
+
};
|
|
119
|
+
await writeJson(path.join(destXiaoma, MANIFEST_NAME), manifest);
|
|
120
|
+
|
|
121
|
+
await writeClaudeSnippet(abs, force);
|
|
122
|
+
|
|
123
|
+
return { destXiaoma, destOut, destClaudeSkills, manifest };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function writeClaudeSnippet(projectRoot, force) {
|
|
127
|
+
const claudeMd = path.join(projectRoot, "CLAUDE.md");
|
|
128
|
+
const block = [
|
|
129
|
+
"",
|
|
130
|
+
"<!-- prdd-cli: begin -->",
|
|
131
|
+
"## PRD 驱动开发(prdd-cli)",
|
|
132
|
+
"",
|
|
133
|
+
"- 产出物根目录:`./_xiaoma-dd-output/`",
|
|
134
|
+
"- 速查:`./_xiaoma-dd/CHEATSHEET.md`",
|
|
135
|
+
"- 在 Claude Code 中使用 Skills:`prdd-ingest`、`prdd-spec-from-prd`、`prdd-story-split`、`prdd-implement-story`、`prdd-review`",
|
|
136
|
+
"<!-- prdd-cli: end -->",
|
|
137
|
+
"",
|
|
138
|
+
].join("\n");
|
|
139
|
+
|
|
140
|
+
if (!(await exists(claudeMd))) {
|
|
141
|
+
await fs.writeFile(
|
|
142
|
+
claudeMd,
|
|
143
|
+
`# 项目说明\n\n${block}`,
|
|
144
|
+
"utf8"
|
|
145
|
+
);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const cur = await fs.readFile(claudeMd, "utf8");
|
|
149
|
+
if (cur.includes("<!-- prdd-cli: begin -->")) {
|
|
150
|
+
if (!force) return;
|
|
151
|
+
const next = cur.replace(
|
|
152
|
+
/<!-- prdd-cli: begin -->[\s\S]*?<!-- prdd-cli: end -->/,
|
|
153
|
+
block.trim()
|
|
154
|
+
);
|
|
155
|
+
await fs.writeFile(claudeMd, next, "utf8");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
await fs.appendFile(claudeMd, block, "utf8");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function uninstallProject({ projectRoot, removeOutput }) {
|
|
162
|
+
const abs = path.resolve(projectRoot);
|
|
163
|
+
const m = await readManifest(abs);
|
|
164
|
+
if (!m?.version) {
|
|
165
|
+
throw new Error("未检测到 prdd-cli 安装记录(_xiaoma-dd/.prdd-install.json)");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const destClaudeSkills = path.join(abs, ".claude", "skills");
|
|
169
|
+
for (const name of m.skills || BUNDLED_SKILLS) {
|
|
170
|
+
if (!name.startsWith(SKILL_PREFIX)) continue;
|
|
171
|
+
const p = path.join(destClaudeSkills, name);
|
|
172
|
+
if (await exists(p)) {
|
|
173
|
+
await fs.rm(p, { recursive: true });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const xiaoma = path.join(abs, "_xiaoma-dd");
|
|
178
|
+
if (await exists(xiaoma)) {
|
|
179
|
+
await fs.rm(xiaoma, { recursive: true });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (removeOutput) {
|
|
183
|
+
const out = path.join(abs, "_xiaoma-dd-output");
|
|
184
|
+
if (await exists(out)) {
|
|
185
|
+
await fs.rm(out, { recursive: true });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
package/lib/paths.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
|
|
4
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
|
|
6
|
+
export function templatesRoot() {
|
|
7
|
+
return path.join(__dirname, "..", "templates");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function templatePath(...parts) {
|
|
11
|
+
return path.join(templatesRoot(), ...parts);
|
|
12
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wozhuzaisi/prdd-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Install PRD-driven Claude Code skills and project scaffolding (XiaoMa-dd style)",
|
|
6
|
+
"bin": {
|
|
7
|
+
"prdd-cli": "bin/prdd-cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "lib/index.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"lib",
|
|
13
|
+
"templates",
|
|
14
|
+
"examples"
|
|
15
|
+
],
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public",
|
|
18
|
+
"registry": "https://registry.npmjs.org/"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"claude-code",
|
|
25
|
+
"prd",
|
|
26
|
+
"skills",
|
|
27
|
+
"xiaoma"
|
|
28
|
+
],
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/wozhuzaisi/xiaoma-dd.git",
|
|
33
|
+
"directory": "prdd-cli"
|
|
34
|
+
},
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/wozhuzaisi/xiaoma-dd/issues"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/wozhuzaisi/xiaoma-dd#readme"
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
type: skill
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# prdd-implement-story:按 Story 实现
|
|
2
|
+
|
|
3
|
+
**目标:** 完成 Story 中全部 AC,并留下可核验的测试与记录。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 输入
|
|
8
|
+
|
|
9
|
+
- 目标 `planning-artifacts/stories/story-NNN-*.md`(若用户未指定,读取 `story-index.md` 中第一个「待开发」)。
|
|
10
|
+
- `tech-spec-from-prd.md` 与相关 PRD 片段(仅读与本次 Story 相关的部分)。
|
|
11
|
+
- `_xiaoma-dd-output/project-context.md`。
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 输出
|
|
16
|
+
|
|
17
|
+
1. **代码与测试**:按 `project-context` 的测试命令可运行通过。
|
|
18
|
+
2. **Story 文件更新**:在同一 Story 文件末尾「完成记录」中填写:
|
|
19
|
+
- 实现概要
|
|
20
|
+
- 变更文件列表
|
|
21
|
+
- 如何验证(命令)
|
|
22
|
+
- 将「元数据-状态」改为 `已完成`(或你们团队约定状态)
|
|
23
|
+
3. **implementation-artifacts**:追加或更新 `change-log.md`(若不存在则创建),记录一行:`日期 | Story ID | 摘要 | 关联 REQ`。
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 执行步骤
|
|
28
|
+
|
|
29
|
+
1. 阅读 Story 的 AC 与依赖;若依赖未满足,先提示用户或先实现依赖 Story。
|
|
30
|
+
2. 在仓库中定位修改点;优先复用现有模式与工具函数。
|
|
31
|
+
3. 编写最小必要测试(单元/集成按项目惯例)。
|
|
32
|
+
4. 本地运行测试与 lint(若 project-context 有说明)。
|
|
33
|
+
5. 更新 Story 与 `change-log.md`,并给用户简短总结与下一步(下一 Story 或 `prdd-review`)。
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 约束
|
|
38
|
+
|
|
39
|
+
- 不扩展 Story 范围;若发现必须扩大范围,在 Story 中记录「范围变更建议」并暂停自动扩展,提示用户走规格/PRD 更新流程。
|
|
40
|
+
- 所有行为应可追溯至 Story 的 REQ / AC。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
type: skill
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# prdd-ingest:PRD 摄入与校验
|
|
2
|
+
|
|
3
|
+
**目标:** 将 PRD 结构化为可开发输入,并暴露缺口。
|
|
4
|
+
|
|
5
|
+
**语言:** 与用户对话使用简体中文;落盘文档默认简体中文(若 `project-context` 另有约定则从其约定)。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 输入
|
|
10
|
+
|
|
11
|
+
- 用户给出的 **PRD 文件路径**(相对仓库根或绝对路径);若未给出,在仓库内按 `_xiaoma-dd/config.yaml` 的 `prd_glob` 搜索并列出候选,请用户确认一个。
|
|
12
|
+
- 可选:`_xiaoma-dd-output/project-context.md`(棕地约束)。
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 输出(写入 `_xiaoma-dd-output/planning-artifacts/`)
|
|
17
|
+
|
|
18
|
+
1. `prd-summary.md`
|
|
19
|
+
- 元信息:PRD 路径、版本/日期(若原文有)、负责人
|
|
20
|
+
- 一页内 **执行摘要**(目标用户、问题、范围 In/Out、里程碑)
|
|
21
|
+
- **需求表**:列包含 `需求 ID`(稳定可引用,如 REQ-001)、描述、优先级、验收要点、对应 PRD 章节锚点
|
|
22
|
+
|
|
23
|
+
2. `prd-gaps.md`
|
|
24
|
+
- **缺口清单**:模糊描述、缺失验收标准、未定义边界、依赖与风险未澄清项
|
|
25
|
+
- 每条缺口标注建议动作(补 PRD / 当面确认 / 技术假设)
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 执行步骤
|
|
30
|
+
|
|
31
|
+
1. 完整阅读 PRD 原文(必要时拆分为多段读取,避免遗漏章节)。
|
|
32
|
+
2. 提取或分配 **需求 ID**;若 PRD 已有编号,沿用并映射到 REQ 表。
|
|
33
|
+
3. 对照「可开发」检查:是否有明确验收标准、数据字段、接口语义、异常与权限;记录到 `prd-gaps.md`。
|
|
34
|
+
4. 若 **阻塞性缺口** 过多,在对话中置顶说明:建议先修订 PRD,再继续 `prdd-spec-from-prd`。
|
|
35
|
+
5. 写文件前确认目录存在;写完后用简短摘要回复用户下一步建议(通常是补缺口或进入 `prdd-spec-from-prd`)。
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 约束
|
|
40
|
+
|
|
41
|
+
- 不编造 PRD 中不存在的需求;不确定处写入 gaps,不得静默猜测为事实。
|
|
42
|
+
- 所有摘要条目尽量可追溯至 PRD 章节标题或锚点。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
type: skill
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# prdd-review:PRD / Story 对齐审查
|
|
2
|
+
|
|
3
|
+
**目标:** 发现实现与 PRD、技术规格、Story AC 的偏离,以及明显的质量与风险问题。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 输入
|
|
8
|
+
|
|
9
|
+
- 当前分支相对主干的 **diff**(或用户指定的文件列表 / Story ID)。
|
|
10
|
+
- `planning-artifacts/prd-summary.md`、`tech-spec-from-prd.md`。
|
|
11
|
+
- 已声称完成的 `stories/story-NNN-*.md`。
|
|
12
|
+
- 可选:`prd-gaps.md` 中未关闭项。
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 输出
|
|
17
|
+
|
|
18
|
+
在 `_xiaoma-dd-output/implementation-artifacts/` 写入 `review-report-YYYYMMDD.md`(若用户指定名称则从其),包含:
|
|
19
|
+
|
|
20
|
+
1. **范围与对象**:审查了哪些 Story / REQ、哪些文件。
|
|
21
|
+
2. **对齐检查表**:列 `REQ 或 AC | 证据(文件/函数) | 结论(满足/部分/不满足) | 说明`。
|
|
22
|
+
3. **风险**:安全、兼容、性能、可维护性。
|
|
23
|
+
4. **行动项**:需补测试、需改文档、需回退设计等,带优先级。
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 执行步骤
|
|
28
|
+
|
|
29
|
+
1. 从 Story 的 AC 出发,逐项在代码与测试中找证据;没有证据则标为不满足或风险。
|
|
30
|
+
2. 对照 `tech-spec-from-prd` 的追溯矩阵,检查是否有 REQ 完全未覆盖。
|
|
31
|
+
3. 不重复实现功能;仅审查与记录。
|
|
32
|
+
4. 给出可执行的修复顺序建议。
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 约束
|
|
37
|
+
|
|
38
|
+
- 不以「代码看起来没问题」代替对 AC 的逐条核验。
|
|
39
|
+
- 对假设与缺口使用明确措辞(Assumption / Gap),避免臆断业务含义。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
type: skill
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# prdd-spec-from-prd:从 PRD 生成技术规格
|
|
2
|
+
|
|
3
|
+
**目标:** 把需求翻译成可实现、可测试的技术设计,并与 PRD 需求 ID 对齐。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 输入
|
|
8
|
+
|
|
9
|
+
- `planning-artifacts/prd-summary.md`(必需)与原始 PRD 文件。
|
|
10
|
+
- `planning-artifacts/prd-gaps.md`(若存在):对仍开放的假设显式标注 **Assumption**。
|
|
11
|
+
- `_xiaoma-dd-output/project-context.md`:技术栈与仓库约定。
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 输出
|
|
16
|
+
|
|
17
|
+
在 `_xiaoma-dd-output/planning-artifacts/` 写入:
|
|
18
|
+
|
|
19
|
+
- `tech-spec-from-prd.md`,建议包含:
|
|
20
|
+
- **范围与目标**(对应 REQ ID 列表)
|
|
21
|
+
- **架构与模块边界**(简图或条目即可)
|
|
22
|
+
- **数据模型 / 状态机**(若适用)
|
|
23
|
+
- **API 或模块接口**(签名、错误码、幂等、分页等)
|
|
24
|
+
- **非功能**:性能、安全、观测、兼容
|
|
25
|
+
- **追溯矩阵**:表格列 `REQ ID | 设计章节 | 备注`
|
|
26
|
+
- **开放问题**:引用 `prd-gaps` 中未关闭项
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 执行步骤
|
|
31
|
+
|
|
32
|
+
1. 阅读 `prd-summary.md` 与 PRD 相关章节,列出本迭代必须满足的 REQ。
|
|
33
|
+
2. 结合 `project-context` 选择与本仓库一致的模式(目录、命名、测试策略)。
|
|
34
|
+
3. 将每个 REQ 映射到具体设计条目;无法映射的 REQ 写入开放问题。
|
|
35
|
+
4. 完成后提示用户运行 `prdd-story-split` 进行任务拆分。
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 约束
|
|
40
|
+
|
|
41
|
+
- 规格中每条关键决策应能引用 **REQ ID** 或明确标记为假设。
|
|
42
|
+
- 避免与 `project-context` 中的禁止事项冲突。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
type: skill
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# prdd-story-split:Story 拆分
|
|
2
|
+
|
|
3
|
+
**目标:** 生成小而可验收的 Story 文件,便于逐条实现与审查。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 输入
|
|
8
|
+
|
|
9
|
+
- `planning-artifacts/tech-spec-from-prd.md`(必需)
|
|
10
|
+
- `planning-artifacts/prd-summary.md`
|
|
11
|
+
- `_xiaoma-dd-output/project-context.md`
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 输出
|
|
16
|
+
|
|
17
|
+
在 `_xiaoma-dd-output/planning-artifacts/stories/` 为每个 Story 创建一个文件,命名:`story-NNN-短标题.md`(NNN 为三位序号)。
|
|
18
|
+
|
|
19
|
+
每个 Story 文件模板结构如下(保持标题层级一致便于解析):
|
|
20
|
+
|
|
21
|
+
```markdown
|
|
22
|
+
# Story: <标题>
|
|
23
|
+
|
|
24
|
+
## 元数据
|
|
25
|
+
- Story ID: STORY-NNN
|
|
26
|
+
- 状态: 待开发
|
|
27
|
+
- 关联 REQ: REQ-xxx, REQ-yyy
|
|
28
|
+
- 依赖: STORY-...(无则写 无)
|
|
29
|
+
|
|
30
|
+
## 背景
|
|
31
|
+
(1 段)
|
|
32
|
+
|
|
33
|
+
## 验收标准(AC)
|
|
34
|
+
1. ...
|
|
35
|
+
2. ...
|
|
36
|
+
|
|
37
|
+
## 实现说明
|
|
38
|
+
- 涉及路径/模块建议:
|
|
39
|
+
- 测试要求:
|
|
40
|
+
|
|
41
|
+
## 完成记录
|
|
42
|
+
(实现阶段由 prdd-implement-story 勾选/填写)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
另外更新或创建 `planning-artifacts/story-index.md`:表格列 `Story ID | 标题 | REQ | 状态 | 文件路径`。
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 执行步骤
|
|
50
|
+
|
|
51
|
+
1. 根据 tech-spec 划分子任务,确保每个 Story 可在 1 个会话或 1 个 PR 内完成(尽量小)。
|
|
52
|
+
2. 为每个 Story 写清 **可验证** 的 AC(避免「优化体验」类模糊句)。
|
|
53
|
+
3. 标注依赖顺序(阻塞关系)。
|
|
54
|
+
4. 完成后建议用户从 STORY-001 起使用 `prdd-implement-story`。
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 约束
|
|
59
|
+
|
|
60
|
+
- 每个 Story 必须关联至少一个 **REQ ID**。
|
|
61
|
+
- 不在 Story 中隐藏架构级变更;若需架构调整,应回到 tech-spec 或单独 Story。
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# prdd-cli · Claude Code 速查
|
|
2
|
+
|
|
3
|
+
## 前置
|
|
4
|
+
|
|
5
|
+
1. 将 **PRD** 放入仓库(任意路径,建议在 `_xiaoma-dd-output/planning-artifacts/` 或你的文档目录)。
|
|
6
|
+
2. 棕地项目请先填写 `_xiaoma-dd-output/project-context.md`(技术栈、目录约定、测试方式)。
|
|
7
|
+
3. 在 Claude Code 中确保已加载本项目的 **Skills**(`.claude/skills/prdd-*`)。
|
|
8
|
+
|
|
9
|
+
## 推荐顺序(MVP)
|
|
10
|
+
|
|
11
|
+
| 顺序 | Skill | 作用 |
|
|
12
|
+
|------|---------|------|
|
|
13
|
+
| 1 | `prdd-ingest` | 读 PRD,输出校验报告 + 摘要 + 需求 ID 表 |
|
|
14
|
+
| 2 | `prdd-spec-from-prd` | 在 PRD 与技术上下文基础上写 **技术规格** |
|
|
15
|
+
| 3 | `prdd-story-split` | 拆 **Story** 文件(含验收标准与 PRD 追溯) |
|
|
16
|
+
| 4 | `prdd-implement-story` | 按单个 Story 改代码与测试 |
|
|
17
|
+
| 5 | `prdd-review` | 对照 PRD / Story 做实现审查 |
|
|
18
|
+
|
|
19
|
+
## 在对话里怎么说
|
|
20
|
+
|
|
21
|
+
自然语言示例(Claude 会按 Skill 描述匹配):
|
|
22
|
+
|
|
23
|
+
- 「用 prdd-ingest 处理 `xxx/PRD.md`」
|
|
24
|
+
- 「根据 PRD 写技术规格」→ `prdd-spec-from-prd`
|
|
25
|
+
- 「把规格拆成多个 Story」→ `prdd-story-split`
|
|
26
|
+
- 「实现 `story-001.md` 这个 Story」→ `prdd-implement-story`
|
|
27
|
+
- 「审查当前改动是否符合 PRD」→ `prdd-review`
|
|
28
|
+
|
|
29
|
+
## 产出物路径(默认)
|
|
30
|
+
|
|
31
|
+
- 规划:`./_xiaoma-dd-output/planning-artifacts/`
|
|
32
|
+
- Story:`./_xiaoma-dd-output/planning-artifacts/stories/`
|
|
33
|
+
- 实施记录:`./_xiaoma-dd-output/implementation-artifacts/`
|
|
34
|
+
|
|
35
|
+
## 与通用 XiaoMa 技能的关系
|
|
36
|
+
|
|
37
|
+
若本仓库同时安装了其他 `xiaoma-*` Skills,可复用例如 `xiaoma-generate-project-context` 来生成/更新 `project-context.md`,再执行 prdd 流程。
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# XiaoMa-dd / prdd-cli 资产说明
|
|
2
|
+
|
|
3
|
+
本目录由 **prdd-cli** 安装生成,包含:
|
|
4
|
+
|
|
5
|
+
- `config.yaml`:路径与语言约定。
|
|
6
|
+
- `CHEATSHEET.md`:在 Claude Code 中应优先打开的速查表。
|
|
7
|
+
- `agents/`:可选的角色提示(轻量);执行时以 **Skills** 为主。
|
|
8
|
+
- `workflows/`:给人看的阶段说明(非自动执行脚本)。
|
|
9
|
+
|
|
10
|
+
产出物统一写入仓库根目录的 `_xiaoma-dd-output/`,与业务源码分离。
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# prdd-cli 项目侧配置(安装器落盘,可按需修改)
|
|
2
|
+
project_name: "${PROJECT_NAME}"
|
|
3
|
+
output_root: "_xiaoma-dd-output"
|
|
4
|
+
communication_language: "zh-CN"
|
|
5
|
+
document_output_language: "zh-CN"
|
|
6
|
+
|
|
7
|
+
# PRD 文件默认搜索 glob(相对仓库根)
|
|
8
|
+
prd_glob: "**/*PRD*.md"
|
|
9
|
+
|
|
10
|
+
paths:
|
|
11
|
+
planning: "_xiaoma-dd-output/planning-artifacts"
|
|
12
|
+
stories: "_xiaoma-dd-output/planning-artifacts/stories"
|
|
13
|
+
implementation: "_xiaoma-dd-output/implementation-artifacts"
|
|
14
|
+
project_context: "_xiaoma-dd-output/project-context.md"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# MVP 流水线(人工/对话驱动)
|
|
2
|
+
|
|
3
|
+
1. **ingest**:确认 PRD 完整、可测、有优先级与范围边界。
|
|
4
|
+
2. **spec**:把需求翻译成技术方案(接口、数据模型、错误码、非功能约束)。
|
|
5
|
+
3. **stories**:每个 Story 小而可验收,并带 PRD 追溯字段。
|
|
6
|
+
4. **implement**:一次一个 Story,提交前自检(测试、lint)。
|
|
7
|
+
5. **review**:对照 PRD 与 Story 的 AC,检查遗漏与过度实现。
|
|
8
|
+
|
|
9
|
+
每步应在 `_xiaoma-dd-output/` 中留下可追溯文件,便于复盘与多会话接力。
|