@openprd/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/.openprd/README.md +82 -0
- package/.openprd/benchmarks/evidence/milvus-io-ai-code-review-gets-better-when-models-debate-claude-vs-gemini-vs-code.md +14 -0
- package/.openprd/benchmarks/evidence/nolanlawson-com-using-ai-to-write-better-code-more-slowly.md +14 -0
- package/.openprd/benchmarks/index.md +37 -0
- package/.openprd/benchmarks/sources.yaml +56 -0
- package/.openprd/config.yaml +50 -0
- package/.openprd/discovery/config.json +21 -0
- package/.openprd/engagements/active/flows.md +30 -0
- package/.openprd/engagements/active/handoff.md +9 -0
- package/.openprd/engagements/active/intake.md +15 -0
- package/.openprd/engagements/active/prd.md +161 -0
- package/.openprd/engagements/active/review.html +61 -0
- package/.openprd/engagements/active/roles.md +21 -0
- package/.openprd/engagements/work-units/wu-20260524015648-6d33ded7.json +23 -0
- package/.openprd/exports/.gitkeep +0 -0
- package/.openprd/knowledge/index.json +7 -0
- package/.openprd/quality/config.json +229 -0
- package/.openprd/reviews/v0001.html +1256 -0
- package/.openprd/schema/diagram-architecture.schema.yaml +49 -0
- package/.openprd/schema/diagram-product-flow.schema.yaml +52 -0
- package/.openprd/schema/prd.schema.yaml +121 -0
- package/.openprd/sessions/.gitkeep +0 -0
- package/.openprd/standards/config.json +88 -0
- package/.openprd/standards/file-manual-template.md +28 -0
- package/.openprd/standards/folder-readme-template.md +28 -0
- package/.openprd/state/.gitkeep +0 -0
- package/.openprd/state/changes.json +12 -0
- package/.openprd/state/current.json +169 -0
- package/.openprd/state/version-index.json +15 -0
- package/.openprd/state/versions/.gitkeep +0 -0
- package/.openprd/state/versions/v0001.json +121 -0
- package/.openprd/state/versions/v0001.md +161 -0
- package/.openprd/templates/agent/intake.md +6 -0
- package/.openprd/templates/agent/prd.md +21 -0
- package/.openprd/templates/b2b/intake.md +6 -0
- package/.openprd/templates/b2b/prd.md +24 -0
- package/.openprd/templates/base/intake.md +18 -0
- package/.openprd/templates/base/prd.md +67 -0
- package/.openprd/templates/company/README.md +10 -0
- package/.openprd/templates/consumer/intake.md +6 -0
- package/.openprd/templates/consumer/prd.md +19 -0
- package/.openprd/templates/diagram/architecture.contract.json +53 -0
- package/.openprd/templates/diagram/product-flow.contract.json +76 -0
- package/.openprd/templates/industry/README.md +16 -0
- package/.openprd/templates/manifest.yaml +27 -0
- package/.openprd/templates/project/README.md +14 -0
- package/.openprd/templates/session/README.md +14 -0
- package/AGENTS.md +44 -0
- package/CONTRIBUTING.md +30 -0
- package/LICENSE +21 -0
- package/README.md +727 -0
- package/README_CN.md +583 -0
- package/SECURITY.md +23 -0
- package/bin/openprd.js +5 -0
- package/docs/assets/openprd-capability-overview-en.png +0 -0
- package/docs/assets/openprd-capability-overview-zh.png +0 -0
- package/docs/assets/openprd-learning-html.png +0 -0
- package/docs/assets/openprd-quality-html.png +0 -0
- package/docs/assets/openprd-review-html.png +0 -0
- package/docs/assets/openprd-scenario-overview.png +0 -0
- package/docs/assets/openprd-scenario-overview.svg +114 -0
- package/docs/assets/openprd-self-evolving-mechanisms-en.png +0 -0
- package/docs/assets/openprd-self-evolving-mechanisms-zh.png +0 -0
- package/docs/assets/openprd-visual-compare-case-study-en.png +0 -0
- package/docs/assets/openprd-visual-compare-case-study-zh.png +0 -0
- package/package.json +59 -0
- package/scripts/openprd-dev-check.mjs +5 -0
- package/scripts/openprd-review-presentation.mjs +82 -0
- package/skills/openprd-benchmark-router/SKILL.md +92 -0
- package/skills/openprd-benchmark-router/agents/openai.yaml +4 -0
- package/skills/openprd-benchmark-router/references/benchmark-sources.md +74 -0
- package/skills/openprd-benchmark-router/references/evaluation-lenses.md +66 -0
- package/skills/openprd-benchmark-router/references/source-policy.md +35 -0
- package/skills/openprd-diagram-review/SKILL.md +91 -0
- package/skills/openprd-diagram-review/agents/openai.yaml +4 -0
- package/skills/openprd-diagram-review/examples/architecture-zh.md +8 -0
- package/skills/openprd-diagram-review/examples/product-flow-zh.md +7 -0
- package/skills/openprd-diagram-review/references/cocoon-patterns.md +17 -0
- package/skills/openprd-diagram-review/references/diagram-contracts.md +126 -0
- package/skills/openprd-diagram-review/references/review-checklist.md +10 -0
- package/skills/openprd-discovery-loop/SKILL.md +196 -0
- package/skills/openprd-discovery-loop/agents/openai.yaml +3 -0
- package/skills/openprd-harness/SKILL.md +179 -0
- package/skills/openprd-harness/agents/openai.yaml +4 -0
- package/skills/openprd-harness/examples/full-workflow-zh.md +9 -0
- package/skills/openprd-harness/references/command-map.md +71 -0
- package/skills/openprd-harness/references/examples.md +26 -0
- package/skills/openprd-harness/references/usage-guide.md +335 -0
- package/skills/openprd-harness/references/workflow-gates.md +51 -0
- package/skills/openprd-learning-review/SKILL.md +75 -0
- package/skills/openprd-learning-review/agents/openai.yaml +4 -0
- package/skills/openprd-learning-review/references/content-contract.md +125 -0
- package/skills/openprd-learning-review/references/ebook-reader.md +46 -0
- package/skills/openprd-learning-review/references/evidence-manifest.md +55 -0
- package/skills/openprd-learning-review/references/genre-library.md +43 -0
- package/skills/openprd-learning-review/references/prompt-engineering.md +71 -0
- package/skills/openprd-learning-review/references/quality-rubric.md +28 -0
- package/skills/openprd-learning-review/references/retrieval-worked-example.md +40 -0
- package/skills/openprd-learning-review/references/style-packs/xianxia-cultivation.prompt.md +67 -0
- package/skills/openprd-quality/SKILL.md +101 -0
- package/skills/openprd-requirement-intake/SKILL.md +76 -0
- package/skills/openprd-requirement-intake/agents/openai.yaml +4 -0
- package/skills/openprd-requirement-intake/references/prd-template-lenses.md +105 -0
- package/skills/openprd-requirement-intake/references/routing-rubric.md +64 -0
- package/skills/openprd-router/SKILL.md +40 -0
- package/skills/openprd-shared/SKILL.md +142 -0
- package/skills/openprd-shared/agents/openai.yaml +4 -0
- package/skills/openprd-shared/references/language-and-review.md +50 -0
- package/skills/openprd-shared/references/operating-rules.md +65 -0
- package/skills/openprd-shared/references/skill-architecture.md +70 -0
- package/skills/openprd-standards/SKILL.md +79 -0
- package/skills/openprd-standards/agents/openai.yaml +4 -0
- package/src/agent-integration.js +1717 -0
- package/src/benchmark.js +873 -0
- package/src/cli/args.js +460 -0
- package/src/cli/print.js +1423 -0
- package/src/codex-hook-runner-template.mjs +2422 -0
- package/src/dev-standards.js +372 -0
- package/src/diagram-core.js +1047 -0
- package/src/diagram-workspace.js +262 -0
- package/src/discovery.js +709 -0
- package/src/fleet.js +531 -0
- package/src/fs-utils.js +83 -0
- package/src/growth.js +545 -0
- package/src/html-artifacts.js +3803 -0
- package/src/knowledge.js +668 -0
- package/src/language-policy.js +142 -0
- package/src/learning-review.js +1655 -0
- package/src/loop.js +1290 -0
- package/src/openprd.js +1136 -0
- package/src/openspec/change-lifecycle.js +359 -0
- package/src/openspec/change-validate.js +248 -0
- package/src/openspec/constants.js +12 -0
- package/src/openspec/execute.js +300 -0
- package/src/openspec/generate.js +692 -0
- package/src/openspec/paths.js +111 -0
- package/src/openspec/tasks.js +352 -0
- package/src/prd-core.js +656 -0
- package/src/quality-html-artifact.js +1414 -0
- package/src/quality-learning.js +658 -0
- package/src/quality.js +1262 -0
- package/src/review-presentation.js +240 -0
- package/src/run-harness.js +1470 -0
- package/src/self-update.js +329 -0
- package/src/session-binding.js +140 -0
- package/src/source-inventory.js +224 -0
- package/src/standards.js +914 -0
- package/src/time.js +33 -0
- package/src/visual-compare.js +216 -0
- package/src/work-unit-migration.js +232 -0
- package/src/work-unit.js +88 -0
- package/src/workspace-core.js +1706 -0
- package/src/workspace-registry.js +162 -0
- package/src/workspace-workflow.js +1797 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { resolveOpenSpecChangeId } from './change-validate.js';
|
|
5
|
+
import { cjoin, resolveChangeDir } from './paths.js';
|
|
6
|
+
import { timestamp } from '../time.js';
|
|
7
|
+
import {
|
|
8
|
+
listOpenSpecStructuredTasks,
|
|
9
|
+
parseOpenSpecTaskDeps,
|
|
10
|
+
summarizeOpenSpecTaskTypes,
|
|
11
|
+
} from './tasks.js';
|
|
12
|
+
|
|
13
|
+
async function readText(filePath) {
|
|
14
|
+
return fs.readFile(filePath, 'utf8');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function writeText(filePath, text) {
|
|
18
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
19
|
+
await fs.writeFile(filePath, text, 'utf8');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function appendJsonl(filePath, value) {
|
|
23
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
24
|
+
await fs.appendFile(filePath, `${JSON.stringify(value)}\n`, 'utf8');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getDependencyState(task, taskById) {
|
|
28
|
+
const deps = parseOpenSpecTaskDeps(task.metadata.deps);
|
|
29
|
+
const missing = [];
|
|
30
|
+
const incomplete = [];
|
|
31
|
+
|
|
32
|
+
for (const depId of deps) {
|
|
33
|
+
const dependency = taskById.get(depId);
|
|
34
|
+
if (!dependency) {
|
|
35
|
+
missing.push(depId);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (!dependency.checked) {
|
|
39
|
+
incomplete.push(depId);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
deps,
|
|
45
|
+
missing,
|
|
46
|
+
incomplete,
|
|
47
|
+
ready: missing.length === 0 && incomplete.length === 0,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function summarizeOpenSpecTasks(tasks, taskById) {
|
|
52
|
+
const pending = tasks.filter((task) => !task.checked);
|
|
53
|
+
const completed = tasks.filter((task) => task.checked);
|
|
54
|
+
const taskTypes = summarizeOpenSpecTaskTypes(tasks);
|
|
55
|
+
const blocked = pending
|
|
56
|
+
.map((task) => ({
|
|
57
|
+
task,
|
|
58
|
+
dependencyState: getDependencyState(task, taskById),
|
|
59
|
+
}))
|
|
60
|
+
.filter((item) => !item.dependencyState.ready);
|
|
61
|
+
const nextReady = pending.find((task) => getDependencyState(task, taskById).ready) ?? null;
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
total: tasks.length,
|
|
65
|
+
completed: completed.length,
|
|
66
|
+
pending: pending.length,
|
|
67
|
+
blocked: blocked.length,
|
|
68
|
+
taskTypes,
|
|
69
|
+
implementation: taskTypes.implementation ?? { total: 0, completed: 0, pending: 0 },
|
|
70
|
+
nextReady,
|
|
71
|
+
blockedTasks: blocked,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function loadTaskState(projectRoot, options = {}) {
|
|
76
|
+
const changeId = await resolveOpenSpecChangeId(projectRoot, options.change);
|
|
77
|
+
const { files, tasks, taskById } = await listOpenSpecStructuredTasks(projectRoot, { changeId });
|
|
78
|
+
const summary = summarizeOpenSpecTasks(tasks, taskById);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
projectRoot,
|
|
82
|
+
changeId,
|
|
83
|
+
changeDir: await resolveChangeDir(projectRoot, changeId),
|
|
84
|
+
files,
|
|
85
|
+
tasks,
|
|
86
|
+
taskById,
|
|
87
|
+
summary,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function resolveTaskSelection(state, options = {}) {
|
|
92
|
+
const requestedId = options.item ?? options.id ?? null;
|
|
93
|
+
const task = requestedId
|
|
94
|
+
? state.taskById.get(requestedId)
|
|
95
|
+
: state.summary.nextReady;
|
|
96
|
+
|
|
97
|
+
if (!task) {
|
|
98
|
+
if (requestedId) {
|
|
99
|
+
throw new Error(`Unknown OpenSpec task: ${requestedId}`);
|
|
100
|
+
}
|
|
101
|
+
throw new Error('No ready OpenSpec task is available.');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return task;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function assertTaskReady(task, state) {
|
|
108
|
+
const dependencyState = getDependencyState(task, state.taskById);
|
|
109
|
+
if (dependencyState.missing.length > 0) {
|
|
110
|
+
throw new Error(`${task.id} depends on unknown task(s): ${dependencyState.missing.join(', ')}`);
|
|
111
|
+
}
|
|
112
|
+
if (dependencyState.incomplete.length > 0) {
|
|
113
|
+
throw new Error(`${task.id} 被未完成任务阻塞: ${dependencyState.incomplete.join(', ')}`);
|
|
114
|
+
}
|
|
115
|
+
return dependencyState;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function runVerifyCommand(command, cwd) {
|
|
119
|
+
return new Promise((resolve) => {
|
|
120
|
+
const child = spawn(command, {
|
|
121
|
+
cwd,
|
|
122
|
+
shell: true,
|
|
123
|
+
env: process.env,
|
|
124
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
125
|
+
});
|
|
126
|
+
let stdout = '';
|
|
127
|
+
let stderr = '';
|
|
128
|
+
child.stdout.on('data', (chunk) => {
|
|
129
|
+
stdout = `${stdout}${chunk}`.slice(-64000);
|
|
130
|
+
});
|
|
131
|
+
child.stderr.on('data', (chunk) => {
|
|
132
|
+
stderr = `${stderr}${chunk}`.slice(-64000);
|
|
133
|
+
});
|
|
134
|
+
child.on('error', (error) => {
|
|
135
|
+
resolve({
|
|
136
|
+
ok: false,
|
|
137
|
+
command,
|
|
138
|
+
exitCode: null,
|
|
139
|
+
stdout,
|
|
140
|
+
stderr: `${stderr}${error.message}`,
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
child.on('close', (exitCode) => {
|
|
144
|
+
resolve({
|
|
145
|
+
ok: exitCode === 0,
|
|
146
|
+
command,
|
|
147
|
+
exitCode,
|
|
148
|
+
stdout,
|
|
149
|
+
stderr,
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function markTaskComplete(task) {
|
|
156
|
+
const text = await readText(task.absolutePath);
|
|
157
|
+
const lines = text.split(/\r?\n/);
|
|
158
|
+
const index = task.lineNumber - 1;
|
|
159
|
+
if (!lines[index]) {
|
|
160
|
+
throw new Error(`Cannot update ${task.relativePath}:${task.lineNumber}; line is missing.`);
|
|
161
|
+
}
|
|
162
|
+
if (!lines[index].startsWith('- [x]') && !lines[index].startsWith('- [X]')) {
|
|
163
|
+
lines[index] = lines[index].replace(/^- \[ \]/, '- [x]');
|
|
164
|
+
}
|
|
165
|
+
await writeText(task.absolutePath, lines.join('\n'));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function appendTaskEvent(state, event) {
|
|
169
|
+
await appendJsonl(cjoin(state.changeDir, 'task-events.jsonl'), {
|
|
170
|
+
version: 1,
|
|
171
|
+
at: timestamp(),
|
|
172
|
+
changeId: state.changeId,
|
|
173
|
+
...event,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function listOpenSpecTaskWorkspace(projectRoot, options = {}) {
|
|
178
|
+
const state = await loadTaskState(projectRoot, options);
|
|
179
|
+
return {
|
|
180
|
+
ok: true,
|
|
181
|
+
action: 'list',
|
|
182
|
+
projectRoot,
|
|
183
|
+
changeId: state.changeId,
|
|
184
|
+
changeDir: state.changeDir,
|
|
185
|
+
tasks: state.tasks,
|
|
186
|
+
summary: {
|
|
187
|
+
total: state.summary.total,
|
|
188
|
+
completed: state.summary.completed,
|
|
189
|
+
pending: state.summary.pending,
|
|
190
|
+
blocked: state.summary.blocked,
|
|
191
|
+
taskTypes: state.summary.taskTypes,
|
|
192
|
+
implementation: state.summary.implementation,
|
|
193
|
+
},
|
|
194
|
+
nextTask: state.summary.nextReady,
|
|
195
|
+
blockedTasks: state.summary.blockedTasks.map(({ task, dependencyState }) => ({
|
|
196
|
+
id: task.id,
|
|
197
|
+
title: task.title,
|
|
198
|
+
missing: dependencyState.missing,
|
|
199
|
+
incomplete: dependencyState.incomplete,
|
|
200
|
+
})),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function verifyOpenSpecTaskWorkspace(projectRoot, options = {}) {
|
|
205
|
+
const state = await loadTaskState(projectRoot, options);
|
|
206
|
+
const task = resolveTaskSelection(state, options);
|
|
207
|
+
assertTaskReady(task, state);
|
|
208
|
+
if (!task.metadata.verify) {
|
|
209
|
+
throw new Error(`${task.id} is missing verify command.`);
|
|
210
|
+
}
|
|
211
|
+
const verification = await runVerifyCommand(task.metadata.verify, projectRoot);
|
|
212
|
+
await appendTaskEvent(state, {
|
|
213
|
+
action: 'verify',
|
|
214
|
+
taskId: task.id,
|
|
215
|
+
taskTitle: task.title,
|
|
216
|
+
ok: verification.ok,
|
|
217
|
+
verify: verification,
|
|
218
|
+
evidence: options.evidence ?? null,
|
|
219
|
+
notes: options.notes ?? null,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
ok: verification.ok,
|
|
224
|
+
action: 'verify',
|
|
225
|
+
projectRoot,
|
|
226
|
+
changeId: state.changeId,
|
|
227
|
+
task,
|
|
228
|
+
verification,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export async function advanceOpenSpecTaskWorkspace(projectRoot, options = {}) {
|
|
233
|
+
const state = await loadTaskState(projectRoot, options);
|
|
234
|
+
const task = resolveTaskSelection(state, options);
|
|
235
|
+
const dependencyState = assertTaskReady(task, state);
|
|
236
|
+
let verification = null;
|
|
237
|
+
|
|
238
|
+
if (options.verify) {
|
|
239
|
+
if (!task.metadata.verify) {
|
|
240
|
+
throw new Error(`${task.id} is missing verify command.`);
|
|
241
|
+
}
|
|
242
|
+
verification = await runVerifyCommand(task.metadata.verify, projectRoot);
|
|
243
|
+
if (!verification.ok) {
|
|
244
|
+
await appendTaskEvent(state, {
|
|
245
|
+
action: 'advance_failed',
|
|
246
|
+
taskId: task.id,
|
|
247
|
+
taskTitle: task.title,
|
|
248
|
+
ok: false,
|
|
249
|
+
verify: verification,
|
|
250
|
+
evidence: options.evidence ?? null,
|
|
251
|
+
notes: options.notes ?? null,
|
|
252
|
+
});
|
|
253
|
+
return {
|
|
254
|
+
ok: false,
|
|
255
|
+
action: 'advance',
|
|
256
|
+
projectRoot,
|
|
257
|
+
changeId: state.changeId,
|
|
258
|
+
task,
|
|
259
|
+
dependencyState,
|
|
260
|
+
verification,
|
|
261
|
+
advanced: false,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
await markTaskComplete(task);
|
|
267
|
+
await appendTaskEvent(state, {
|
|
268
|
+
action: 'advance',
|
|
269
|
+
taskId: task.id,
|
|
270
|
+
taskTitle: task.title,
|
|
271
|
+
ok: true,
|
|
272
|
+
verify: verification,
|
|
273
|
+
evidence: options.evidence ?? null,
|
|
274
|
+
notes: options.notes ?? null,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const updatedState = await loadTaskState(projectRoot, { change: state.changeId });
|
|
278
|
+
return {
|
|
279
|
+
ok: true,
|
|
280
|
+
action: 'advance',
|
|
281
|
+
projectRoot,
|
|
282
|
+
changeId: state.changeId,
|
|
283
|
+
task: {
|
|
284
|
+
...task,
|
|
285
|
+
checked: true,
|
|
286
|
+
},
|
|
287
|
+
dependencyState,
|
|
288
|
+
verification,
|
|
289
|
+
advanced: true,
|
|
290
|
+
nextTask: updatedState.summary.nextReady,
|
|
291
|
+
summary: {
|
|
292
|
+
total: updatedState.summary.total,
|
|
293
|
+
completed: updatedState.summary.completed,
|
|
294
|
+
pending: updatedState.summary.pending,
|
|
295
|
+
blocked: updatedState.summary.blocked,
|
|
296
|
+
taskTypes: updatedState.summary.taskTypes,
|
|
297
|
+
implementation: updatedState.summary.implementation,
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
}
|