@openprd/cli 0.1.10 → 0.1.18
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/benchmarks/index.md +17 -15
- package/.openprd/benchmarks/sources.yaml +53 -1
- package/.openprd/changes/adaptive-project-framing/.openprd.yaml +2 -0
- package/.openprd/changes/adaptive-project-framing/design.md +47 -0
- package/.openprd/changes/adaptive-project-framing/proposal.md +35 -0
- package/.openprd/changes/adaptive-project-framing/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/adaptive-project-framing/task-events.jsonl +20 -0
- package/.openprd/changes/adaptive-project-framing/tasks.md +340 -0
- package/.openprd/changes/docs-anti-pollution-and-design-direction-review/.openprd.yaml +2 -0
- package/.openprd/changes/docs-anti-pollution-and-design-direction-review/design.md +34 -0
- package/.openprd/changes/docs-anti-pollution-and-design-direction-review/proposal.md +23 -0
- package/.openprd/changes/docs-anti-pollution-and-design-direction-review/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/docs-anti-pollution-and-design-direction-review/task-events.jsonl +14 -0
- package/.openprd/changes/docs-anti-pollution-and-design-direction-review/tasks.md +238 -0
- package/.openprd/changes/loop-isolated-worktree-commit/.openprd.yaml +2 -0
- package/.openprd/changes/loop-isolated-worktree-commit/design.md +53 -0
- package/.openprd/changes/loop-isolated-worktree-commit/proposal.md +38 -0
- package/.openprd/changes/loop-isolated-worktree-commit/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/loop-isolated-worktree-commit/task-events.jsonl +22 -0
- package/.openprd/changes/loop-isolated-worktree-commit/tasks.md +357 -0
- package/.openprd/changes/openprd/.openprd.yaml +2 -0
- package/.openprd/changes/openprd/design.md +46 -0
- package/.openprd/changes/openprd/proposal.md +36 -0
- package/.openprd/changes/openprd/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/openprd/task-events.jsonl +23 -0
- package/.openprd/changes/openprd/tasks.md +242 -0
- package/.openprd/changes/pm-human-in-the-loop/.openprd.yaml +2 -0
- package/.openprd/changes/pm-human-in-the-loop/design.md +38 -0
- package/.openprd/changes/pm-human-in-the-loop/proposal.md +29 -0
- package/.openprd/changes/pm-human-in-the-loop/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/pm-human-in-the-loop/task-events.jsonl +18 -0
- package/.openprd/changes/pm-human-in-the-loop/tasks.md +306 -0
- package/.openprd/changes/test-strategy-router/.openprd.yaml +2 -0
- package/.openprd/changes/test-strategy-router/design.md +65 -0
- package/.openprd/changes/test-strategy-router/proposal.md +45 -0
- package/.openprd/changes/test-strategy-router/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/test-strategy-router/task-events.jsonl +1 -0
- package/.openprd/changes/test-strategy-router/tasks.md +132 -0
- package/.openprd/changes/verify-boundaries-and-historical-refresh/.openprd.yaml +2 -0
- package/.openprd/changes/verify-boundaries-and-historical-refresh/design.md +28 -0
- package/.openprd/changes/verify-boundaries-and-historical-refresh/proposal.md +23 -0
- package/.openprd/changes/verify-boundaries-and-historical-refresh/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/verify-boundaries-and-historical-refresh/task-events.jsonl +11 -0
- package/.openprd/changes/verify-boundaries-and-historical-refresh/tasks.md +66 -0
- package/.openprd/changes/vibe-coding-dev-check/.openprd.yaml +2 -0
- package/.openprd/changes/vibe-coding-dev-check/design.md +37 -0
- package/.openprd/changes/vibe-coding-dev-check/proposal.md +28 -0
- package/.openprd/changes/vibe-coding-dev-check/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/vibe-coding-dev-check/task-events.jsonl +18 -0
- package/.openprd/changes/vibe-coding-dev-check/tasks.md +306 -0
- package/.openprd/design/README.md +11 -6
- package/.openprd/design/README_EN.md +4 -1
- package/.openprd/design/active/asset-spec.md +6 -0
- package/.openprd/design/active/direction-plan.md +5 -5
- package/.openprd/design/active/selected-direction.md +2 -0
- package/.openprd/design/anti-slop.md +26 -1
- package/.openprd/design/assets/surface-presets.md +6 -0
- package/.openprd/design/checklists/ui-quality-gate.md +7 -0
- package/.openprd/design/lenses/frontend-lenses.md +28 -0
- package/.openprd/design/recipes/content-experience.md +8 -1
- package/.openprd/design/recipes/operational-tool.md +7 -0
- package/.openprd/design/recipes/product-launch.md +8 -1
- package/.openprd/design/templates/README.md +6 -3
- package/.openprd/design/templates/README_EN.md +6 -3
- package/.openprd/design/templates/ops-dashboard.html +8 -1
- package/.openprd/design/templates/product-launch.html +2 -1
- package/.openprd/design/themes/theme-catalog.json +23 -5
- package/.openprd/engagements/active/prd.md +103 -100
- package/.openprd/schema/prd.schema.yaml +15 -0
- package/.openprd/standards/config.json +7 -1
- package/.openprd/templates/base/intake.md +13 -0
- package/.openprd/templates/base/prd.md +15 -0
- package/AGENTS.md +14 -4
- package/README.md +107 -14
- package/README_EN.md +131 -13
- package/package.json +1 -1
- package/scripts/dev-check-wrapup-copy.mjs +46 -21
- package/skills/openprd-benchmark-router/SKILL.md +2 -1
- package/skills/openprd-diagram-review/SKILL.md +27 -2
- package/skills/openprd-diagram-review/references/explanation-svg-patterns.md +131 -0
- package/skills/openprd-discovery-loop/SKILL.md +2 -2
- package/skills/openprd-frontend-design/SKILL.md +44 -20
- package/skills/openprd-frontend-design/references/design-asset-contract.md +6 -0
- package/skills/openprd-frontend-design/references/direction-engine.md +29 -4
- package/skills/openprd-harness/SKILL.md +22 -16
- package/skills/openprd-quality/SKILL.md +10 -8
- package/skills/openprd-requirement-intake/SKILL.md +5 -3
- package/skills/openprd-requirement-intake/references/prd-template-lenses.md +2 -2
- package/skills/openprd-requirement-intake/references/startup-validation-lens.md +98 -13
- package/skills/openprd-router/SKILL.md +2 -1
- package/skills/openprd-shared/SKILL.md +27 -17
- package/skills/openprd-standards/SKILL.md +4 -2
- package/src/agent-canonical-content.js +808 -0
- package/src/agent-integration.js +259 -778
- package/src/brainstorm-artifacts.js +156 -8
- package/src/brainstorm-presentation.js +14 -4
- package/src/brainstorm.js +165 -2
- package/src/canvas-app.html.js +2910 -0
- package/src/canvas-i18n.js +274 -0
- package/src/canvas-workspace.js +2271 -0
- package/src/cli/args.js +72 -4
- package/src/cli/basic-print.js +3 -0
- package/src/cli/canvas-print.js +106 -0
- package/src/cli/doctor-print.js +20 -0
- package/src/cli/print.js +2 -0
- package/src/cli/quality-commands.js +222 -0
- package/src/cli/quality-print.js +37 -5
- package/src/cli/run-print.js +5 -0
- package/src/cli/workflow-print.js +3 -0
- package/src/codex-app-server-relay.js +251 -0
- package/src/codex-hook-runner-template.mjs +963 -65
- package/src/design-starter-support.js +2 -2
- package/src/design-starter.js +39 -3
- package/src/dev-standards.js +125 -10
- package/src/diagram-core.js +390 -171
- package/src/discovery.js +2 -8
- package/src/docs-compact.js +125 -0
- package/src/execution-strategy.js +1 -1
- package/src/fleet.js +9 -3
- package/src/guide.js +227 -0
- package/src/html-artifacts.js +825 -51
- package/src/knowledge.js +101 -20
- package/src/language-policy.js +63 -3
- package/src/learning-review.js +1 -1
- package/src/loop.js +1 -1
- package/src/openprd.js +68 -131
- package/src/openspec/change-validate.js +2 -2
- package/src/openspec/constants.js +6 -7
- package/src/openspec/execute.js +2 -2
- package/src/openspec/generate.js +318 -102
- package/src/openspec/migration.js +168 -0
- package/src/openspec/paths.js +1 -22
- package/src/prd-core.js +102 -3
- package/src/quality-html-artifact.js +30 -5
- package/src/quality-visual-review.js +257 -14
- package/src/quality.js +66 -18
- package/src/review-presentation.js +60 -0
- package/src/run-harness.js +41 -3
- package/src/session-evidence.js +363 -0
- package/src/standards.js +64 -0
- package/src/test-strategy.js +7 -4
- package/src/visual-compare-centering.js +622 -0
- package/src/visual-compare-core.js +1123 -0
- package/src/visual-compare.js +620 -698
- package/src/workspace-core.js +132 -4
- package/src/workspace-workflow.js +172 -10
package/src/knowledge.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
1
2
|
import fs from 'node:fs/promises';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { appendJsonl, cjoin, exists, readJson, readJsonl, writeJson, writeText } from './fs-utils.js';
|
|
4
5
|
import { resolveQualityLearningSource } from './quality-learning.js';
|
|
6
|
+
import {
|
|
7
|
+
buildSessionEvidenceReviewSource,
|
|
8
|
+
extractSessionEvidenceRecords,
|
|
9
|
+
filterGenericFrameworkTokens,
|
|
10
|
+
normalizeSessionEvidenceRecords,
|
|
11
|
+
sanitizeEvidenceText,
|
|
12
|
+
} from './session-evidence.js';
|
|
5
13
|
import { timestamp } from './time.js';
|
|
6
14
|
|
|
7
15
|
const KNOWLEDGE_DIR = cjoin('.openprd', 'knowledge');
|
|
@@ -210,7 +218,7 @@ function normalizeSearchTokens(value) {
|
|
|
210
218
|
.map((item) => item.trim())
|
|
211
219
|
.filter((item) => item.length >= 3);
|
|
212
220
|
const hanTokens = String(value ?? '').match(/[\u4e00-\u9fa5]{2,}/g) ?? [];
|
|
213
|
-
return uniq([...asciiTokens, ...hanTokens]);
|
|
221
|
+
return filterGenericFrameworkTokens(uniq([...asciiTokens, ...hanTokens]));
|
|
214
222
|
}
|
|
215
223
|
|
|
216
224
|
function sortByLength(items = []) {
|
|
@@ -252,16 +260,17 @@ function scoreQueryAgainstFields(queryText, queryTokens, fields = []) {
|
|
|
252
260
|
let score = 0;
|
|
253
261
|
const matchedOn = [];
|
|
254
262
|
for (const field of fields) {
|
|
255
|
-
const text =
|
|
263
|
+
const text = sanitizeEvidenceText(field);
|
|
256
264
|
if (!text) continue;
|
|
257
265
|
const normalized = normalizeSearchText(text);
|
|
258
266
|
if (!normalized) continue;
|
|
267
|
+
const fieldTokens = normalizeSearchTokens(text);
|
|
268
|
+
if (fieldTokens.length === 0) continue;
|
|
259
269
|
let matched = false;
|
|
260
270
|
if (normalized.length >= 6 && queryText.includes(normalized)) {
|
|
261
271
|
matched = true;
|
|
262
272
|
score += normalized.length >= 18 ? 10 : 7;
|
|
263
273
|
} else {
|
|
264
|
-
const fieldTokens = normalizeSearchTokens(text);
|
|
265
274
|
const overlap = fieldTokens.filter((token) => queryTokens.includes(token));
|
|
266
275
|
if (overlap.length > 0) {
|
|
267
276
|
matched = true;
|
|
@@ -858,16 +867,56 @@ function deriveKnowledgeNames(source, options = {}) {
|
|
|
858
867
|
return { incidentId, patternId, skillName };
|
|
859
868
|
}
|
|
860
869
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
870
|
+
const LOW_SIGNAL_TITLE_PATTERN = /^(只回复|回复|继续|可以|好的|没问题|嗯|哦|行|是|对|确认|同意|执行|开始|ok|okay|yes|done|go)([\s,。,.!!??]|$)/i;
|
|
871
|
+
|
|
872
|
+
function titleLooksLikeRawInstruction(text) {
|
|
873
|
+
const value = String(text ?? '').trim();
|
|
874
|
+
if (!value) return true;
|
|
875
|
+
if (value.length < 8) return true;
|
|
876
|
+
return LOW_SIGNAL_TITLE_PATTERN.test(value);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function describeTouchedFilesTitle(touchedFiles) {
|
|
880
|
+
const files = [...new Set((touchedFiles ?? []).map((file) => String(file ?? '').trim()).filter(Boolean))];
|
|
881
|
+
if (files.length === 0) return null;
|
|
882
|
+
const primary = files[0].split('/').filter(Boolean).at(-1);
|
|
883
|
+
return files.length > 1
|
|
884
|
+
? `围绕 ${primary} 等 ${files.length} 个文件的实现经验`
|
|
885
|
+
: `围绕 ${primary} 的实现经验`;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function buildTurnReviewTitle(raw, source, touchedFiles = []) {
|
|
889
|
+
const structured = firstString(raw?.title, raw?.summary?.title, source.title);
|
|
890
|
+
if (structured && !titleLooksLikeRawInstruction(structured)) return structured;
|
|
891
|
+
const rootCause = source.rootCauseCandidates?.[0]?.title;
|
|
892
|
+
if (rootCause && !titleLooksLikeRawInstruction(rootCause)) return rootCause;
|
|
893
|
+
const prompt = firstString(raw?.promptPreview, raw?.prompt);
|
|
894
|
+
if (prompt && !titleLooksLikeRawInstruction(prompt)) return prompt;
|
|
895
|
+
return describeTouchedFilesTitle(touchedFiles)
|
|
896
|
+
?? firstString(structured, source.sourceId, '项目经验草案')
|
|
897
|
+
?? '项目经验草案';
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function knowledgeCandidateFingerprint(categories = [], touchedFiles = [], sourceSeed = null) {
|
|
901
|
+
const normalizedCategories = [...new Set((categories ?? []).map((item) => String(item ?? '').trim()).filter(Boolean))].sort();
|
|
902
|
+
const normalizedFiles = [...new Set((touchedFiles ?? []).map((item) => String(item ?? '').trim()).filter(Boolean))].sort();
|
|
903
|
+
const normalizedSourceSeed = String(sourceSeed ?? '').trim();
|
|
904
|
+
return crypto.createHash('sha256').update(JSON.stringify([normalizedCategories, normalizedFiles, normalizedSourceSeed])).digest('hex').slice(0, 16);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
async function findPendingCandidateByFingerprint(projectRoot, fingerprint, excludeId) {
|
|
908
|
+
const index = await readKnowledgeIndex(projectRoot);
|
|
909
|
+
for (const entry of index.candidates ?? []) {
|
|
910
|
+
if (!entry?.candidateId || entry.candidateId === excludeId) continue;
|
|
911
|
+
const candidate = await readCandidateById(projectRoot, entry.candidateId);
|
|
912
|
+
if (!candidate || !isPendingKnowledgeCandidateStatus(normalizeCandidateStatus(candidate.status))) continue;
|
|
913
|
+
const existingFingerprint = candidate.fingerprint
|
|
914
|
+
?? knowledgeCandidateFingerprint(candidate.categories, candidate.touchedFiles);
|
|
915
|
+
if (existingFingerprint === fingerprint) {
|
|
916
|
+
return candidate;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
return null;
|
|
871
920
|
}
|
|
872
921
|
|
|
873
922
|
async function loadRawReviewInput(projectRoot, from) {
|
|
@@ -1155,6 +1204,7 @@ function buildKnowledgeCandidateMeta({
|
|
|
1155
1204
|
updatedAt: timestamp(),
|
|
1156
1205
|
sourceKind: source.kind,
|
|
1157
1206
|
sourceRef: source.sourceId,
|
|
1207
|
+
evidenceOnly: source.evidenceOnly === true,
|
|
1158
1208
|
title,
|
|
1159
1209
|
summary,
|
|
1160
1210
|
categories,
|
|
@@ -1365,6 +1415,11 @@ export async function reviewKnowledgeWorkspace(projectRoot, options = {}) {
|
|
|
1365
1415
|
const turnStateSource = (await exists(knowledgePath(projectRoot, OPENPRD_HARNESS_TURN_STATE))) ? OPENPRD_HARNESS_TURN_STATE : null;
|
|
1366
1416
|
const from = firstString(options.from, turnStateSource, latestReportPath);
|
|
1367
1417
|
const rawInput = from ? await loadRawReviewInput(projectRoot, from) : { sourcePath: null, raw: null };
|
|
1418
|
+
const evidenceRecords = normalizeSessionEvidenceRecords([
|
|
1419
|
+
...normalizeSessionEvidenceRecords(options.evidenceRecords),
|
|
1420
|
+
...extractSessionEvidenceRecords(options.sessionEvents ?? []),
|
|
1421
|
+
]);
|
|
1422
|
+
const cleanEvidenceRecords = evidenceRecords.filter((record) => record.sourceKind !== 'context-only');
|
|
1368
1423
|
const resolved = from
|
|
1369
1424
|
? await resolveQualityLearningSource(projectRoot, {
|
|
1370
1425
|
from,
|
|
@@ -1374,13 +1429,20 @@ export async function reviewKnowledgeWorkspace(projectRoot, options = {}) {
|
|
|
1374
1429
|
: { ok: false, error: 'no-review-source' };
|
|
1375
1430
|
const source = resolved.ok
|
|
1376
1431
|
? resolved.source
|
|
1432
|
+
: (cleanEvidenceRecords.length > 0
|
|
1433
|
+
? buildSessionEvidenceReviewSource({
|
|
1434
|
+
records: cleanEvidenceRecords,
|
|
1435
|
+
sourceId: firstString(options.sourceId, options.title, cleanEvidenceRecords[0]?.text),
|
|
1436
|
+
sourcePath: rawInput.sourcePath ?? latestReportPath ?? KNOWLEDGE_REVIEW_SIGNAL_LOG,
|
|
1437
|
+
title: firstString(options.title, cleanEvidenceRecords.find((record) => record.sourceKind === 'user-direct')?.text),
|
|
1438
|
+
})
|
|
1377
1439
|
: buildSyntheticReviewSource(projectRoot, {
|
|
1378
1440
|
signal: options.signal,
|
|
1379
1441
|
recentSignals,
|
|
1380
1442
|
touchedFiles: options.touchedFiles,
|
|
1381
1443
|
sourcePath: rawInput.sourcePath ?? latestReportPath ?? KNOWLEDGE_REVIEW_SIGNAL_LOG,
|
|
1382
1444
|
title: firstString(options.title, readJsonObject(rawInput.raw)?.title),
|
|
1383
|
-
});
|
|
1445
|
+
}));
|
|
1384
1446
|
if (!resolved.ok && source.evidenceSources.length === 0 && source.rootCauseCandidates.length === 0) {
|
|
1385
1447
|
return {
|
|
1386
1448
|
ok: true,
|
|
@@ -1415,28 +1477,39 @@ export async function reviewKnowledgeWorkspace(projectRoot, options = {}) {
|
|
|
1415
1477
|
const reviewSignals = reviewContext.reviewSignals;
|
|
1416
1478
|
const categories = buildKnowledgeCategories({ source, touchedFiles: substantiveTouchedFiles, reviewSignals });
|
|
1417
1479
|
const reasons = categories.map(categoryReason);
|
|
1480
|
+
const allowEvidenceOnly = options.allowEvidenceOnly === true || source.evidenceOnly === true || source.kind === 'session-evidence-review';
|
|
1418
1481
|
const hasStrongSignal = categories.length > 0
|
|
1419
1482
|
|| source.rootCauseCandidates.length > 0
|
|
1420
1483
|
|| source.symptoms.length > 1
|
|
1421
1484
|
|| source.kind === 'completion-review'
|
|
1485
|
+
|| source.kind === 'session-evidence-review'
|
|
1422
1486
|
|| reviewSignals.some((signal) => signal.ok === true || signal.productionReady === true);
|
|
1423
1487
|
|
|
1424
|
-
if (substantiveTouchedFiles.length === 0 || !hasStrongSignal) {
|
|
1488
|
+
if ((substantiveTouchedFiles.length === 0 && !allowEvidenceOnly) || !hasStrongSignal) {
|
|
1425
1489
|
return {
|
|
1426
1490
|
ok: true,
|
|
1427
1491
|
action: 'quality-knowledge-review',
|
|
1428
1492
|
skipped: true,
|
|
1429
|
-
reason: substantiveTouchedFiles.length === 0 ? 'no-substantive-touched-files' : 'no-knowledge-signal',
|
|
1493
|
+
reason: substantiveTouchedFiles.length === 0 && !allowEvidenceOnly ? 'no-substantive-touched-files' : 'no-knowledge-signal',
|
|
1430
1494
|
sourceKind: source.kind,
|
|
1431
1495
|
sourcePath: source.sourcePath,
|
|
1432
1496
|
};
|
|
1433
1497
|
}
|
|
1434
1498
|
|
|
1435
|
-
const title = buildTurnReviewTitle(raw, source);
|
|
1499
|
+
const title = buildTurnReviewTitle(raw, source, substantiveTouchedFiles);
|
|
1436
1500
|
const rawCandidateRef = firstString(raw.knowledgeCandidateId, raw.id);
|
|
1437
|
-
|
|
1501
|
+
let candidateId = rawCandidateRef
|
|
1438
1502
|
? (rawCandidateRef.startsWith('candidate-') ? rawCandidateRef : `candidate-${slugify(rawCandidateRef, 'knowledge')}`)
|
|
1439
1503
|
: `candidate-${slugify(source.sourceId ?? title, 'knowledge')}`;
|
|
1504
|
+
const fingerprint = knowledgeCandidateFingerprint(
|
|
1505
|
+
categories,
|
|
1506
|
+
substantiveTouchedFiles,
|
|
1507
|
+
allowEvidenceOnly ? firstString(source.sourceId, source.title, source.sourcePath) : null,
|
|
1508
|
+
);
|
|
1509
|
+
const duplicateCandidate = await findPendingCandidateByFingerprint(projectRoot, fingerprint, candidateId);
|
|
1510
|
+
if (duplicateCandidate?.candidateId) {
|
|
1511
|
+
candidateId = duplicateCandidate.candidateId;
|
|
1512
|
+
}
|
|
1440
1513
|
const promotedSource = { ...source, sourceId: candidateId };
|
|
1441
1514
|
const names = deriveKnowledgeNames(promotedSource, { stablePattern: false });
|
|
1442
1515
|
const candidateDir = knowledgePath(projectRoot, cjoin(KNOWLEDGE_CANDIDATES_DIR, candidateId));
|
|
@@ -1448,11 +1521,15 @@ export async function reviewKnowledgeWorkspace(projectRoot, options = {}) {
|
|
|
1448
1521
|
const existingCandidate = await readJson(candidatePath).catch(() => null);
|
|
1449
1522
|
const relativeCandidateDir = path.relative(projectRoot, candidateDir).split(path.sep).join('/');
|
|
1450
1523
|
const relativeDraftSkillPath = path.relative(projectRoot, draftSkillPath).split(path.sep).join('/');
|
|
1524
|
+
const scopeSummary = substantiveTouchedFiles.length > 0
|
|
1525
|
+
? `本轮围绕 ${substantiveTouchedFiles.length} 个可沉淀文件生成回顾。`
|
|
1526
|
+
: '本轮基于只读证据生成项目经验候选。';
|
|
1451
1527
|
const reviewSummary = [
|
|
1452
|
-
|
|
1528
|
+
scopeSummary,
|
|
1453
1529
|
reasons[0] ?? '这次实现已经具备项目级经验抽象价值。',
|
|
1454
1530
|
reviewSignals.length > 0 ? `已记录 ${reviewSignals.length} 条回顾信号。` : null,
|
|
1455
1531
|
touchedFileSource === 'inferred-recent-files' ? '本轮 touched files 来自最近修改文件推断。' : null,
|
|
1532
|
+
allowEvidenceOnly && substantiveTouchedFiles.length === 0 ? '本轮没有把 AGENTS、system 或 tool schema 命中当成用户证据。' : null,
|
|
1456
1533
|
].filter(Boolean).join(' ');
|
|
1457
1534
|
const draftCandidate = buildKnowledgeCandidateMeta({
|
|
1458
1535
|
projectRoot,
|
|
@@ -1482,6 +1559,8 @@ export async function reviewKnowledgeWorkspace(projectRoot, options = {}) {
|
|
|
1482
1559
|
const candidate = {
|
|
1483
1560
|
...draftCandidate,
|
|
1484
1561
|
abstraction,
|
|
1562
|
+
fingerprint,
|
|
1563
|
+
occurrences: (readJsonObject(existingCandidate)?.occurrences ?? 0) + 1,
|
|
1485
1564
|
};
|
|
1486
1565
|
const userFacingExperience = buildKnowledgeUserFacingExperience({
|
|
1487
1566
|
candidate,
|
|
@@ -1693,7 +1772,9 @@ function buildKnowledgeMatchQuery(options = {}) {
|
|
|
1693
1772
|
options.activeChange,
|
|
1694
1773
|
options.nextTaskTitle,
|
|
1695
1774
|
...(normalizeStringList(options.relatedFiles)),
|
|
1696
|
-
]
|
|
1775
|
+
]
|
|
1776
|
+
.map((field) => sanitizeEvidenceText(field))
|
|
1777
|
+
.filter(Boolean);
|
|
1697
1778
|
const text = candidateFields.join('\n');
|
|
1698
1779
|
return {
|
|
1699
1780
|
text,
|
package/src/language-policy.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
export const
|
|
1
|
+
export const OPENPRD_FALLBACK_LOCALE = 'zh-CN';
|
|
2
|
+
export const OPENPRD_CONTENT_LOCALE = OPENPRD_FALLBACK_LOCALE;
|
|
2
3
|
|
|
3
4
|
export const OPENPRD_LANGUAGE_POLICY =
|
|
4
|
-
'OpenPrd
|
|
5
|
+
'OpenPrd 用户可见内容以及 Agent 产出的 spec、tasks 和说明文案应跟随用户当前主语言;无法判断时才使用简体中文兜底。PRD、OpenPrd、OpenSpec、API、SDK、CLI、TypeScript、JSON、HTTP、WebSocket、字段 key、命令名、品牌名、产品名和协议名等必要专有名词按原文保留。';
|
|
5
6
|
|
|
6
7
|
export const TBD_ZH = '待补充';
|
|
7
8
|
|
|
8
9
|
const CJK_RE = /[\u3400-\u9fff]/;
|
|
10
|
+
const LATIN_WORD_RE = /[A-Za-z][A-Za-z0-9+_.-]*/g;
|
|
9
11
|
|
|
10
12
|
export function scalarZh(value, fallback = TBD_ZH) {
|
|
11
13
|
if (value === null || value === undefined) {
|
|
@@ -19,14 +21,72 @@ export function hasSimplifiedChinese(value) {
|
|
|
19
21
|
return CJK_RE.test(String(value ?? ''));
|
|
20
22
|
}
|
|
21
23
|
|
|
24
|
+
export function englishWords(value) {
|
|
25
|
+
return String(value ?? '').match(LATIN_WORD_RE) ?? [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isChineseLocale(locale) {
|
|
29
|
+
return normalizeOutputLocale(locale).startsWith('zh');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function normalizeOutputLocale(locale, fallback = OPENPRD_FALLBACK_LOCALE) {
|
|
33
|
+
const text = String(locale ?? '').trim();
|
|
34
|
+
if (!text) {
|
|
35
|
+
return fallback;
|
|
36
|
+
}
|
|
37
|
+
const lower = text.toLowerCase();
|
|
38
|
+
if (lower === 'cn' || lower.startsWith('zh')) {
|
|
39
|
+
return 'zh-CN';
|
|
40
|
+
}
|
|
41
|
+
if (lower.startsWith('en')) {
|
|
42
|
+
return 'en';
|
|
43
|
+
}
|
|
44
|
+
return text;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function detectPrimaryLanguage(values, fallback = OPENPRD_FALLBACK_LOCALE) {
|
|
48
|
+
const text = (Array.isArray(values) ? values : [values])
|
|
49
|
+
.map((value) => String(value ?? ''))
|
|
50
|
+
.join('\n');
|
|
51
|
+
const cjkCount = (text.match(CJK_RE) ?? []).length;
|
|
52
|
+
const latinCount = englishWords(text).length;
|
|
53
|
+
if (cjkCount > 0) {
|
|
54
|
+
return 'zh-CN';
|
|
55
|
+
}
|
|
56
|
+
if (latinCount >= 4) {
|
|
57
|
+
return 'en';
|
|
58
|
+
}
|
|
59
|
+
return normalizeOutputLocale(fallback);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function isEnglishHeavyText(value) {
|
|
63
|
+
const text = String(value ?? '').trim();
|
|
64
|
+
if (!text || hasSimplifiedChinese(text)) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
return englishWords(text).length >= 4;
|
|
68
|
+
}
|
|
69
|
+
|
|
22
70
|
export function preferSimplifiedChinese(value, fallback = TBD_ZH) {
|
|
23
71
|
const text = scalarZh(value, '');
|
|
24
72
|
return hasSimplifiedChinese(text) ? text : fallback;
|
|
25
73
|
}
|
|
26
74
|
|
|
75
|
+
export function preferUserLanguage(value, locale = OPENPRD_FALLBACK_LOCALE, fallbacks = {}) {
|
|
76
|
+
const text = scalarZh(value, '');
|
|
77
|
+
if (text) {
|
|
78
|
+
return text;
|
|
79
|
+
}
|
|
80
|
+
const normalizedLocale = normalizeOutputLocale(locale);
|
|
81
|
+
if (!isChineseLocale(normalizedLocale) && fallbacks.en) {
|
|
82
|
+
return fallbacks.en;
|
|
83
|
+
}
|
|
84
|
+
return fallbacks.zh ?? fallbacks.default ?? TBD_ZH;
|
|
85
|
+
}
|
|
86
|
+
|
|
27
87
|
export function languagePolicyLines() {
|
|
28
88
|
return [
|
|
29
|
-
'>
|
|
89
|
+
'> 语言规则:用户可见说明、PRD、spec 和 tasks 跟随用户当前主语言;无法判断时才使用简体中文兜底。PRD、OpenPrd、OpenSpec、API、SDK、CLI、TypeScript、JSON、HTTP、WebSocket、字段 key、命令名、品牌名、产品名和协议名等必要专有名词保留原文。',
|
|
30
90
|
'',
|
|
31
91
|
];
|
|
32
92
|
}
|
package/src/learning-review.js
CHANGED
|
@@ -622,7 +622,7 @@ async function buildEvidenceManifest(ws, snapshot, options = {}) {
|
|
|
622
622
|
];
|
|
623
623
|
|
|
624
624
|
if (changeId) {
|
|
625
|
-
const changeRoot = path.join(ws.projectRoot, 'openprd', 'changes', changeId);
|
|
625
|
+
const changeRoot = path.join(ws.projectRoot, '.openprd', 'changes', changeId);
|
|
626
626
|
candidateDefs.push(
|
|
627
627
|
makeEvidenceCandidate({
|
|
628
628
|
id: 'change-proposal',
|
package/src/loop.js
CHANGED
|
@@ -550,7 +550,7 @@ async function syncLoopIsolationArtifacts(sourceRoot, targetRoot, options = {})
|
|
|
550
550
|
LOOP_STATE,
|
|
551
551
|
LOOP_BOOTSTRAP,
|
|
552
552
|
RELEASE_LEDGER,
|
|
553
|
-
options.change ? path.join('openprd', 'changes', options.change) : null,
|
|
553
|
+
options.change ? path.join('.openprd', 'changes', options.change) : null,
|
|
554
554
|
].filter(Boolean);
|
|
555
555
|
for (const relativePath of relativePaths) {
|
|
556
556
|
if (await copyRelativePath(sourceRoot, targetRoot, relativePath, { force: Boolean(options.force) })) {
|
package/src/openprd.js
CHANGED
|
@@ -33,7 +33,8 @@ import { doctorOpenPrdAgentIntegration, setupOpenPrdAgentIntegration, updateOpen
|
|
|
33
33
|
import { finishLoopWorkspace, initLoopWorkspace, nextLoopWorkspace, planLoopWorkspace, promptLoopWorkspace, runLoopWorkspace, statusLoopWorkspace, verifyLoopWorkspace } from './loop.js';
|
|
34
34
|
import { timestamp } from './time.js';
|
|
35
35
|
import { parseCommandArgs, usage } from './cli/args.js';
|
|
36
|
-
import {
|
|
36
|
+
import { runQualityCommand } from './cli/quality-commands.js';
|
|
37
|
+
import { printAcceptedSpecsResult, printAgentIntegrationResult, printBenchmarkResult, printBrainstormResult, printCanvasResult, printCaptureResult, printClarifyResult, printClassifyResult, printDesignStarterResult, printDevelopmentStandardsResult, printDiagramResult, printDiffResult, printDoctorResult, printFleetResult, printFreezeResult, printGrowthResult, printHandoffResult, printHistoryResult, printInitResult, printInterviewResult, printKnowledgeResult, printLearningResult, printLoopResult, printNextResult, printOpenPrdChangeActionResult, printOpenPrdChangesResult, printOpenSpecChangeValidationResult, printOpenSpecDiscoveryResult, printOpenSpecGenerateResult, printOpenSpecTaskResult, printPlaygroundResult, printQualityResult, printReleaseResult, printReviewResult, printRunResult, printSelfUpdateResult, printStandardsResult, printStatus, printSynthesizeResult, printUpgradeResult, printValidation, printVisualCompareResult, printVisualPrepareResult } from './cli/print.js';
|
|
37
38
|
import { cjoin, exists, readJson, writeJson, writeText, writeYaml } from './fs-utils.js';
|
|
38
39
|
import { diagramWorkspace } from './diagram-workspace.js';
|
|
39
40
|
import { createOpenSpecDiscoveryWorkspace } from './discovery.js';
|
|
@@ -64,6 +65,7 @@ import {
|
|
|
64
65
|
import { brainstormWorkspace } from './brainstorm.js';
|
|
65
66
|
import { buildBrainstormPresentationTemplatePayload, brainstormPresentationWorkspace } from './brainstorm-presentation.js';
|
|
66
67
|
import { designStarterWorkspace } from './design-starter.js';
|
|
68
|
+
import { canvasWorkspace } from './canvas-workspace.js';
|
|
67
69
|
import { buildReviewPresentationTemplatePayload, reviewPresentationWorkspace } from './review-presentation.js';
|
|
68
70
|
import { analyzeWorkspaceRegistryHygiene } from './registry-hygiene.js';
|
|
69
71
|
import { syncSessionBindingFromChange } from './session-binding.js';
|
|
@@ -74,6 +76,22 @@ import { captureWorkspace, clarifyWorkspace, classifyWorkspace, computeWorkspace
|
|
|
74
76
|
import { appendDecision, appendProgress, appendVerification, appendWorkflowEvent, buildCurrentStateSnapshot, buildWorkflowTaskGraph, computeWorkspaceDigest, CORE_TEMPLATE_FILES, ensureWorkspaceSkeleton, isSupportedProductType, loadCurrentLaneSnapshot, loadLatestVersionSnapshot, loadWorkspace, migrateWorkspaceSkeleton, normalizeVersionId, persistWorkspaceCurrentState, readVersionIndex, resolveActiveTemplatePack, resolveCurrentProductType, validateWorkspace } from './workspace-core.js';
|
|
75
77
|
import { readWorkspaceRegistry } from './workspace-registry.js';
|
|
76
78
|
|
|
79
|
+
function buildInitTemplatePackGuidance(templatePack, options = {}) {
|
|
80
|
+
const label = formatTemplatePackDisplay(templatePack, { fallback: '通用产品或工程场景' });
|
|
81
|
+
if (options.explicit) {
|
|
82
|
+
return {
|
|
83
|
+
source: 'explicit',
|
|
84
|
+
templatePack,
|
|
85
|
+
message: `已按指定场景模板使用${label}起步;后续需求澄清或场景分类时,仍可按用户确认调整产品场景。`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
source: 'default',
|
|
90
|
+
templatePack,
|
|
91
|
+
message: `未指定场景模板,已使用${label}起步;后续需求澄清或场景分类时,可再锁定为个人消费者、企业服务或 Agent 场景。`,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
77
95
|
async function initWorkspace(projectRoot, options) {
|
|
78
96
|
const ws = await ensureWorkspaceSkeleton(projectRoot, options);
|
|
79
97
|
const workspace = await loadWorkspace(projectRoot);
|
|
@@ -131,12 +149,16 @@ async function initWorkspace(projectRoot, options) {
|
|
|
131
149
|
templatePack: currentState.templatePack,
|
|
132
150
|
projectRoot,
|
|
133
151
|
});
|
|
152
|
+
const templatePackGuidance = buildInitTemplatePackGuidance(currentState.templatePack, {
|
|
153
|
+
explicit: Boolean(options.templatePack),
|
|
154
|
+
});
|
|
134
155
|
await appendProgress(workspace, [
|
|
135
156
|
`已初始化工作区: ${workspace.workspaceRoot}。`,
|
|
136
157
|
`场景模板: ${formatTemplatePackDisplay(currentState.templatePack, { fallback: '待确认' })}。`,
|
|
158
|
+
templatePackGuidance.message,
|
|
137
159
|
]);
|
|
138
160
|
|
|
139
|
-
return { ws: workspace, created: ws.created, currentState, standards, quality, growth, agentIntegration };
|
|
161
|
+
return { ws: workspace, created: ws.created, currentState, templatePackGuidance, standards, quality, growth, agentIntegration };
|
|
140
162
|
}
|
|
141
163
|
|
|
142
164
|
async function setupAgentIntegrationWorkspace(projectRoot, options = {}) {
|
|
@@ -171,7 +193,16 @@ async function setupAgentIntegrationWorkspace(projectRoot, options = {}) {
|
|
|
171
193
|
platform: options.platform,
|
|
172
194
|
hookProfile: options.hookProfile,
|
|
173
195
|
});
|
|
174
|
-
return {
|
|
196
|
+
return {
|
|
197
|
+
...agentIntegration,
|
|
198
|
+
ok: agentIntegration.ok && migration.ok,
|
|
199
|
+
initialized: false,
|
|
200
|
+
migration,
|
|
201
|
+
standards,
|
|
202
|
+
quality,
|
|
203
|
+
growth,
|
|
204
|
+
errors: [...(agentIntegration.errors ?? []), ...(migration.errors ?? [])],
|
|
205
|
+
};
|
|
175
206
|
}
|
|
176
207
|
|
|
177
208
|
async function updateAgentIntegrationWorkspace(projectRoot, options = {}) {
|
|
@@ -189,7 +220,15 @@ async function updateAgentIntegrationWorkspace(projectRoot, options = {}) {
|
|
|
189
220
|
platform: options.platform,
|
|
190
221
|
hookProfile: options.hookProfile,
|
|
191
222
|
});
|
|
192
|
-
return {
|
|
223
|
+
return {
|
|
224
|
+
...agentIntegration,
|
|
225
|
+
ok: agentIntegration.ok && migration.ok,
|
|
226
|
+
migration,
|
|
227
|
+
standards,
|
|
228
|
+
quality,
|
|
229
|
+
growth,
|
|
230
|
+
errors: [...(agentIntegration.errors ?? []), ...(migration.errors ?? [])],
|
|
231
|
+
};
|
|
193
232
|
}
|
|
194
233
|
|
|
195
234
|
async function doctorWorkspace(projectRoot, options = {}) {
|
|
@@ -897,138 +936,34 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
897
936
|
return result.ok ? 0 : 1;
|
|
898
937
|
}
|
|
899
938
|
|
|
900
|
-
if (command === '
|
|
901
|
-
const result =
|
|
902
|
-
|
|
903
|
-
: flags.
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
html: flags.html,
|
|
916
|
-
learn: Boolean(flags.learn),
|
|
917
|
-
review: flags.review,
|
|
918
|
-
from: flags.from,
|
|
919
|
-
force: flags.force,
|
|
920
|
-
});
|
|
921
|
-
printQualityResult(result, flags.json);
|
|
922
|
-
return result.ok ? 0 : 1;
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
if (command === 'knowledge') {
|
|
926
|
-
const subcommand = positionals[0] ?? 'candidates';
|
|
927
|
-
const firstArgIsProjectPath = !flags.path && positionals.length > 1 && await isDirectoryPath(positionals[1]);
|
|
928
|
-
const knowledgeProjectPath = path.resolve(flags.path ?? (firstArgIsProjectPath ? positionals[1] : process.cwd()));
|
|
929
|
-
let result;
|
|
930
|
-
if (subcommand === 'candidates' || subcommand === 'list') {
|
|
931
|
-
result = await listKnowledgeCandidates(knowledgeProjectPath, {
|
|
932
|
-
status: flags.status ?? 'pending-review',
|
|
933
|
-
});
|
|
934
|
-
} else if (subcommand === 'reject') {
|
|
935
|
-
result = await rejectKnowledgeCandidate(knowledgeProjectPath, {
|
|
936
|
-
id: flags.id ?? positionals[1] ?? null,
|
|
937
|
-
reason: flags.reason ?? flags.notes,
|
|
938
|
-
});
|
|
939
|
-
} else if (subcommand === 'archive') {
|
|
940
|
-
result = await archiveKnowledgeCandidate(knowledgeProjectPath, {
|
|
941
|
-
id: flags.id ?? positionals[1] ?? null,
|
|
942
|
-
reason: flags.reason ?? flags.notes,
|
|
943
|
-
});
|
|
944
|
-
} else if (subcommand === 'restore') {
|
|
945
|
-
result = await restoreKnowledgeCandidate(knowledgeProjectPath, {
|
|
946
|
-
id: flags.id ?? positionals[1] ?? null,
|
|
947
|
-
});
|
|
948
|
-
} else {
|
|
949
|
-
console.log('Usage: openprd knowledge <candidates|reject|archive|restore> [path-or-id] [--status <pending-review|all|rejected|archived|promoted|merged>] [--id <candidate-id>] [--reason <text>] [--json]');
|
|
950
|
-
return 1;
|
|
951
|
-
}
|
|
952
|
-
printKnowledgeResult(result, flags.json);
|
|
953
|
-
return result.ok ? 0 : 1;
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
if (command === 'visual-prepare') {
|
|
957
|
-
const result = await visualPrepareWorkspace(projectPath, {
|
|
958
|
-
reference: flags.reference,
|
|
959
|
-
grid: flags.grid,
|
|
960
|
-
boxes: flags.boxes,
|
|
961
|
-
include: flags.include,
|
|
939
|
+
if (command === 'canvas') {
|
|
940
|
+
const result = await canvasWorkspace(projectPath, {
|
|
941
|
+
thread: flags.thread,
|
|
942
|
+
threadTitle: flags.threadTitle,
|
|
943
|
+
conversationTitle: flags.conversationTitle,
|
|
944
|
+
session: flags.session,
|
|
945
|
+
sessionTitle: flags.sessionTitle,
|
|
946
|
+
host: flags.host,
|
|
947
|
+
port: flags.port,
|
|
948
|
+
daemon: flags.daemon,
|
|
949
|
+
stop: flags.stop,
|
|
950
|
+
bridgeOutbox: flags.bridgeOutbox,
|
|
951
|
+
bridgeSent: flags.bridgeSent,
|
|
952
|
+
bridgeFailed: flags.bridgeFailed,
|
|
953
|
+
claim: flags.claim,
|
|
962
954
|
id: flags.id,
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
return result.ok ? 0 : 1;
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
if (command === 'visual-compare') {
|
|
971
|
-
const result = await visualCompareWorkspace(projectPath, {
|
|
972
|
-
reference: flags.reference,
|
|
973
|
-
actual: flags.actual,
|
|
974
|
-
before: flags.before,
|
|
975
|
-
after: flags.after,
|
|
976
|
-
board: flags.board,
|
|
977
|
-
out: flags.out,
|
|
978
|
-
format: flags.format,
|
|
979
|
-
quality: flags.quality,
|
|
980
|
-
maxPanelWidth: flags.maxPanelWidth,
|
|
981
|
-
referenceLabel: flags.referenceLabel,
|
|
982
|
-
actualLabel: flags.actualLabel,
|
|
983
|
-
});
|
|
984
|
-
printVisualCompareResult(result, flags.json);
|
|
985
|
-
return result.ok ? 0 : 1;
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
if (command === 'design-starter') {
|
|
989
|
-
const result = await designStarterWorkspace(projectPath, {
|
|
990
|
-
starter: flags.starter,
|
|
991
|
-
out: flags.out,
|
|
992
|
-
title: flags.title,
|
|
993
|
-
brief: flags.brief,
|
|
994
|
-
sections: flags.sections,
|
|
995
|
-
noExternalFacts: flags.noExternalFacts,
|
|
996
|
-
noBrandAssets: flags.noBrandAssets,
|
|
997
|
-
noRealImages: flags.noRealImages,
|
|
998
|
-
force: flags.force,
|
|
955
|
+
reason: flags.reason,
|
|
956
|
+
open: flags.open,
|
|
957
|
+
openTarget: flags.openTarget,
|
|
958
|
+
dryRun: flags.dryRun,
|
|
999
959
|
});
|
|
1000
|
-
|
|
960
|
+
printCanvasResult(result, flags.json);
|
|
1001
961
|
return result.ok ? 0 : 1;
|
|
1002
962
|
}
|
|
1003
963
|
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
let result;
|
|
1008
|
-
if (flags.init) {
|
|
1009
|
-
result = await initGrowthWorkspace(growthProjectPath);
|
|
1010
|
-
} else if (flags.apply) {
|
|
1011
|
-
result = await applyGrowthCandidateWorkspace(growthProjectPath, { id: flags.id });
|
|
1012
|
-
} else if (flags.reject) {
|
|
1013
|
-
result = await rejectGrowthCandidateWorkspace(growthProjectPath, { id: flags.id, notes: flags.notes });
|
|
1014
|
-
} else if (flags.check) {
|
|
1015
|
-
result = await checkGrowthWorkspace(growthProjectPath);
|
|
1016
|
-
} else {
|
|
1017
|
-
result = await reviewGrowthWorkspace(growthProjectPath);
|
|
1018
|
-
}
|
|
1019
|
-
printGrowthResult(result, flags.json);
|
|
1020
|
-
return result.ok ? 0 : 1;
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
if (command === 'dev-check') {
|
|
1024
|
-
const firstArgIsProjectPath = !flags.path && positionals.length > 1 && await isDirectoryPath(positionals[0]);
|
|
1025
|
-
const devProjectPath = path.resolve(flags.path ?? (firstArgIsProjectPath ? positionals[0] : process.cwd()));
|
|
1026
|
-
const files = flags.path
|
|
1027
|
-
? positionals
|
|
1028
|
-
: (firstArgIsProjectPath ? positionals.slice(1) : positionals);
|
|
1029
|
-
const result = await checkDevelopmentStandardsWorkspace(devProjectPath, { files });
|
|
1030
|
-
printDevelopmentStandardsResult(result, flags.json);
|
|
1031
|
-
return result.ok ? 0 : 1;
|
|
964
|
+
const qualityCommandExit = await runQualityCommand(command, { projectPath, flags, positionals });
|
|
965
|
+
if (qualityCommandExit !== null) {
|
|
966
|
+
return qualityCommandExit;
|
|
1032
967
|
}
|
|
1033
968
|
|
|
1034
969
|
if (command === 'benchmark') {
|
|
@@ -1171,6 +1106,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
1171
1106
|
version: flags.version,
|
|
1172
1107
|
digest: flags.digest,
|
|
1173
1108
|
workUnit: flags.workUnit,
|
|
1109
|
+
direction: flags.direction,
|
|
1174
1110
|
});
|
|
1175
1111
|
printReviewResult(result, flags.json);
|
|
1176
1112
|
return result.ok ? 0 : 1;
|
|
@@ -1454,6 +1390,7 @@ export {
|
|
|
1454
1390
|
fleetWorkspace,
|
|
1455
1391
|
backfillWorkUnitsWorkspace,
|
|
1456
1392
|
runWorkspace,
|
|
1393
|
+
canvasWorkspace,
|
|
1457
1394
|
initLoopWorkspace,
|
|
1458
1395
|
planLoopWorkspace,
|
|
1459
1396
|
statusLoopWorkspace,
|
|
@@ -47,7 +47,7 @@ export async function resolveOpenSpecChangeId(projectRoot, requestedChange) {
|
|
|
47
47
|
return changeDirs[0];
|
|
48
48
|
}
|
|
49
49
|
if (changeDirs.length === 0) {
|
|
50
|
-
throw new Error('openprd/changes 下没有找到 OpenPrd change。');
|
|
50
|
+
throw new Error('.openprd/changes 下没有找到 OpenPrd change。');
|
|
51
51
|
}
|
|
52
52
|
throw new Error(`找到多个 OpenPrd change;请传入 --change <id>。已找到: ${changeDirs.join(', ')}`);
|
|
53
53
|
}
|
|
@@ -60,7 +60,7 @@ function extractProposalCapabilities(text) {
|
|
|
60
60
|
return capabilities;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
const SPEC_SECTION_HEADING_RE = /^##\s+(?:(ADDED|MODIFIED|REMOVED)\s+Requirements|(?:新增|修改|移除)需求)\s*$/gim;
|
|
63
|
+
const SPEC_SECTION_HEADING_RE = /^##\s+(?:(ADDED|MODIFIED|REMOVED|NEW|UPDATED|CHANGED)\s+Requirements|(?:新增|修改|移除)需求)\s*$/gim;
|
|
64
64
|
const REQUIREMENT_HEADING_RE = /^###\s+(?:Requirement|需求)[::]\s*(.+)$/gm;
|
|
65
65
|
const SCENARIO_HEADING_RE = /^####\s+(?:Scenario|场景)[::]\s*(.+)$/gm;
|
|
66
66
|
const WHEN_STEP_RE = /^-\s+\*\*(?:WHEN|当)\*\*/im;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
export const OPENPRD_DISCOVERY_DIR = ['.openprd', 'discovery'];
|
|
2
2
|
export const OPENPRD_DISCOVERY_CONFIG_PATH = ['.openprd', 'discovery', 'config.json'];
|
|
3
|
-
export const
|
|
4
|
-
export const
|
|
5
|
-
export const
|
|
6
|
-
export const
|
|
7
|
-
export const
|
|
8
|
-
export const
|
|
9
|
-
export const OPENPRD_ARCHIVE_CHANGE_ROOT = ['openprd', 'archive', 'changes'];
|
|
3
|
+
export const OPENPRD_CHANGE_ROOT = ['.openprd', 'changes'];
|
|
4
|
+
export const LEGACY_OPENPRD_CHANGE_ROOT = ['openprd', 'changes'];
|
|
5
|
+
export const OPENPRD_ACCEPTED_SPEC_ROOT = ['.openprd', 'specs'];
|
|
6
|
+
export const LEGACY_OPENPRD_ACCEPTED_SPEC_ROOT = ['openprd', 'specs'];
|
|
7
|
+
export const OPENPRD_ARCHIVE_CHANGE_ROOT = ['.openprd', 'archive', 'changes'];
|
|
8
|
+
export const LEGACY_OPENPRD_ARCHIVE_CHANGE_ROOT = ['openprd', 'archive', 'changes'];
|
|
10
9
|
export const OPENSPEC_TASK_MAX_ITEMS_PER_FILE = 25;
|
|
11
10
|
export const OPENSPEC_TASK_FILE_PATTERN = /^tasks(?:-\d{3})?\.md$/;
|
|
12
11
|
export const OPENSPEC_TASK_ID_PATTERN = /^T\d{3}\.\d+$/;
|
package/src/openspec/execute.js
CHANGED
|
@@ -96,9 +96,9 @@ function resolveTaskSelection(state, options = {}) {
|
|
|
96
96
|
|
|
97
97
|
if (!task) {
|
|
98
98
|
if (requestedId) {
|
|
99
|
-
throw new Error(`Unknown
|
|
99
|
+
throw new Error(`Unknown OpenPrd task: ${requestedId}`);
|
|
100
100
|
}
|
|
101
|
-
throw new Error('No ready
|
|
101
|
+
throw new Error('No ready OpenPrd task is available.');
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
return task;
|