@openprd/cli 0.1.1 → 0.1.9
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/.openprd/README.md +43 -69
- package/.openprd/README_EN.md +84 -0
- package/.openprd/benchmarks/index.md +7 -0
- package/.openprd/benchmarks/sources.yaml +25 -3
- package/.openprd/discovery/config.json +16 -2
- package/.openprd/engagements/active/flows.md +19 -14
- package/.openprd/engagements/active/handoff.md +11 -4
- package/.openprd/engagements/active/prd.md +99 -71
- package/.openprd/engagements/active/review.html +4 -4
- package/.openprd/engagements/active/roles.md +9 -8
- package/.openprd/engagements/work-units/wu-20260524015648-6d33ded7.json +4 -4
- package/.openprd/engagements/work-units/wu-20260602113956-a99b5b88.json +18 -0
- package/.openprd/engagements/work-units/wu-20260602122244-78656aaf.json +18 -0
- package/.openprd/engagements/work-units/wu-20260602122442-e96489e2.json +18 -0
- package/.openprd/engagements/work-units/wu-20260602132835-695429e8.json +18 -0
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/candidate.json +78 -0
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/diagnostic-report.json +129 -0
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/root-cause-candidates.json +41 -0
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/timeline.json +14 -0
- package/.openprd/knowledge/drafts/openprd-experience-diagnostic-candidate-turn-1780116203372-5f266a79e968c758/SKILL.md +49 -0
- package/.openprd/knowledge/index.json +44 -4
- package/.openprd/reviews/v0001.html +195 -129
- package/.openprd/reviews/v0002.html +1150 -0
- package/.openprd/reviews/v0003.html +1150 -0
- package/.openprd/reviews/v0004.html +1150 -0
- package/.openprd/reviews/v0005.html +1150 -0
- package/.openprd/standards/config.json +12 -9
- package/.openprd/state/changes.json +17 -2
- package/.openprd/state/current.json +399 -63
- package/.openprd/state/release-ledger.json +387 -0
- package/.openprd/state/version-index.json +52 -0
- package/.openprd/state/versions/v0002.json +264 -0
- package/.openprd/state/versions/v0002.md +183 -0
- package/.openprd/state/versions/v0003.json +269 -0
- package/.openprd/state/versions/v0003.md +188 -0
- package/.openprd/state/versions/v0004.json +274 -0
- package/.openprd/state/versions/v0004.md +193 -0
- package/.openprd/state/versions/v0005.json +299 -0
- package/.openprd/state/versions/v0005.md +189 -0
- package/.openprd/templates/agent/intake.md +5 -4
- package/.openprd/templates/b2b/intake.md +5 -4
- package/.openprd/templates/base/intake.md +10 -4
- package/.openprd/templates/company/README.md +9 -7
- package/.openprd/templates/company/README_EN.md +12 -0
- package/.openprd/templates/consumer/intake.md +5 -4
- package/.openprd/templates/industry/README.md +12 -10
- package/.openprd/templates/industry/README_EN.md +18 -0
- package/.openprd/templates/project/README.md +11 -9
- package/.openprd/templates/project/README_EN.md +16 -0
- package/.openprd/templates/session/README.md +11 -9
- package/.openprd/templates/session/README_EN.md +16 -0
- package/AGENTS.md +12 -8
- package/README.md +419 -438
- package/README_CN.md +4 -578
- package/README_EN.md +870 -0
- package/docs/assets/openprd-requirement-routing-en.png +0 -0
- package/docs/assets/openprd-requirement-routing-en.svg +102 -0
- package/docs/assets/openprd-requirement-routing-zh-refined.png +0 -0
- package/docs/assets/openprd-requirement-routing-zh.png +0 -0
- package/docs/assets/openprd-requirement-routing-zh.svg +102 -0
- package/package.json +6 -2
- package/scripts/dev-check-wrapup-copy.mjs +110 -0
- package/scripts/openprd-github-release-notes.mjs +99 -0
- package/scripts/quality-perf-check.mjs +203 -0
- package/skills/openprd-benchmark-router/SKILL.md +1 -0
- package/skills/openprd-benchmark-router/references/benchmark-sources.md +1 -0
- package/skills/openprd-benchmark-router/references/source-policy.md +2 -0
- package/skills/openprd-discovery-loop/SKILL.md +2 -2
- package/skills/openprd-harness/SKILL.md +47 -25
- package/skills/openprd-harness/references/workflow-gates.md +15 -0
- package/skills/openprd-quality/SKILL.md +11 -5
- package/skills/openprd-requirement-intake/SKILL.md +31 -20
- package/skills/openprd-requirement-intake/references/prd-template-lenses.md +6 -6
- package/skills/openprd-requirement-intake/references/routing-rubric.md +10 -2
- package/skills/openprd-router/SKILL.md +2 -2
- package/skills/openprd-shared/SKILL.md +51 -23
- package/skills/openprd-standards/SKILL.md +2 -1
- package/src/agent-integration.js +271 -71
- package/src/benchmark/constants.js +107 -0
- package/src/benchmark/operations.js +235 -0
- package/src/benchmark/registry.js +64 -0
- package/src/benchmark/render.js +115 -0
- package/src/benchmark/source.js +617 -0
- package/src/benchmark/storage.js +121 -0
- package/src/benchmark/verify.js +235 -0
- package/src/benchmark.js +50 -851
- package/src/change-summary.js +339 -0
- package/src/cli/args.js +67 -6
- package/src/cli/basic-print.js +365 -0
- package/src/cli/benchmark-print.js +91 -0
- package/src/cli/change-print.js +221 -0
- package/src/cli/doctor-print.js +268 -0
- package/src/cli/growth-print.js +176 -0
- package/src/cli/print.js +73 -1384
- package/src/cli/quality-print.js +284 -0
- package/src/cli/run-print.js +297 -0
- package/src/cli/shared-print.js +127 -0
- package/src/cli/workflow-print.js +195 -0
- package/src/codex-hook-runner-template.mjs +659 -124
- package/src/codex-runtime.js +324 -0
- package/src/dev-standards.js +178 -5
- package/src/diagram-core.js +5 -5
- package/src/discovery.js +2 -1
- package/src/execution-strategy.js +369 -0
- package/src/fleet.js +4 -0
- package/src/github-release.js +156 -0
- package/src/growth.js +311 -13
- package/src/html-artifact-utils.js +25 -0
- package/src/html-artifacts.js +157 -1596
- package/src/knowledge.js +1321 -76
- package/src/language-policy.js +2 -112
- package/src/learning-html-artifact.js +1031 -0
- package/src/learning-review.js +3 -2
- package/src/loop.js +280 -9
- package/src/openprd.js +341 -38
- package/src/openspec/change-validate.js +0 -9
- package/src/openspec/execute.js +79 -3
- package/src/openspec/generate.js +33 -20
- package/src/openspec/tasks.js +33 -2
- package/src/prd-core.js +10 -9
- package/src/product-type-copy.js +69 -0
- package/src/quality-html-artifact.js +108 -9
- package/src/quality-learning.js +30 -0
- package/src/quality-visual-review.js +237 -0
- package/src/quality.js +329 -43
- package/src/registry-hygiene.js +54 -0
- package/src/release-ledger.js +413 -0
- package/src/review-presentation.js +12 -6
- package/src/run-harness.js +722 -48
- package/src/session-binding.js +40 -3
- package/src/session-registry.js +159 -0
- package/src/standards.js +5 -3
- package/src/test-strategy.js +386 -0
- package/src/visual-compare.js +915 -34
- package/src/work-unit-migration.js +5 -1
- package/src/workspace-core.js +343 -19
- package/src/workspace-workflow.js +538 -134
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 核心功能
|
|
3
|
+
* 统一生成面向用户的变化摘要、loop commit 默认文案和 handoff 版本说明条目。
|
|
4
|
+
*
|
|
5
|
+
* 输入
|
|
6
|
+
* 接收任务文本、PRD 快照字段或评审摘要原文。
|
|
7
|
+
*
|
|
8
|
+
* 输出
|
|
9
|
+
* 返回动作词、短摘要、详细说明,以及 commit / handoff / review 可复用的格式化结果。
|
|
10
|
+
*
|
|
11
|
+
* 定位
|
|
12
|
+
* 位于 OpenPrd 用户可见变化表达层,连接 loop、handoff 和 review 文案约定。
|
|
13
|
+
*
|
|
14
|
+
* 依赖
|
|
15
|
+
* 仅依赖基础字符串处理,不依赖 workspace 或渲染上下文。
|
|
16
|
+
*
|
|
17
|
+
* 维护规则
|
|
18
|
+
* 动作词和摘要规则要保持用户视角,避免把内部流程词和实现细节直接当成最终文案。
|
|
19
|
+
*/
|
|
20
|
+
export const CHANGE_SUMMARY_VERBS = ['新增', '修复', '优化', '调整', '移除'];
|
|
21
|
+
|
|
22
|
+
export const USER_CHANGE_SUMMARY_GUIDE = {
|
|
23
|
+
perspective: '从用户可感知变化出发,优先写用户现在能做什么、会看到什么、哪个问题被修好。',
|
|
24
|
+
preferredVerbs: CHANGE_SUMMARY_VERBS,
|
|
25
|
+
panelExamples: {
|
|
26
|
+
flow: { summary: '新增入口', detail: '用户现在可以直接进入对应流程,不用再自己找路径。' },
|
|
27
|
+
function: { summary: '优化说明', detail: '用户先看到新增、修复、优化这类短摘要,再决定是否继续细读。' },
|
|
28
|
+
guardrail: { summary: '调整边界', detail: '只保留用户需要知道的限制、影响和下一步。' },
|
|
29
|
+
risk: { summary: '修复误判', detail: '避免把实现授权写成再次索取确认。' },
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const CHANGE_TYPE_RULES = [
|
|
34
|
+
{ type: '修复', pattern: /修复|修正|解决|避免|恢复|补齐|兼容|纠正|排查|报错|失败|异常|错误|误判|崩溃|缺失|遗漏|bug|fix/i },
|
|
35
|
+
{ type: '移除', pattern: /移除|删除|下线|废弃|停用|去掉|清理|剔除/u },
|
|
36
|
+
{ type: '新增', pattern: /新增|添加|加入|支持|提供|创建|引入|接入|生成|开放|允许|启用/u },
|
|
37
|
+
{ type: '优化', pattern: /优化|改进|提升|增强|简化|提炼|统一|压缩|对齐/u },
|
|
38
|
+
{ type: '调整', pattern: /调整|更新|改为|切换|重命名|改动|变更|重构|迁移/u },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const SUMMARY_PREFIX_PATTERNS = [
|
|
42
|
+
/^[A-Za-z][A-Za-z0-9._/-]*\s+/u,
|
|
43
|
+
/^(支持|提供|允许|实现|用于|帮助|让用户|让团队|让执行方|默认|现在|可以|需要|先|再)/u,
|
|
44
|
+
/^(用户|团队|协作者)(现在)?可以/u,
|
|
45
|
+
/^(导出的|默认|当前|新的|本次|这次|这个)/u,
|
|
46
|
+
/^(新增|修复|优化|调整|移除|删除|改进|更新|统一|改为|切换)/u,
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const TECHNICAL_SUMMARY_PREFIX = /^(commit|review|handoff|loop|task|spec|release notes)\b/i;
|
|
50
|
+
|
|
51
|
+
function normalizedText(value) {
|
|
52
|
+
return String(value ?? '')
|
|
53
|
+
.replace(/^\s*-\s*\[[ xX]\]\s*/u, '')
|
|
54
|
+
.replace(/^\s*[-*]\s*/u, '')
|
|
55
|
+
.replace(/\s+/g, ' ')
|
|
56
|
+
.trim();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function textLength(value) {
|
|
60
|
+
return Array.from(normalizedText(value)).length;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function stripTrailingPunctuation(value) {
|
|
64
|
+
return normalizedText(value).replace(/[。.!?!?;;,,、::\-]+$/u, '').trim();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function stripLeadingChangePrefix(value, verbs = CHANGE_SUMMARY_VERBS) {
|
|
68
|
+
const text = normalizedText(value);
|
|
69
|
+
for (const verb of [...verbs, '删除']) {
|
|
70
|
+
const stripped = text.replace(new RegExp(`^${verb}[::、,,\\s-]*`, 'u'), '').trim();
|
|
71
|
+
if (stripped && stripped !== text) {
|
|
72
|
+
return stripped;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return text;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function firstClause(value) {
|
|
79
|
+
const text = stripTrailingPunctuation(value);
|
|
80
|
+
return text
|
|
81
|
+
.split(/[\n。!?!?;;,,、]/u)
|
|
82
|
+
.map((item) => item.trim())
|
|
83
|
+
.find(Boolean) ?? text;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function splitClauses(value) {
|
|
87
|
+
const text = stripTrailingPunctuation(value);
|
|
88
|
+
return text
|
|
89
|
+
.split(/[\n。!?!?;;,,、]/u)
|
|
90
|
+
.map((item) => item.trim())
|
|
91
|
+
.filter(Boolean);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function shortenText(value, maxLength) {
|
|
95
|
+
const chars = Array.from(stripTrailingPunctuation(value));
|
|
96
|
+
if (chars.length <= maxLength) return chars.join('');
|
|
97
|
+
return chars.slice(0, maxLength).join('').replace(/[的了和并且及等、,,::\-]+$/u, '').trim();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function stripSummaryNoise(value) {
|
|
101
|
+
let text = firstClause(value);
|
|
102
|
+
let changed = true;
|
|
103
|
+
while (changed) {
|
|
104
|
+
changed = false;
|
|
105
|
+
for (const pattern of SUMMARY_PREFIX_PATTERNS) {
|
|
106
|
+
const next = text.replace(pattern, '').trim();
|
|
107
|
+
if (next && next !== text && textLength(next) >= 2) {
|
|
108
|
+
text = next;
|
|
109
|
+
changed = true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return text;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function pickSummaryClause(value) {
|
|
117
|
+
const clauses = splitClauses(value).map((item) => stripSummaryNoise(item)).filter(Boolean);
|
|
118
|
+
if (clauses.length === 0) {
|
|
119
|
+
return stripSummaryNoise(value) || firstClause(value);
|
|
120
|
+
}
|
|
121
|
+
if (clauses.length > 1 && TECHNICAL_SUMMARY_PREFIX.test(clauses[0])) {
|
|
122
|
+
return clauses[1];
|
|
123
|
+
}
|
|
124
|
+
if (clauses.length > 1 && /^[A-Za-z][A-Za-z0-9 ._/-]+$/u.test(clauses[0])) {
|
|
125
|
+
return clauses[1];
|
|
126
|
+
}
|
|
127
|
+
return clauses[0];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function parseStructuredChange(value) {
|
|
131
|
+
const text = normalizedText(value);
|
|
132
|
+
const markdown = text.match(/^\*\*([^*]+)\*\*\s*[::]\s*(.+)$/u);
|
|
133
|
+
if (markdown) {
|
|
134
|
+
return {
|
|
135
|
+
summary: normalizedText(markdown[1]),
|
|
136
|
+
detail: normalizedText(markdown[2]),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const plain = text.match(/^([^::]{2,20})[::]\s*(.+)$/u);
|
|
140
|
+
if (plain) {
|
|
141
|
+
return {
|
|
142
|
+
summary: normalizedText(plain[1]),
|
|
143
|
+
detail: normalizedText(plain[2]),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function defaultSummaryFromDetail(detail, type, maxLength) {
|
|
150
|
+
const available = Math.max(2, maxLength - Array.from(type).length);
|
|
151
|
+
const clause = pickSummaryClause(detail);
|
|
152
|
+
const label = shortenText(clause, available);
|
|
153
|
+
if (!label) return type;
|
|
154
|
+
return shortenText(`${type}${label}`, maxLength);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function detectChangeVerb(value, fallbackType = '调整') {
|
|
158
|
+
const text = normalizedText(value);
|
|
159
|
+
const matchText = text.replace(/^[A-Za-z][A-Za-z0-9._/-]*\s+/u, '');
|
|
160
|
+
for (const verb of CHANGE_SUMMARY_VERBS) {
|
|
161
|
+
if (matchText.startsWith(verb)) {
|
|
162
|
+
return verb;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
for (const rule of CHANGE_TYPE_RULES) {
|
|
166
|
+
const match = matchText.match(rule.pattern);
|
|
167
|
+
if (match && typeof match.index === 'number' && match.index <= 4) {
|
|
168
|
+
return rule.type;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return fallbackType;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function buildChangeEntry(value, options = {}) {
|
|
175
|
+
const text = normalizedText(value);
|
|
176
|
+
if (!text) return null;
|
|
177
|
+
|
|
178
|
+
const {
|
|
179
|
+
fallbackType = '调整',
|
|
180
|
+
summaryMaxLength = 15,
|
|
181
|
+
detailMaxLength = null,
|
|
182
|
+
} = options;
|
|
183
|
+
|
|
184
|
+
const structured = parseStructuredChange(text);
|
|
185
|
+
const type = detectChangeVerb(structured?.summary ?? structured?.detail ?? text, fallbackType);
|
|
186
|
+
const rawDetail = structured?.detail ?? stripLeadingChangePrefix(text);
|
|
187
|
+
const detailBase = rawDetail || text;
|
|
188
|
+
const detail = detailMaxLength ? shortenText(detailBase, detailMaxLength) : stripTrailingPunctuation(detailBase);
|
|
189
|
+
|
|
190
|
+
let summary = structured?.summary ?? defaultSummaryFromDetail(detail, type, summaryMaxLength);
|
|
191
|
+
if (textLength(summary) > summaryMaxLength) {
|
|
192
|
+
summary = defaultSummaryFromDetail(detail, type, summaryMaxLength);
|
|
193
|
+
}
|
|
194
|
+
if (!summary.startsWith(type)) {
|
|
195
|
+
summary = defaultSummaryFromDetail(detail, type, summaryMaxLength);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
type,
|
|
200
|
+
summary,
|
|
201
|
+
detail,
|
|
202
|
+
sentence: `${type}:${detail}`,
|
|
203
|
+
panel: `**${summary}**:${detail}`,
|
|
204
|
+
bullet: `- ${type}:${detail}`,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function buildChangeEntries(values, options = {}) {
|
|
209
|
+
const items = Array.isArray(values) ? values : [values];
|
|
210
|
+
const entries = [];
|
|
211
|
+
const seen = new Set();
|
|
212
|
+
for (const item of items) {
|
|
213
|
+
const entry = buildChangeEntry(item, options);
|
|
214
|
+
if (!entry) continue;
|
|
215
|
+
const key = `${entry.type}::${entry.detail}`;
|
|
216
|
+
if (seen.has(key)) continue;
|
|
217
|
+
seen.add(key);
|
|
218
|
+
entries.push(entry);
|
|
219
|
+
if (options.limit && entries.length >= options.limit) break;
|
|
220
|
+
}
|
|
221
|
+
return entries;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function buildTaskCommitMessage(task) {
|
|
225
|
+
const basis = task?.done ?? task?.title ?? task?.id ?? '本次变更';
|
|
226
|
+
const entry = buildChangeEntry(basis, {
|
|
227
|
+
fallbackType: detectChangeVerb(`${task?.title ?? ''} ${task?.done ?? ''}`, '优化'),
|
|
228
|
+
summaryMaxLength: 20,
|
|
229
|
+
}) ?? {
|
|
230
|
+
type: '调整',
|
|
231
|
+
summary: '调整本次变更',
|
|
232
|
+
detail: basis,
|
|
233
|
+
sentence: `调整:${basis}`,
|
|
234
|
+
};
|
|
235
|
+
const taskLine = [task?.id, task?.title].filter(Boolean).join(' ');
|
|
236
|
+
const body = [`- ${entry.sentence}`];
|
|
237
|
+
if (taskLine && taskLine !== entry.detail) {
|
|
238
|
+
body.push(`- 任务:${taskLine}`);
|
|
239
|
+
}
|
|
240
|
+
return [entry.summary, '', ...body].join('\n');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function collectSnapshotCandidates(snapshot) {
|
|
244
|
+
const sections = snapshot?.sections ?? {};
|
|
245
|
+
return [
|
|
246
|
+
...(Array.isArray(sections.requirements?.functional)
|
|
247
|
+
? sections.requirements.functional.map((text) => ({ text, fallbackType: '新增' }))
|
|
248
|
+
: []),
|
|
249
|
+
...(Array.isArray(sections.scenarios?.primaryFlows)
|
|
250
|
+
? sections.scenarios.primaryFlows.map((text) => ({ text, fallbackType: '优化' }))
|
|
251
|
+
: []),
|
|
252
|
+
...(Array.isArray(sections.scope?.inScope)
|
|
253
|
+
? sections.scope.inScope.map((text) => ({ text, fallbackType: '调整' }))
|
|
254
|
+
: []),
|
|
255
|
+
...(Array.isArray(sections.goals?.goals)
|
|
256
|
+
? sections.goals.goals.map((text) => ({ text, fallbackType: '优化' }))
|
|
257
|
+
: []),
|
|
258
|
+
...(Array.isArray(sections.businessGuardrails?.usageLimits)
|
|
259
|
+
? sections.businessGuardrails.usageLimits.map((text) => ({ text, fallbackType: '调整' }))
|
|
260
|
+
: []),
|
|
261
|
+
...(Array.isArray(sections.businessGuardrails?.costControls)
|
|
262
|
+
? sections.businessGuardrails.costControls.map((text) => ({ text, fallbackType: '调整' }))
|
|
263
|
+
: []),
|
|
264
|
+
];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function buildSnapshotChangeSummary(snapshot, options = {}) {
|
|
268
|
+
const limit = options.limit ?? 5;
|
|
269
|
+
const entries = [];
|
|
270
|
+
const seen = new Set();
|
|
271
|
+
for (const candidate of collectSnapshotCandidates(snapshot)) {
|
|
272
|
+
const entry = buildChangeEntry(candidate.text, {
|
|
273
|
+
fallbackType: candidate.fallbackType,
|
|
274
|
+
summaryMaxLength: 15,
|
|
275
|
+
});
|
|
276
|
+
if (!entry) continue;
|
|
277
|
+
const key = `${entry.type}::${entry.detail}`;
|
|
278
|
+
if (seen.has(key)) continue;
|
|
279
|
+
seen.add(key);
|
|
280
|
+
entries.push(entry);
|
|
281
|
+
if (entries.length >= limit) break;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (entries.length === 0 && snapshot?.title) {
|
|
285
|
+
const fallback = buildChangeEntry(snapshot.title, { fallbackType: '调整' });
|
|
286
|
+
if (fallback) {
|
|
287
|
+
entries.push(fallback);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return buildChangeSummaryFromEntries(entries, {
|
|
292
|
+
title: `${snapshot?.title ?? '当前版本'}变化摘要`,
|
|
293
|
+
limit,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function buildChangeSummaryFromEntries(values, options = {}) {
|
|
298
|
+
const limit = options.limit ?? null;
|
|
299
|
+
const items = Array.isArray(values) ? values : [values];
|
|
300
|
+
const seen = new Set();
|
|
301
|
+
const entries = [];
|
|
302
|
+
for (const value of items) {
|
|
303
|
+
const entry = value?.type && value?.detail && value?.summary && value?.sentence
|
|
304
|
+
? {
|
|
305
|
+
type: value.type,
|
|
306
|
+
summary: value.summary,
|
|
307
|
+
detail: value.detail,
|
|
308
|
+
sentence: value.sentence,
|
|
309
|
+
bullet: value.bullet ?? `- ${value.sentence}`,
|
|
310
|
+
}
|
|
311
|
+
: buildChangeEntry(value, {
|
|
312
|
+
fallbackType: options.fallbackType ?? '调整',
|
|
313
|
+
summaryMaxLength: 15,
|
|
314
|
+
});
|
|
315
|
+
if (!entry) continue;
|
|
316
|
+
const key = `${entry.type}::${entry.detail}`;
|
|
317
|
+
if (seen.has(key)) continue;
|
|
318
|
+
seen.add(key);
|
|
319
|
+
entries.push(entry);
|
|
320
|
+
if (limit && entries.length >= limit) break;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
title: options.title ?? '当前版本变化摘要',
|
|
325
|
+
perspective: USER_CHANGE_SUMMARY_GUIDE.perspective,
|
|
326
|
+
preferredVerbs: USER_CHANGE_SUMMARY_GUIDE.preferredVerbs,
|
|
327
|
+
items: entries.map(({ type, summary, detail, sentence }) => ({
|
|
328
|
+
type,
|
|
329
|
+
summary,
|
|
330
|
+
detail,
|
|
331
|
+
sentence,
|
|
332
|
+
})),
|
|
333
|
+
markdown: entries.map((entry) => entry.bullet).join('\n'),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export function buildReviewFallbackPanelItems(items, options = {}) {
|
|
338
|
+
return buildChangeEntries(items, options).map((entry) => entry.panel);
|
|
339
|
+
}
|
package/src/cli/args.js
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 核心功能
|
|
3
|
+
* 解析 OpenPrd CLI 参数并生成用户可见 usage 文案。
|
|
4
|
+
*
|
|
5
|
+
* 输入
|
|
6
|
+
* 接收 process argv 风格的字符串数组。
|
|
7
|
+
*
|
|
8
|
+
* 输出
|
|
9
|
+
* 返回 command、projectPath、flags 和 usage 文本。
|
|
10
|
+
*
|
|
11
|
+
* 定位
|
|
12
|
+
* 位于 CLI 边界层,只负责参数归一化,不执行业务流程。
|
|
13
|
+
*
|
|
14
|
+
* 依赖
|
|
15
|
+
* 被 openprd.js 主入口调用;flag 名称需与 print、workspace 函数和文档保持一致。
|
|
16
|
+
*
|
|
17
|
+
* 维护规则
|
|
18
|
+
* 新增 flag 时必须同步默认值、解析分支、usage、测试和 docs/basic/backend-structure.md。
|
|
19
|
+
*/
|
|
1
20
|
function parseCommandArgs(argv) {
|
|
2
21
|
const args = [...argv];
|
|
3
|
-
const flags = { json: false, force: false, open: false, append: false, init: false, check: false, review: false, reject: false, resume: false, advance: false, verify: false, next: false, generate: false, validate: false, apply: false, archive: false, activate: false, close: false, keep: false, write: false, dryRun: false, fleet: false, updateOpenprd: false, backfillWorkUnits: false, syncRegistry: false, setupMissing: false, doctor: false, context: false, recordHook: false, plan: false, prompt: false, loopRun: false, finish: false, commit: false, html: false, template: false, failOnViolation: false, mark: null, type: 'architecture', mode: 'auto', input: null, field: null, value: null, jsonFile: null, artifactMarkdown: null, contentJson: null, presentation: null, source: null, reference: null, actual: null, out: null, format: null, quality: null, maxPanelWidth: null, referenceLabel: null, actualLabel: null, classifyExternal: null, maxIterations: null, maxDepth: null, include: null, exclude: null, report: null, item: null, id: null, status: null, claim: null, notes: null, confidence: null, change: null, tools: 'all', hookProfile: null, templatePack: null, target: 'openprd', targetRoot: null, path: null, productType: null, title: null, owner: null, problem: null, whyNow: null, evidence: null, from: null, to: null, version: null, digest: null, workUnit: null, event: null, risk: null, outcome: null, preview: null, learn: null, genre: null, style: null, topic: null, enable: false, disable: false, agent: 'codex', agentCommand: null, message: null };
|
|
22
|
+
const flags = { json: false, force: false, fix: false, open: false, append: false, init: false, check: false, review: false, reject: false, resume: false, advance: false, verify: false, evidenceRequired: false, next: false, generate: false, validate: false, apply: false, archive: false, activate: false, close: false, keep: false, write: false, dryRun: false, repairAgent: false, fleet: false, updateOpenprd: false, backfillWorkUnits: false, syncRegistry: false, setupMissing: false, doctor: false, context: false, recordHook: false, hookInject: false, plan: false, prompt: false, loopRun: false, finish: false, commit: false, html: false, template: false, failOnViolation: false, mark: null, type: 'architecture', mode: 'auto', input: null, field: null, value: null, set: null, jsonFile: null, artifactMarkdown: null, contentJson: null, presentation: null, source: null, reference: null, actual: null, before: null, after: null, board: null, out: null, format: null, quality: null, maxPanelWidth: null, referenceLabel: null, actualLabel: null, classifyExternal: null, maxIterations: null, maxDepth: null, include: null, exclude: null, report: null, item: null, id: null, status: null, claim: null, notes: null, reason: null, confidence: null, threshold: null, change: null, tools: 'all', hookProfile: null, templatePack: null, target: 'openprd', targetRoot: null, path: null, productType: null, title: null, owner: null, problem: null, whyNow: null, evidence: null, from: null, to: null, version: null, digest: null, workUnit: null, event: null, risk: null, outcome: null, preview: null, learn: null, genre: null, style: null, topic: null, enable: false, disable: false, agent: 'codex', agentCommand: null, message: null };
|
|
4
23
|
const positionals = [];
|
|
5
24
|
|
|
6
25
|
while (args.length > 0) {
|
|
@@ -13,6 +32,10 @@ function parseCommandArgs(argv) {
|
|
|
13
32
|
flags.force = true;
|
|
14
33
|
continue;
|
|
15
34
|
}
|
|
35
|
+
if (arg === '--fix') {
|
|
36
|
+
flags.fix = true;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
16
39
|
if (arg === '--open') {
|
|
17
40
|
flags.open = true;
|
|
18
41
|
continue;
|
|
@@ -57,6 +80,10 @@ function parseCommandArgs(argv) {
|
|
|
57
80
|
flags.verify = true;
|
|
58
81
|
continue;
|
|
59
82
|
}
|
|
83
|
+
if (arg === '--evidence-required') {
|
|
84
|
+
flags.evidenceRequired = true;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
60
87
|
if (arg === '--generate') {
|
|
61
88
|
flags.generate = true;
|
|
62
89
|
continue;
|
|
@@ -89,6 +116,10 @@ function parseCommandArgs(argv) {
|
|
|
89
116
|
flags.dryRun = true;
|
|
90
117
|
continue;
|
|
91
118
|
}
|
|
119
|
+
if (arg === '--repair-agent') {
|
|
120
|
+
flags.repairAgent = true;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
92
123
|
if (arg === '--fleet') {
|
|
93
124
|
flags.fleet = true;
|
|
94
125
|
continue;
|
|
@@ -157,6 +188,10 @@ function parseCommandArgs(argv) {
|
|
|
157
188
|
flags.recordHook = true;
|
|
158
189
|
continue;
|
|
159
190
|
}
|
|
191
|
+
if (arg === '--hook-inject') {
|
|
192
|
+
flags.hookInject = true;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
160
195
|
if (arg === '--next') {
|
|
161
196
|
flags.next = true;
|
|
162
197
|
continue;
|
|
@@ -209,6 +244,10 @@ function parseCommandArgs(argv) {
|
|
|
209
244
|
flags.value = args.shift() ?? null;
|
|
210
245
|
continue;
|
|
211
246
|
}
|
|
247
|
+
if (arg === '--set') {
|
|
248
|
+
flags.set = args.shift() ?? null;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
212
251
|
if (arg === '--json-file') {
|
|
213
252
|
flags.jsonFile = args.shift() ?? null;
|
|
214
253
|
continue;
|
|
@@ -237,6 +276,18 @@ function parseCommandArgs(argv) {
|
|
|
237
276
|
flags.actual = args.shift() ?? null;
|
|
238
277
|
continue;
|
|
239
278
|
}
|
|
279
|
+
if (arg === '--before') {
|
|
280
|
+
flags.before = args.shift() ?? null;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (arg === '--after') {
|
|
284
|
+
flags.after = args.shift() ?? null;
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (arg === '--board') {
|
|
288
|
+
flags.board = args.shift() ?? null;
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
240
291
|
if (arg === '--out') {
|
|
241
292
|
flags.out = args.shift() ?? null;
|
|
242
293
|
continue;
|
|
@@ -305,10 +356,18 @@ function parseCommandArgs(argv) {
|
|
|
305
356
|
flags.notes = args.shift() ?? null;
|
|
306
357
|
continue;
|
|
307
358
|
}
|
|
359
|
+
if (arg === '--reason') {
|
|
360
|
+
flags.reason = args.shift() ?? null;
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
308
363
|
if (arg === '--confidence') {
|
|
309
364
|
flags.confidence = args.shift() ?? null;
|
|
310
365
|
continue;
|
|
311
366
|
}
|
|
367
|
+
if (arg === '--threshold') {
|
|
368
|
+
flags.threshold = args.shift() ?? null;
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
312
371
|
if (arg === '--change') {
|
|
313
372
|
flags.change = args.shift() ?? null;
|
|
314
373
|
continue;
|
|
@@ -421,10 +480,10 @@ function usage() {
|
|
|
421
480
|
' openprd update [path] [--tools <all|codex,claude,cursor>] [--hook-profile <lite|guarded|full>] [--force] [--json]',
|
|
422
481
|
' openprd self-update [--dry-run] [--json]',
|
|
423
482
|
' openprd upgrade [path] [--fleet] [--dry-run] [--tools <all|codex,claude,cursor>] [--hook-profile <lite|guarded|full>] [--max-depth <n>] [--include <csv>] [--exclude <csv>] [--report <file>] [--force] [--json]',
|
|
424
|
-
' openprd doctor [path] [--tools <all|codex,claude,cursor>] [--hook-profile <lite|guarded|full>] [--json]',
|
|
483
|
+
' openprd doctor [path] [--tools <all|codex,claude,cursor>] [--hook-profile <lite|guarded|full>] [--fix] [--json]',
|
|
425
484
|
' openprd fleet <root> [--dry-run|--doctor|--update-openprd|--backfill-work-units|--sync-registry|--setup-missing] [--hook-profile <lite|guarded|full>] [--max-depth <n>] [--include <csv>] [--exclude <csv>] [--report <file>] [--json]',
|
|
426
485
|
' openprd run [path] [--context|--verify|--record-hook --event <name> --risk <level> --outcome <text> --preview <text>] [--message <text>] [--json]',
|
|
427
|
-
' openprd loop [path] [--init|--plan|--next|--prompt|--run|--verify|--finish] [--change <id>] [--item <task-id-or-handle>] [--agent <codex|claude>] [--agent-command <cmd>] [--commit] [--dry-run] [--message <text>] [--json]',
|
|
486
|
+
' openprd loop [path] [--init|--plan|--next|--prompt|--run|--verify|--finish] [--change <id>] [--item <task-id-or-handle>] [--agent <codex|claude>] [--agent-command <cmd>] [--repair-agent] [--commit] [--dry-run] [--message <text>] [--json]',
|
|
428
487
|
' openprd classify [path] <consumer|b2b|agent>',
|
|
429
488
|
' openprd clarify [path] [--mode <auto|inline|inline-with-checklist>] [--json]',
|
|
430
489
|
' openprd capture [path] (--field <section.path> --value <text|json> | --json-file <answers.json> | --artifact-markdown <artifact.md>) [--source <user-confirmed|project-derived|agent-inferred|agent-normalized>] [--append] [--json]',
|
|
@@ -432,16 +491,18 @@ function usage() {
|
|
|
432
491
|
' openprd playground [path] [--open] [--json]',
|
|
433
492
|
' openprd learn [path] [--topic <text>] [--genre <internet-product|scientific|fairy-tale|web-novel|xianxia>] [--style <substyle>] [--source <workspace|docs|loop|all>] [--content-json <file>] [--open] [--enable|--disable] [--json]',
|
|
434
493
|
' openprd quality [path] [--init|--verify|--report --html|--learn [--review] --from <report-id-or-json-or-diagnostics-or-turn-state>] [--force] [--json]',
|
|
435
|
-
' openprd
|
|
494
|
+
' openprd knowledge <candidates|reject|archive|restore> [path-or-id] [--status <pending-review|all|rejected|archived|promoted|merged>] [--id <candidate-id>] [--reason <text>] [--json]',
|
|
495
|
+
' openprd visual-compare [path] ((--reference <effect-image> --actual <screenshot-image>) | (--before <before-screenshot> --after <after-screenshot>) | --board <board.json>) [--out <file.jpg>] [--format <jpg|png|webp>] [--quality <1..100>] [--max-panel-width <px>] [--json]',
|
|
436
496
|
' openprd dev-check [path] <file...> [--json]',
|
|
437
497
|
' openprd grow [path] [--review|--apply --id <candidate-id>|--reject --id <candidate-id>|--init|--check] [--notes <text>] [--json]',
|
|
438
|
-
' openprd benchmark <add|list|approve|verify> [target-or-id] [path-for-list-or-verify] [--path <project>] [--notes <text>] [--id <benchmark-id>] [--json]',
|
|
498
|
+
' openprd benchmark <add|observe|list|approve|verify> [target-or-id] [path-for-list-or-verify] [--path <project>] [--notes <text>] [--threshold <n>] [--id <benchmark-id>] [--json]',
|
|
439
499
|
' openprd synthesize [path] [--title <text>] [--owner <text>] [--problem <text>] [--why-now <text>] [--work-unit <id>] [--target-root <path>] [--open] [--json]',
|
|
440
500
|
' openprd review [path] [--open] [--mark <pending-confirmation|confirmed|needs-revision>] [--version <id>] [--digest <sha256>] [--work-unit <id>] [--notes <text>] [--json]',
|
|
441
501
|
' openprd review-presentation [path] [--template] [--version <id>] [--presentation <json>] [--write] [--fail-on-violation] [--json]',
|
|
442
502
|
' openprd diagram [path] [--type <architecture|product-flow>] [--input <contract.json>] [--mark <pending-confirmation|confirmed|needs-revision>] [--open] [--json]',
|
|
443
503
|
' openprd diff [path] [--from <version>] [--to <version>]',
|
|
444
504
|
' openprd history [path]',
|
|
505
|
+
' openprd release [path] [--enable|--disable] [--set <0.1.23>] [--status <draft|current|released>] [--version <id>] [--notes <text>] [--json]',
|
|
445
506
|
' openprd validate [path] [--json]',
|
|
446
507
|
' openprd status [path] [--json]',
|
|
447
508
|
' openprd freeze [path] [--json]',
|
|
@@ -450,7 +511,7 @@ function usage() {
|
|
|
450
511
|
' openprd change [path] (--generate|--validate|--apply|--archive|--activate|--close) [--change <id>] [--force] [--keep] [--json]',
|
|
451
512
|
' openprd changes [path] [--json]',
|
|
452
513
|
' openprd specs [path] [--json]',
|
|
453
|
-
' openprd tasks [path] [--next] [--advance] [--verify] [--item <task-id>] [--change <id>] [--evidence <path>] [--notes <text>] [--json]',
|
|
514
|
+
' openprd tasks [path] [--next] [--advance] [--verify|--evidence-required] [--item <task-id>] [--change <id>] [--evidence <path>] [--notes <text>] [--json]',
|
|
454
515
|
' openprd discovery [path] [--mode <auto|brownfield|reference|requirement>] [--reference <path>] [--max-iterations <n>] [--resume] [--advance] [--verify] [--item <id>] [--status <covered|blocked|pending>] [--claim <text>] [--evidence <path>] [--notes <text>] [--confidence <0..1>] [--json]',
|
|
455
516
|
'',
|
|
456
517
|
].join('\n');
|