@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
package/src/discovery.js
ADDED
|
@@ -0,0 +1,709 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { analyzePrdSnapshot, buildPrdSnapshot, formatVersionId } from './prd-core.js';
|
|
4
|
+
import { analyzeOpenSpecTaskVolumes } from './openspec/tasks.js';
|
|
5
|
+
import { legacyOpenSpecDiscoveryDir, openPrdDiscoveryDir, readDiscoveryConfig } from './openspec/paths.js';
|
|
6
|
+
import { appendJsonl, cjoin, exists, readJson, readJsonl, writeJson, writeText } from './fs-utils.js';
|
|
7
|
+
import { collectSourceInventory, shouldIgnoreSourceDirectory } from './source-inventory.js';
|
|
8
|
+
import { compactTimestamp, timestamp } from './time.js';
|
|
9
|
+
|
|
10
|
+
const OPENSPEC_DISCOVERY_MODES = ['brownfield', 'reference', 'requirement'];
|
|
11
|
+
const OPENSPEC_DISCOVERY_COVERAGE_STATUSES = ['pending', 'covered', 'blocked'];
|
|
12
|
+
const OPENSPEC_DISCOVERY_DEFAULT_MAX_ITERATIONS = 10;
|
|
13
|
+
const FLEET_DEFAULT_MAX_DEPTH = 4;
|
|
14
|
+
function slugify(value, fallback = 'item') {
|
|
15
|
+
const slug = String(value ?? '')
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
18
|
+
.replace(/^-+|-+$/g, '')
|
|
19
|
+
.slice(0, 80);
|
|
20
|
+
return slug || fallback;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalizeDiscoveryMode(mode) {
|
|
24
|
+
const normalized = String(mode ?? 'auto').trim().toLowerCase();
|
|
25
|
+
if (!OPENSPEC_DISCOVERY_MODES.includes(normalized)) {
|
|
26
|
+
throw new Error(`Unsupported OpenPrd discovery mode: ${mode}`);
|
|
27
|
+
}
|
|
28
|
+
return normalized;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function resolveDiscoveryMode(projectRoot, options = {}) {
|
|
32
|
+
const requested = String(options.mode ?? 'auto').trim().toLowerCase();
|
|
33
|
+
if (requested && requested !== 'auto') {
|
|
34
|
+
return normalizeDiscoveryMode(requested);
|
|
35
|
+
}
|
|
36
|
+
if (options.reference) {
|
|
37
|
+
return 'reference';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let entries = [];
|
|
41
|
+
try {
|
|
42
|
+
entries = await fs.readdir(projectRoot, { withFileTypes: true });
|
|
43
|
+
} catch {
|
|
44
|
+
return 'requirement';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const hasProjectMaterial = entries.some((entry) => {
|
|
48
|
+
if (shouldIgnoreSourceDirectory(entry.name)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
if (entry.isDirectory()) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
return entry.isFile() && shouldInventorySourceFile(entry.name);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return hasProjectMaterial ? 'brownfield' : 'requirement';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function normalizeDiscoveryMaxIterations(value) {
|
|
61
|
+
const normalized = Number(value ?? OPENSPEC_DISCOVERY_DEFAULT_MAX_ITERATIONS);
|
|
62
|
+
if (!Number.isInteger(normalized) || normalized < 1) {
|
|
63
|
+
throw new Error(`Invalid OpenPrd discovery max iterations: ${value}`);
|
|
64
|
+
}
|
|
65
|
+
return normalized;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function normalizeCoverageStatus(status) {
|
|
69
|
+
const normalized = String(status ?? 'covered').trim().toLowerCase();
|
|
70
|
+
if (!OPENSPEC_DISCOVERY_COVERAGE_STATUSES.includes(normalized)) {
|
|
71
|
+
throw new Error(`Unsupported OpenPrd discovery coverage status: ${status}`);
|
|
72
|
+
}
|
|
73
|
+
return normalized;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function normalizeClaimConfidence(value) {
|
|
77
|
+
const normalized = Number(value ?? 0.7);
|
|
78
|
+
if (!Number.isFinite(normalized) || normalized < 0 || normalized > 1) {
|
|
79
|
+
throw new Error(`Invalid OpenPrd discovery claim confidence: ${value}`);
|
|
80
|
+
}
|
|
81
|
+
return normalized;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function buildDiscoveryRunId(mode, now = new Date()) {
|
|
85
|
+
const stamp = compactTimestamp(now);
|
|
86
|
+
return `${stamp}-${mode}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function claimValuePreview(value) {
|
|
90
|
+
if (Array.isArray(value)) {
|
|
91
|
+
return value.slice(0, 5).map((item) => String(item)).join(', ');
|
|
92
|
+
}
|
|
93
|
+
if (value && typeof value === 'object') {
|
|
94
|
+
return JSON.stringify(value).slice(0, 240);
|
|
95
|
+
}
|
|
96
|
+
return String(value ?? '').slice(0, 240);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function buildSeedClaims(analysis) {
|
|
100
|
+
return analysis.completeFields.slice(0, 80).map((field) => ({
|
|
101
|
+
id: `claim:${slugify(field.path)}`,
|
|
102
|
+
status: 'seeded',
|
|
103
|
+
source: 'openprd-snapshot',
|
|
104
|
+
confidence: 0.7,
|
|
105
|
+
path: field.path,
|
|
106
|
+
summary: `${field.label}: ${claimValuePreview(field.value)}`,
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function buildCoverageItems({ mode, inventory, analysis }) {
|
|
111
|
+
const missingFieldItems = analysis.missingFields.map((field) => ({
|
|
112
|
+
id: `field:${slugify(field.path)}`,
|
|
113
|
+
title: field.label,
|
|
114
|
+
kind: 'missing-prd-field',
|
|
115
|
+
status: 'pending',
|
|
116
|
+
priority: 'high',
|
|
117
|
+
target: field.path,
|
|
118
|
+
prompt: field.prompt,
|
|
119
|
+
source: 'openprd-analysis',
|
|
120
|
+
claimIds: [],
|
|
121
|
+
}));
|
|
122
|
+
|
|
123
|
+
const sourceItems = inventory.files.slice(0, 120).map((file) => ({
|
|
124
|
+
id: `source:${slugify(file.path)}`,
|
|
125
|
+
title: file.path,
|
|
126
|
+
kind: `${file.kind}-evidence`,
|
|
127
|
+
status: 'pending',
|
|
128
|
+
priority: file.kind === 'implementation' || file.kind === 'schema' ? 'medium' : 'low',
|
|
129
|
+
source: file.path,
|
|
130
|
+
claimIds: [],
|
|
131
|
+
}));
|
|
132
|
+
|
|
133
|
+
const items = mode === 'requirement'
|
|
134
|
+
? missingFieldItems
|
|
135
|
+
: [...missingFieldItems, ...sourceItems];
|
|
136
|
+
|
|
137
|
+
if (items.length === 0) {
|
|
138
|
+
items.push({
|
|
139
|
+
id: 'review:openprd-completeness',
|
|
140
|
+
title: 'OpenPrd 完整性评审',
|
|
141
|
+
kind: 'review',
|
|
142
|
+
status: 'pending',
|
|
143
|
+
priority: 'medium',
|
|
144
|
+
source: 'generated',
|
|
145
|
+
claimIds: [],
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const pendingItems = items.filter((item) => item.status === 'pending');
|
|
150
|
+
return {
|
|
151
|
+
version: 1,
|
|
152
|
+
generatedAt: timestamp(),
|
|
153
|
+
mode,
|
|
154
|
+
summary: {
|
|
155
|
+
total: items.length,
|
|
156
|
+
pending: pendingItems.length,
|
|
157
|
+
covered: items.filter((item) => item.status === 'covered').length,
|
|
158
|
+
blocked: items.filter((item) => item.status === 'blocked').length,
|
|
159
|
+
},
|
|
160
|
+
nextPendingItem: pendingItems[0] ?? null,
|
|
161
|
+
items,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function summarizeCoverageItems(items) {
|
|
166
|
+
const pendingItems = items.filter((item) => item.status === 'pending');
|
|
167
|
+
return {
|
|
168
|
+
summary: {
|
|
169
|
+
total: items.length,
|
|
170
|
+
pending: pendingItems.length,
|
|
171
|
+
covered: items.filter((item) => item.status === 'covered').length,
|
|
172
|
+
blocked: items.filter((item) => item.status === 'blocked').length,
|
|
173
|
+
},
|
|
174
|
+
nextPendingItem: pendingItems[0] ?? null,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function refreshCoverageMatrix(coverageMatrix) {
|
|
179
|
+
const { summary, nextPendingItem } = summarizeCoverageItems(coverageMatrix.items ?? []);
|
|
180
|
+
return {
|
|
181
|
+
...coverageMatrix,
|
|
182
|
+
generatedAt: timestamp(),
|
|
183
|
+
summary,
|
|
184
|
+
nextPendingItem,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function renderDiscoveryContext({ mode, projectRoot, sourceRoot, snapshot, analysis, coverageMatrix }) {
|
|
189
|
+
const next = coverageMatrix.nextPendingItem;
|
|
190
|
+
return [
|
|
191
|
+
'# OpenPrd 持续发现上下文',
|
|
192
|
+
'',
|
|
193
|
+
`- 模式: ${mode}`,
|
|
194
|
+
`- 项目根目录: ${projectRoot}`,
|
|
195
|
+
`- 来源根目录: ${sourceRoot}`,
|
|
196
|
+
`- PRD 版本: ${snapshot.versionId}`,
|
|
197
|
+
`- 产品类型: ${snapshot.productType ?? '未分类'}`,
|
|
198
|
+
`- 必填字段完成度: ${analysis.completedRequiredFields}/${analysis.totalRequiredFields}`,
|
|
199
|
+
`- 覆盖项: ${coverageMatrix.summary.pending}/${coverageMatrix.summary.total} 待处理`,
|
|
200
|
+
next ? `- 下一项: ${next.title}` : '- 下一项: 无',
|
|
201
|
+
'',
|
|
202
|
+
'## 执行循环',
|
|
203
|
+
'',
|
|
204
|
+
'1. 从本次运行目录和 OpenPrd 工作区重建上下文。',
|
|
205
|
+
'2. 选择下一个待处理覆盖项。',
|
|
206
|
+
'3. 在写入新的 OpenPrd claim 前先收集证据。',
|
|
207
|
+
'4. 更新 OpenPrd 文档、claims、覆盖状态、开放问题和迭代记录。',
|
|
208
|
+
'5. 只有在覆盖项处理完成、被阻断或达到迭代预算时才停止。',
|
|
209
|
+
'',
|
|
210
|
+
].join('\n');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function renderDiscoveryOpenQuestions(analysis, mode) {
|
|
214
|
+
const questions = analysis.missingFields.map((field) => `- [ ] ${field.prompt} (${field.path})`);
|
|
215
|
+
if (questions.length === 0) {
|
|
216
|
+
questions.push('- [ ] 检查是否仍有隐含行为缺少 OpenPrd 覆盖。');
|
|
217
|
+
}
|
|
218
|
+
return [
|
|
219
|
+
'# 开放问题',
|
|
220
|
+
'',
|
|
221
|
+
`模式: ${mode}`,
|
|
222
|
+
'',
|
|
223
|
+
...questions,
|
|
224
|
+
'',
|
|
225
|
+
].join('\n');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function openspecDiscoveryWorkspaceImpl(projectRoot, options = {}, dependencies = {}) {
|
|
229
|
+
const {
|
|
230
|
+
appendProgress,
|
|
231
|
+
appendWorkflowEvent,
|
|
232
|
+
loadLatestVersionSnapshot,
|
|
233
|
+
loadWorkspace,
|
|
234
|
+
readVersionIndex,
|
|
235
|
+
resolveActiveTemplatePack,
|
|
236
|
+
resolveCurrentProductType,
|
|
237
|
+
} = dependencies;
|
|
238
|
+
if (options.verify) {
|
|
239
|
+
return verifyOpenSpecDiscoveryWorkspace(projectRoot, dependencies);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (options.advance) {
|
|
243
|
+
return advanceOpenSpecDiscoveryWorkspace(projectRoot, options);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (options.resume) {
|
|
247
|
+
return resumeOpenSpecDiscoveryWorkspace(projectRoot);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const mode = await resolveDiscoveryMode(projectRoot, options);
|
|
251
|
+
const maxIterations = normalizeDiscoveryMaxIterations(options.maxIterations);
|
|
252
|
+
const ws = await loadWorkspace(projectRoot);
|
|
253
|
+
if (!(await exists(ws.workspaceRoot))) {
|
|
254
|
+
throw new Error(`Missing workspace: ${ws.workspaceRoot}`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const versionIndex = await readVersionIndex(ws);
|
|
258
|
+
const latest = await loadLatestVersionSnapshot(ws).catch(() => null);
|
|
259
|
+
const currentState = ws.data.currentState ?? {};
|
|
260
|
+
const snapshot = latest?.snapshot ?? buildPrdSnapshot(ws, {
|
|
261
|
+
...currentState,
|
|
262
|
+
versionNumber: currentState.prdVersion ?? (versionIndex.at(-1)?.versionNumber ?? 0),
|
|
263
|
+
versionId: currentState.prdVersion > 0
|
|
264
|
+
? formatVersionId(currentState.prdVersion)
|
|
265
|
+
: (versionIndex.at(-1)?.versionId ?? 'v0000'),
|
|
266
|
+
productType: resolveCurrentProductType(ws),
|
|
267
|
+
templatePack: resolveActiveTemplatePack(ws),
|
|
268
|
+
});
|
|
269
|
+
const analysis = analyzePrdSnapshot(snapshot);
|
|
270
|
+
|
|
271
|
+
const sourceRoot = mode === 'reference' && options.reference
|
|
272
|
+
? path.resolve(projectRoot, options.reference)
|
|
273
|
+
: projectRoot;
|
|
274
|
+
const inventory = await collectSourceInventory(sourceRoot, {
|
|
275
|
+
maxDepth: options.maxDepth,
|
|
276
|
+
maxFiles: options.maxFiles,
|
|
277
|
+
});
|
|
278
|
+
const coverageMatrix = buildCoverageItems({ mode, inventory, analysis });
|
|
279
|
+
const claims = buildSeedClaims(analysis);
|
|
280
|
+
const runId = options.runId ?? buildDiscoveryRunId(mode);
|
|
281
|
+
const discoveryRoot = openPrdDiscoveryDir(projectRoot);
|
|
282
|
+
const runDir = cjoin(discoveryRoot, 'runs', runId);
|
|
283
|
+
const now = timestamp();
|
|
284
|
+
const control = {
|
|
285
|
+
version: 1,
|
|
286
|
+
runId,
|
|
287
|
+
mode,
|
|
288
|
+
status: coverageMatrix.summary.pending > 0 ? 'active' : 'ready_for_review',
|
|
289
|
+
iteration: 1,
|
|
290
|
+
maxIterations,
|
|
291
|
+
createdAt: now,
|
|
292
|
+
updatedAt: now,
|
|
293
|
+
projectRoot,
|
|
294
|
+
openprdWorkspaceRoot: ws.workspaceRoot,
|
|
295
|
+
sourceRoot,
|
|
296
|
+
referencePath: mode === 'reference' ? (options.reference ?? null) : null,
|
|
297
|
+
latestPrdVersion: snapshot.versionId,
|
|
298
|
+
nextAction: coverageMatrix.nextPendingItem
|
|
299
|
+
? `调研 ${coverageMatrix.nextPendingItem.title}`
|
|
300
|
+
: '评审 OpenPrd 完整性',
|
|
301
|
+
};
|
|
302
|
+
const firstIteration = {
|
|
303
|
+
iteration: 1,
|
|
304
|
+
at: now,
|
|
305
|
+
action: 'initialized',
|
|
306
|
+
mode,
|
|
307
|
+
nextCoverageItemId: coverageMatrix.nextPendingItem?.id ?? null,
|
|
308
|
+
pendingCoverageItems: coverageMatrix.summary.pending,
|
|
309
|
+
seededClaims: claims.length,
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
await fs.mkdir(runDir, { recursive: true });
|
|
313
|
+
await writeJson(cjoin(discoveryRoot, 'current.json'), {
|
|
314
|
+
version: 1,
|
|
315
|
+
activeRunId: runId,
|
|
316
|
+
activeRunDir: runDir,
|
|
317
|
+
mode,
|
|
318
|
+
updatedAt: now,
|
|
319
|
+
});
|
|
320
|
+
await writeText(cjoin(discoveryRoot, 'README.md'), [
|
|
321
|
+
'# OpenPrd 持续发现',
|
|
322
|
+
'',
|
|
323
|
+
'本目录保存 OpenPrd 持续发现状态。',
|
|
324
|
+
'',
|
|
325
|
+
'## 文件',
|
|
326
|
+
'',
|
|
327
|
+
'- `control.json` 记录当前循环状态和迭代预算。',
|
|
328
|
+
'- `coverage-matrix.json` 记录仍需沉淀到 OpenPrd specs 和 tasks 的覆盖项。',
|
|
329
|
+
'- `claims.jsonl` 记录有证据支撑的需求声明。',
|
|
330
|
+
'- `open-questions.md` 保持用户或产品开放问题可见。',
|
|
331
|
+
'- `iterations.jsonl` 记录每一轮循环。',
|
|
332
|
+
'',
|
|
333
|
+
'## 任务拆分',
|
|
334
|
+
'',
|
|
335
|
+
'- `tasks.md` 保持为第一个任务入口。',
|
|
336
|
+
'- 长变更继续使用 `tasks-002.md`、`tasks-003.md` 等文件。',
|
|
337
|
+
'- 每个非最终任务文件的最后一个 checkbox 必须交接到下一个文件。',
|
|
338
|
+
'- 项目可以通过 `.openprd/discovery/config.json` 的 `taskSharding.maxItemsPerFile` 覆盖单文件任务上限。',
|
|
339
|
+
'- 结构化任务只使用稳定任务 id 下的 `deps`、`done` 和 `verify` 元数据。',
|
|
340
|
+
'',
|
|
341
|
+
'```md',
|
|
342
|
+
'- [ ] T009.07 迁移历史数据库导入预览',
|
|
343
|
+
' - deps: T001.14, T007.06',
|
|
344
|
+
' - done: 预览展示数量、冲突、跳过项和警告',
|
|
345
|
+
' - verify: npm run test -- migration',
|
|
346
|
+
'```',
|
|
347
|
+
'',
|
|
348
|
+
'- 没有依赖时省略 `deps`。',
|
|
349
|
+
'',
|
|
350
|
+
].join('\n'));
|
|
351
|
+
await writeJson(cjoin(runDir, 'control.json'), control);
|
|
352
|
+
await writeText(cjoin(runDir, 'context.md'), renderDiscoveryContext({
|
|
353
|
+
mode,
|
|
354
|
+
projectRoot,
|
|
355
|
+
sourceRoot,
|
|
356
|
+
snapshot,
|
|
357
|
+
analysis,
|
|
358
|
+
coverageMatrix,
|
|
359
|
+
}));
|
|
360
|
+
await writeJson(cjoin(runDir, 'source-inventory.json'), inventory);
|
|
361
|
+
await writeJson(cjoin(runDir, 'coverage-matrix.json'), coverageMatrix);
|
|
362
|
+
await writeText(cjoin(runDir, 'claims.jsonl'), `${claims.map((claim) => JSON.stringify(claim)).join('\n')}${claims.length > 0 ? '\n' : ''}`);
|
|
363
|
+
await writeText(cjoin(runDir, 'open-questions.md'), renderDiscoveryOpenQuestions(analysis, mode));
|
|
364
|
+
await writeText(cjoin(runDir, 'iterations.jsonl'), `${JSON.stringify(firstIteration)}\n`);
|
|
365
|
+
|
|
366
|
+
await appendWorkflowEvent(ws, 'openspec_discovery_initialized', {
|
|
367
|
+
runId,
|
|
368
|
+
mode,
|
|
369
|
+
pendingCoverageItems: coverageMatrix.summary.pending,
|
|
370
|
+
});
|
|
371
|
+
await appendProgress(ws, [
|
|
372
|
+
`已初始化 OpenPrd 持续发现运行 ${runId}。`,
|
|
373
|
+
`模式: ${mode}。`,
|
|
374
|
+
`待处理覆盖项: ${coverageMatrix.summary.pending}。`,
|
|
375
|
+
]);
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
ok: true,
|
|
379
|
+
resumed: false,
|
|
380
|
+
ws,
|
|
381
|
+
runId,
|
|
382
|
+
runDir,
|
|
383
|
+
discoveryRoot,
|
|
384
|
+
control,
|
|
385
|
+
inventory,
|
|
386
|
+
coverageMatrix,
|
|
387
|
+
claims,
|
|
388
|
+
openQuestionsPath: cjoin(runDir, 'open-questions.md'),
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function loadOpenSpecDiscoveryRun(projectRoot) {
|
|
393
|
+
let discoveryRoot = openPrdDiscoveryDir(projectRoot);
|
|
394
|
+
if (!(await exists(cjoin(discoveryRoot, 'current.json')))) {
|
|
395
|
+
const legacyRoot = legacyOpenSpecDiscoveryDir(projectRoot);
|
|
396
|
+
if (await exists(cjoin(legacyRoot, 'current.json'))) {
|
|
397
|
+
discoveryRoot = legacyRoot;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
const currentPath = cjoin(discoveryRoot, 'current.json');
|
|
401
|
+
if (!(await exists(currentPath))) {
|
|
402
|
+
throw new Error(`Missing OpenPrd discovery state: ${currentPath}`);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const current = await readJson(currentPath);
|
|
406
|
+
const runDir = current.activeRunDir;
|
|
407
|
+
const control = await readJson(cjoin(runDir, 'control.json'));
|
|
408
|
+
const inventory = await readJson(cjoin(runDir, 'source-inventory.json'));
|
|
409
|
+
const coverageMatrix = await readJson(cjoin(runDir, 'coverage-matrix.json'));
|
|
410
|
+
const claims = await readJsonl(cjoin(runDir, 'claims.jsonl')).catch(() => []);
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
current,
|
|
414
|
+
runId: current.activeRunId,
|
|
415
|
+
runDir,
|
|
416
|
+
discoveryRoot,
|
|
417
|
+
control,
|
|
418
|
+
inventory,
|
|
419
|
+
coverageMatrix,
|
|
420
|
+
claims,
|
|
421
|
+
openQuestionsPath: cjoin(runDir, 'open-questions.md'),
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async function resumeOpenSpecDiscoveryWorkspace(projectRoot) {
|
|
426
|
+
const state = await loadOpenSpecDiscoveryRun(projectRoot);
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
ok: true,
|
|
430
|
+
resumed: true,
|
|
431
|
+
runId: state.runId,
|
|
432
|
+
runDir: state.runDir,
|
|
433
|
+
discoveryRoot: state.discoveryRoot,
|
|
434
|
+
control: state.control,
|
|
435
|
+
inventory: state.inventory,
|
|
436
|
+
coverageMatrix: state.coverageMatrix,
|
|
437
|
+
claims: state.claims,
|
|
438
|
+
openQuestionsPath: state.openQuestionsPath,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
async function advanceOpenSpecDiscoveryWorkspace(projectRoot, options = {}) {
|
|
443
|
+
const state = await loadOpenSpecDiscoveryRun(projectRoot);
|
|
444
|
+
const coverageMatrix = state.coverageMatrix;
|
|
445
|
+
const items = Array.isArray(coverageMatrix.items) ? coverageMatrix.items : [];
|
|
446
|
+
const nextPending = coverageMatrix.nextPendingItem ?? items.find((item) => item.status === 'pending');
|
|
447
|
+
const itemId = options.item ?? nextPending?.id;
|
|
448
|
+
if (!itemId) {
|
|
449
|
+
throw new Error('No pending OpenPrd discovery coverage item to advance.');
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const itemIndex = items.findIndex((item) => item.id === itemId);
|
|
453
|
+
if (itemIndex < 0) {
|
|
454
|
+
throw new Error(`Unknown OpenPrd discovery coverage item: ${itemId}`);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const status = normalizeCoverageStatus(options.status);
|
|
458
|
+
const claimSummary = options.claim ?? null;
|
|
459
|
+
const notes = options.notes ?? null;
|
|
460
|
+
if (status === 'covered' && !claimSummary && !notes) {
|
|
461
|
+
throw new Error('Covering an OpenPrd discovery item requires --claim or --notes.');
|
|
462
|
+
}
|
|
463
|
+
if (status === 'blocked' && !notes && !claimSummary) {
|
|
464
|
+
throw new Error('Blocking an OpenPrd discovery item requires --notes or --claim.');
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const now = timestamp();
|
|
468
|
+
const item = {
|
|
469
|
+
...items[itemIndex],
|
|
470
|
+
status,
|
|
471
|
+
updatedAt: now,
|
|
472
|
+
};
|
|
473
|
+
if (notes) {
|
|
474
|
+
item.notes = notes;
|
|
475
|
+
}
|
|
476
|
+
if (options.evidence) {
|
|
477
|
+
item.evidence = [...new Set([...(item.evidence ?? []), options.evidence])];
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
let claim = null;
|
|
481
|
+
if (claimSummary) {
|
|
482
|
+
const nextIteration = Number(state.control.iteration ?? 0) + 1;
|
|
483
|
+
claim = {
|
|
484
|
+
id: `claim:${slugify(item.id)}:${nextIteration}`,
|
|
485
|
+
status: 'active',
|
|
486
|
+
source: options.source ?? (options.evidence ? 'project-derived' : 'agent-inferred'),
|
|
487
|
+
confidence: normalizeClaimConfidence(options.confidence),
|
|
488
|
+
coverageItemId: item.id,
|
|
489
|
+
summary: claimSummary,
|
|
490
|
+
evidence: options.evidence ? [options.evidence] : [],
|
|
491
|
+
notes,
|
|
492
|
+
createdAt: now,
|
|
493
|
+
};
|
|
494
|
+
item.claimIds = [...new Set([...(item.claimIds ?? []), claim.id])];
|
|
495
|
+
await appendJsonl(cjoin(state.runDir, 'claims.jsonl'), claim);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
items[itemIndex] = item;
|
|
499
|
+
const updatedCoverageMatrix = refreshCoverageMatrix({
|
|
500
|
+
...coverageMatrix,
|
|
501
|
+
items,
|
|
502
|
+
});
|
|
503
|
+
const nextIteration = Number(state.control.iteration ?? 0) + 1;
|
|
504
|
+
const updatedControl = {
|
|
505
|
+
...state.control,
|
|
506
|
+
iteration: nextIteration,
|
|
507
|
+
updatedAt: now,
|
|
508
|
+
status: updatedCoverageMatrix.summary.pending > 0 ? 'active' : 'ready_for_review',
|
|
509
|
+
nextAction: updatedCoverageMatrix.nextPendingItem
|
|
510
|
+
? `调研 ${updatedCoverageMatrix.nextPendingItem.title}`
|
|
511
|
+
: '评审 OpenPrd 完整性',
|
|
512
|
+
};
|
|
513
|
+
const iterationEntry = {
|
|
514
|
+
iteration: nextIteration,
|
|
515
|
+
at: now,
|
|
516
|
+
action: 'advance',
|
|
517
|
+
coverageItemId: item.id,
|
|
518
|
+
status,
|
|
519
|
+
claimId: claim?.id ?? null,
|
|
520
|
+
evidence: options.evidence ?? null,
|
|
521
|
+
notes,
|
|
522
|
+
pendingCoverageItems: updatedCoverageMatrix.summary.pending,
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
await writeJson(cjoin(state.runDir, 'coverage-matrix.json'), updatedCoverageMatrix);
|
|
526
|
+
await writeJson(cjoin(state.runDir, 'control.json'), updatedControl);
|
|
527
|
+
await appendJsonl(cjoin(state.runDir, 'iterations.jsonl'), iterationEntry);
|
|
528
|
+
|
|
529
|
+
return {
|
|
530
|
+
ok: true,
|
|
531
|
+
advanced: true,
|
|
532
|
+
runId: state.runId,
|
|
533
|
+
runDir: state.runDir,
|
|
534
|
+
discoveryRoot: state.discoveryRoot,
|
|
535
|
+
control: updatedControl,
|
|
536
|
+
inventory: state.inventory,
|
|
537
|
+
coverageMatrix: updatedCoverageMatrix,
|
|
538
|
+
claims: claim ? [...state.claims, claim] : state.claims,
|
|
539
|
+
advancedItem: item,
|
|
540
|
+
claim,
|
|
541
|
+
openQuestionsPath: state.openQuestionsPath,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function verifyOpenSpecDiscoveryState(state) {
|
|
546
|
+
const errors = [];
|
|
547
|
+
const warnings = [];
|
|
548
|
+
const checks = [];
|
|
549
|
+
const items = Array.isArray(state.coverageMatrix.items) ? state.coverageMatrix.items : [];
|
|
550
|
+
|
|
551
|
+
if (!state.control.runId) {
|
|
552
|
+
errors.push('control.json is missing runId.');
|
|
553
|
+
}
|
|
554
|
+
if (!OPENSPEC_DISCOVERY_MODES.includes(state.control.mode)) {
|
|
555
|
+
errors.push(`control.json has unsupported mode: ${state.control.mode}`);
|
|
556
|
+
}
|
|
557
|
+
if (!Number.isInteger(Number(state.control.iteration)) || Number(state.control.iteration) < 1) {
|
|
558
|
+
errors.push('control.json has invalid iteration.');
|
|
559
|
+
}
|
|
560
|
+
if (!Number.isInteger(Number(state.control.maxIterations)) || Number(state.control.maxIterations) < 1) {
|
|
561
|
+
errors.push('control.json has invalid maxIterations.');
|
|
562
|
+
}
|
|
563
|
+
if (Number(state.control.iteration) > Number(state.control.maxIterations)) {
|
|
564
|
+
warnings.push('OpenPrd discovery iteration budget has been reached.');
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (!Array.isArray(state.coverageMatrix.items)) {
|
|
568
|
+
errors.push('coverage-matrix.json is missing items.');
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
for (const item of items) {
|
|
572
|
+
if (!item.id) {
|
|
573
|
+
errors.push('coverage item is missing id.');
|
|
574
|
+
}
|
|
575
|
+
if (!OPENSPEC_DISCOVERY_COVERAGE_STATUSES.includes(item.status)) {
|
|
576
|
+
errors.push(`coverage item ${item.id ?? '<unknown>'} has unsupported status: ${item.status}`);
|
|
577
|
+
}
|
|
578
|
+
if (item.status === 'covered' && (!Array.isArray(item.claimIds) || item.claimIds.length === 0) && !item.notes) {
|
|
579
|
+
warnings.push(`covered item ${item.id} has no claimIds or notes.`);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const recomputed = summarizeCoverageItems(items);
|
|
584
|
+
const storedSummary = state.coverageMatrix.summary ?? {};
|
|
585
|
+
for (const key of ['total', 'pending', 'covered', 'blocked']) {
|
|
586
|
+
if (Number(storedSummary[key] ?? 0) !== recomputed.summary[key]) {
|
|
587
|
+
errors.push(`coverage summary mismatch for ${key}: expected ${recomputed.summary[key]}, found ${storedSummary[key]}`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
for (const claim of state.claims) {
|
|
592
|
+
if (!claim.id) {
|
|
593
|
+
errors.push('claim is missing id.');
|
|
594
|
+
}
|
|
595
|
+
if (!claim.summary) {
|
|
596
|
+
errors.push(`claim ${claim.id ?? '<unknown>'} is missing summary.`);
|
|
597
|
+
}
|
|
598
|
+
if (!claim.source) {
|
|
599
|
+
warnings.push(`claim ${claim.id ?? '<unknown>'} is missing source.`);
|
|
600
|
+
}
|
|
601
|
+
if (claim.source !== 'user-confirmed' && claim.source !== 'openprd-snapshot' && (!Array.isArray(claim.evidence) || claim.evidence.length === 0)) {
|
|
602
|
+
warnings.push(`claim ${claim.id ?? '<unknown>'} has no evidence path.`);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
checks.push(`Coverage: ${recomputed.summary.covered}/${recomputed.summary.total} covered, ${recomputed.summary.pending} pending, ${recomputed.summary.blocked} blocked.`);
|
|
607
|
+
checks.push(`Claims: ${state.claims.length}.`);
|
|
608
|
+
|
|
609
|
+
return {
|
|
610
|
+
valid: errors.length === 0,
|
|
611
|
+
complete: recomputed.summary.pending === 0,
|
|
612
|
+
errors,
|
|
613
|
+
warnings,
|
|
614
|
+
checks,
|
|
615
|
+
coverage: recomputed.summary,
|
|
616
|
+
nextPendingItem: recomputed.nextPendingItem,
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
async function verifyOpenSpecDiscoveryWorkspace(projectRoot, dependencies = {}) {
|
|
621
|
+
const {
|
|
622
|
+
checkStandardsWorkspace,
|
|
623
|
+
validateOpenSpecChangeWorkspace,
|
|
624
|
+
} = dependencies;
|
|
625
|
+
const state = await loadOpenSpecDiscoveryRun(projectRoot);
|
|
626
|
+
const verification = verifyOpenSpecDiscoveryState(state);
|
|
627
|
+
const discoveryConfig = await readDiscoveryConfig(projectRoot, readJson);
|
|
628
|
+
let openSpecChange = null;
|
|
629
|
+
let taskVolume = null;
|
|
630
|
+
let standards = null;
|
|
631
|
+
if (discoveryConfig?.activeChange) {
|
|
632
|
+
openSpecChange = await validateOpenSpecChangeWorkspace(projectRoot, {
|
|
633
|
+
change: discoveryConfig.activeChange,
|
|
634
|
+
sourceManuals: false,
|
|
635
|
+
docsContent: false,
|
|
636
|
+
});
|
|
637
|
+
taskVolume = openSpecChange.taskVolume;
|
|
638
|
+
standards = openSpecChange.standards ?? null;
|
|
639
|
+
verification.errors.push(...openSpecChange.errors);
|
|
640
|
+
verification.warnings.push(...openSpecChange.warnings);
|
|
641
|
+
verification.checks.push(...openSpecChange.checks);
|
|
642
|
+
} else {
|
|
643
|
+
taskVolume = await analyzeOpenSpecTaskVolumes(projectRoot);
|
|
644
|
+
verification.errors.push(...taskVolume.errors);
|
|
645
|
+
verification.checks.push(...taskVolume.checks);
|
|
646
|
+
standards = await checkStandardsWorkspace(projectRoot, {
|
|
647
|
+
optional: !(await exists(cjoin(projectRoot, '.openprd'))),
|
|
648
|
+
sourceManuals: false,
|
|
649
|
+
docsContent: false,
|
|
650
|
+
});
|
|
651
|
+
if (!standards.skipped) {
|
|
652
|
+
verification.errors.push(...standards.errors);
|
|
653
|
+
verification.warnings.push(...standards.warnings);
|
|
654
|
+
verification.checks.push(...standards.checks);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
verification.valid = verification.errors.length === 0;
|
|
658
|
+
const now = timestamp();
|
|
659
|
+
const updatedControl = {
|
|
660
|
+
...state.control,
|
|
661
|
+
status: verification.complete && verification.valid ? 'ready_for_review' : state.control.status,
|
|
662
|
+
lastVerifiedAt: now,
|
|
663
|
+
updatedAt: now,
|
|
664
|
+
};
|
|
665
|
+
await writeJson(cjoin(state.runDir, 'control.json'), updatedControl);
|
|
666
|
+
await appendJsonl(cjoin(state.runDir, 'iterations.jsonl'), {
|
|
667
|
+
iteration: state.control.iteration,
|
|
668
|
+
at: now,
|
|
669
|
+
action: 'verify',
|
|
670
|
+
valid: verification.valid,
|
|
671
|
+
complete: verification.complete,
|
|
672
|
+
errors: verification.errors.length,
|
|
673
|
+
warnings: verification.warnings.length,
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
return {
|
|
677
|
+
ok: verification.valid,
|
|
678
|
+
verified: true,
|
|
679
|
+
runId: state.runId,
|
|
680
|
+
runDir: state.runDir,
|
|
681
|
+
discoveryRoot: state.discoveryRoot,
|
|
682
|
+
control: updatedControl,
|
|
683
|
+
inventory: state.inventory,
|
|
684
|
+
coverageMatrix: state.coverageMatrix,
|
|
685
|
+
claims: state.claims,
|
|
686
|
+
verification,
|
|
687
|
+
openSpecChange,
|
|
688
|
+
taskVolume,
|
|
689
|
+
standards,
|
|
690
|
+
openQuestionsPath: state.openQuestionsPath,
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
function createOpenSpecDiscoveryWorkspace(dependencies) {
|
|
696
|
+
return {
|
|
697
|
+
openspecDiscoveryWorkspace(projectRoot, options = {}) {
|
|
698
|
+
return openspecDiscoveryWorkspaceImpl(projectRoot, options, dependencies);
|
|
699
|
+
},
|
|
700
|
+
resumeOpenSpecDiscoveryWorkspace(projectRoot) {
|
|
701
|
+
return resumeOpenSpecDiscoveryWorkspace(projectRoot);
|
|
702
|
+
},
|
|
703
|
+
verifyOpenSpecDiscoveryWorkspace(projectRoot) {
|
|
704
|
+
return verifyOpenSpecDiscoveryWorkspace(projectRoot, dependencies);
|
|
705
|
+
},
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
export { createOpenSpecDiscoveryWorkspace };
|