@openprd/cli 0.1.9 → 0.1.11
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 +16 -0
- package/.openprd/benchmarks/sources.yaml +53 -1
- package/.openprd/config.yaml +10 -0
- package/.openprd/design/README.md +47 -0
- package/.openprd/design/README_EN.md +25 -0
- package/.openprd/design/active/asset-spec.md +15 -0
- package/.openprd/design/active/direction-plan.md +7 -0
- package/.openprd/design/active/facts-sheet.md +13 -0
- package/.openprd/design/active/image-preflight.md +9 -0
- package/.openprd/design/active/selected-direction.md +11 -0
- package/.openprd/design/anti-slop.md +16 -0
- package/.openprd/design/assets/dark-mesh.svg +14 -0
- package/.openprd/design/assets/paper-grid.svg +14 -0
- package/.openprd/design/assets/surface-presets.md +21 -0
- package/.openprd/design/assets/workbench-grid.svg +16 -0
- package/.openprd/design/checklists/ui-quality-gate.md +22 -0
- package/.openprd/design/components/component-catalog.md +42 -0
- package/.openprd/design/layouts/layout-catalog.json +45 -0
- package/.openprd/design/lenses/frontend-lenses.md +31 -0
- package/.openprd/design/recipes/content-experience.md +26 -0
- package/.openprd/design/recipes/operational-tool.md +25 -0
- package/.openprd/design/recipes/product-launch.md +26 -0
- package/.openprd/design/templates/README.md +36 -0
- package/.openprd/design/templates/README_EN.md +31 -0
- package/.openprd/design/templates/content-home.html +415 -0
- package/.openprd/design/templates/ops-dashboard.html +340 -0
- package/.openprd/design/templates/product-launch.html +360 -0
- package/.openprd/design/themes/theme-catalog.json +77 -0
- package/.openprd/engagements/active/prd.md +111 -100
- package/.openprd/schema/prd.schema.yaml +25 -0
- package/.openprd/templates/base/intake.md +17 -0
- package/.openprd/templates/base/prd.md +31 -6
- package/AGENTS.md +10 -7
- package/README.md +26 -28
- package/README_EN.md +3 -3
- package/package.json +41 -2
- package/scripts/openprd-codex-isolated-worker.mjs +799 -0
- package/skills/openprd-benchmark-router/SKILL.md +5 -0
- package/skills/openprd-frontend-design/SKILL.md +161 -0
- package/skills/openprd-frontend-design/references/design-asset-contract.md +34 -0
- package/skills/openprd-frontend-design/references/direction-engine.md +46 -0
- package/skills/openprd-harness/SKILL.md +37 -20
- package/skills/openprd-learning-review/SKILL.md +2 -1
- package/skills/openprd-learning-review/references/style-packs/xianxia-cultivation.prompt.md +1 -1
- package/skills/openprd-quality/SKILL.md +5 -3
- package/skills/openprd-requirement-intake/SKILL.md +27 -5
- package/skills/openprd-requirement-intake/references/prd-template-lenses.md +6 -3
- package/skills/openprd-requirement-intake/references/routing-rubric.md +9 -5
- package/skills/openprd-requirement-intake/references/startup-validation-lens.md +108 -0
- package/skills/openprd-router/SKILL.md +13 -5
- package/skills/openprd-shared/SKILL.md +38 -25
- package/src/agent-integration.js +120 -69
- package/src/brainstorm-artifacts.js +1805 -0
- package/src/brainstorm-presentation.js +233 -0
- package/src/brainstorm.js +766 -0
- package/src/cli/args.js +51 -3
- package/src/cli/doctor-print.js +34 -8
- package/src/cli/print.js +6 -0
- package/src/cli/quality-print.js +79 -3
- package/src/cli/run-print.js +43 -1
- package/src/cli/shared-print.js +33 -2
- package/src/cli/workflow-print.js +21 -0
- package/src/codex-hook-runner-template.mjs +838 -58
- package/src/design-starter-support.js +462 -0
- package/src/design-starter.js +1207 -0
- package/src/diagram-core.js +384 -151
- package/src/html-artifacts.js +403 -50
- package/src/knowledge.js +291 -32
- package/src/learning-review.js +3 -3
- package/src/loop.js +839 -101
- package/src/openprd.js +86 -1
- package/src/openspec/change-validate.js +1 -0
- package/src/prd-core.js +119 -2
- package/src/quality-html-artifact.js +30 -5
- package/src/quality-learning.js +50 -6
- package/src/quality.js +22 -12
- package/src/run-harness.js +191 -29
- package/src/self-update.js +301 -4
- package/src/standards.js +46 -5
- package/src/visual-prepare.js +940 -0
- package/src/workspace-core.js +175 -0
- package/src/workspace-workflow.js +307 -7
- package/.openprd/benchmarks/evidence/milvus-io-ai-code-review-gets-better-when-models-debate-claude-vs-gemini-vs-code.md +0 -14
- package/.openprd/benchmarks/evidence/nolanlawson-com-using-ai-to-write-better-code-more-slowly.md +0 -14
- package/.openprd/discovery/config.json +0 -35
- package/.openprd/engagements/active/flows.md +0 -35
- package/.openprd/engagements/active/handoff.md +0 -16
- package/.openprd/engagements/active/review.html +0 -61
- package/.openprd/engagements/active/roles.md +0 -22
- package/.openprd/engagements/work-units/wu-20260524015648-6d33ded7.json +0 -23
- package/.openprd/engagements/work-units/wu-20260602113956-a99b5b88.json +0 -18
- package/.openprd/engagements/work-units/wu-20260602122244-78656aaf.json +0 -18
- package/.openprd/engagements/work-units/wu-20260602122442-e96489e2.json +0 -18
- package/.openprd/engagements/work-units/wu-20260602132835-695429e8.json +0 -18
- package/.openprd/exports/.gitkeep +0 -0
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/candidate.json +0 -78
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/diagnostic-report.json +0 -129
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/root-cause-candidates.json +0 -41
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/timeline.json +0 -14
- package/.openprd/knowledge/drafts/openprd-experience-diagnostic-candidate-turn-1780116203372-5f266a79e968c758/SKILL.md +0 -49
- package/.openprd/knowledge/index.json +0 -47
- package/.openprd/reviews/v0001.html +0 -1322
- package/.openprd/reviews/v0002.html +0 -1150
- package/.openprd/reviews/v0003.html +0 -1150
- package/.openprd/reviews/v0004.html +0 -1150
- package/.openprd/reviews/v0005.html +0 -1150
- package/.openprd/sessions/.gitkeep +0 -0
- package/.openprd/state/changes.json +0 -27
- package/.openprd/state/current.json +0 -505
- package/.openprd/state/release-ledger.json +0 -387
- package/.openprd/state/version-index.json +0 -67
- package/.openprd/state/versions/.gitkeep +0 -0
- package/.openprd/state/versions/v0001.json +0 -121
- package/.openprd/state/versions/v0001.md +0 -161
- package/.openprd/state/versions/v0002.json +0 -264
- package/.openprd/state/versions/v0002.md +0 -183
- package/.openprd/state/versions/v0003.json +0 -269
- package/.openprd/state/versions/v0003.md +0 -188
- package/.openprd/state/versions/v0004.json +0 -274
- package/.openprd/state/versions/v0004.md +0 -193
- package/.openprd/state/versions/v0005.json +0 -299
- package/.openprd/state/versions/v0005.md +0 -189
- 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-requirement-routing-en.png +0 -0
- package/docs/assets/openprd-requirement-routing-en.svg +0 -102
- package/docs/assets/openprd-requirement-routing-zh-refined.png +0 -0
- package/docs/assets/openprd-requirement-routing-zh.png +0 -0
- package/docs/assets/openprd-requirement-routing-zh.svg +0 -102
- 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 +0 -114
- 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/src/self-update.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { readWorkspaceRegistry } from './workspace-registry.js';
|
|
5
6
|
|
|
6
7
|
export const DEFAULT_SELF_UPDATE_SOURCE = '@openprd/cli@latest';
|
|
7
8
|
|
|
@@ -20,6 +21,139 @@ export function formatCommand(command, args = []) {
|
|
|
20
21
|
return [command, ...args].map(shellQuote).join(' ');
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
function normalizeVersionText(value) {
|
|
25
|
+
const raw = String(value ?? '').trim();
|
|
26
|
+
if (!raw) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return raw.startsWith('v') ? raw.slice(1) : raw;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseSemverParts(value) {
|
|
33
|
+
const normalized = normalizeVersionText(value);
|
|
34
|
+
const match = normalized?.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/u);
|
|
35
|
+
if (!match) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
major: Number.parseInt(match[1], 10),
|
|
40
|
+
minor: Number.parseInt(match[2], 10),
|
|
41
|
+
patch: Number.parseInt(match[3], 10),
|
|
42
|
+
prerelease: match[4] ?? null,
|
|
43
|
+
build: match[5] ?? null,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function comparePrereleaseIdentifiers(left, right) {
|
|
48
|
+
const leftParts = String(left ?? '').split('.');
|
|
49
|
+
const rightParts = String(right ?? '').split('.');
|
|
50
|
+
const max = Math.max(leftParts.length, rightParts.length);
|
|
51
|
+
for (let index = 0; index < max; index += 1) {
|
|
52
|
+
const leftPart = leftParts[index];
|
|
53
|
+
const rightPart = rightParts[index];
|
|
54
|
+
if (leftPart === undefined) {
|
|
55
|
+
return -1;
|
|
56
|
+
}
|
|
57
|
+
if (rightPart === undefined) {
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
const leftNumeric = /^\d+$/u.test(leftPart);
|
|
61
|
+
const rightNumeric = /^\d+$/u.test(rightPart);
|
|
62
|
+
if (leftNumeric && rightNumeric) {
|
|
63
|
+
const diff = Number.parseInt(leftPart, 10) - Number.parseInt(rightPart, 10);
|
|
64
|
+
if (diff !== 0) {
|
|
65
|
+
return diff > 0 ? 1 : -1;
|
|
66
|
+
}
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (leftNumeric !== rightNumeric) {
|
|
70
|
+
return leftNumeric ? -1 : 1;
|
|
71
|
+
}
|
|
72
|
+
const diff = leftPart.localeCompare(rightPart);
|
|
73
|
+
if (diff !== 0) {
|
|
74
|
+
return diff > 0 ? 1 : -1;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function compareVersions(left, right) {
|
|
81
|
+
const leftParts = parseSemverParts(left);
|
|
82
|
+
const rightParts = parseSemverParts(right);
|
|
83
|
+
if (!leftParts || !rightParts) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
for (const field of ['major', 'minor', 'patch']) {
|
|
87
|
+
if (leftParts[field] !== rightParts[field]) {
|
|
88
|
+
return leftParts[field] > rightParts[field] ? 1 : -1;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (!leftParts.prerelease && !rightParts.prerelease) {
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
if (!leftParts.prerelease) {
|
|
95
|
+
return 1;
|
|
96
|
+
}
|
|
97
|
+
if (!rightParts.prerelease) {
|
|
98
|
+
return -1;
|
|
99
|
+
}
|
|
100
|
+
return comparePrereleaseIdentifiers(leftParts.prerelease, rightParts.prerelease);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function packageNameFromSource(source, fallback = '@openprd/cli') {
|
|
104
|
+
const text = String(source ?? '').trim();
|
|
105
|
+
if (!text) {
|
|
106
|
+
return fallback;
|
|
107
|
+
}
|
|
108
|
+
const scopedMatch = text.match(/^(@[^/]+\/[^@]+)(?:@.+)?$/u);
|
|
109
|
+
if (scopedMatch) {
|
|
110
|
+
return scopedMatch[1];
|
|
111
|
+
}
|
|
112
|
+
const unscopedMatch = text.match(/^([^@]+)(?:@.+)?$/u);
|
|
113
|
+
return unscopedMatch?.[1] ?? fallback;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function parseVersionOutput(text) {
|
|
117
|
+
const raw = String(text ?? '').trim();
|
|
118
|
+
if (!raw) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const parsed = JSON.parse(raw);
|
|
123
|
+
if (typeof parsed === 'string') {
|
|
124
|
+
return normalizeVersionText(parsed);
|
|
125
|
+
}
|
|
126
|
+
if (Array.isArray(parsed)) {
|
|
127
|
+
const first = parsed.find((item) => typeof item === 'string' && item.trim());
|
|
128
|
+
return first ? normalizeVersionText(first) : null;
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
// Fall back to plain text parsing.
|
|
132
|
+
}
|
|
133
|
+
const firstLine = raw.split(/\r?\n/u).map((line) => line.trim()).find(Boolean);
|
|
134
|
+
return firstLine ? normalizeVersionText(firstLine) : null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function versionComparisonLabel(currentVersion, targetVersion) {
|
|
138
|
+
const comparison = compareVersions(currentVersion, targetVersion);
|
|
139
|
+
if (comparison === null) {
|
|
140
|
+
return 'unknown';
|
|
141
|
+
}
|
|
142
|
+
if (comparison < 0) {
|
|
143
|
+
return 'behind';
|
|
144
|
+
}
|
|
145
|
+
if (comparison > 0) {
|
|
146
|
+
return 'ahead';
|
|
147
|
+
}
|
|
148
|
+
return 'same';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function refreshCandidateNote(workspaceRoot) {
|
|
152
|
+
return /(^|\/)(archive|archives|归档)(\/|$)/iu.test(String(workspaceRoot ?? ''))
|
|
153
|
+
? '归档项目,建议先确认'
|
|
154
|
+
: '可直接处理';
|
|
155
|
+
}
|
|
156
|
+
|
|
23
157
|
async function readPackageInfo(packageRoot = DEFAULT_PACKAGE_ROOT) {
|
|
24
158
|
const packageJsonPath = path.join(packageRoot, 'package.json');
|
|
25
159
|
const raw = await fs.readFile(packageJsonPath, 'utf8');
|
|
@@ -101,6 +235,109 @@ async function resolveOpenPrdExecutable(deps = {}) {
|
|
|
101
235
|
};
|
|
102
236
|
}
|
|
103
237
|
|
|
238
|
+
async function inspectPublishedVersion(packageInfo, options = {}, deps = {}) {
|
|
239
|
+
const runCommand = deps.runCommand ?? runProcess;
|
|
240
|
+
const packageName = packageInfo.name ?? packageNameFromSource(options.source ?? DEFAULT_SELF_UPDATE_SOURCE);
|
|
241
|
+
const command = 'npm';
|
|
242
|
+
const args = ['view', packageName, 'version', '--json'];
|
|
243
|
+
const result = await runCommand(command, args, {
|
|
244
|
+
cwd: options.cwd ?? packageInfo.packageRoot ?? process.cwd(),
|
|
245
|
+
env: options.env ?? process.env,
|
|
246
|
+
});
|
|
247
|
+
const publishedVersion = result.ok ? parseVersionOutput(result.stdout) : null;
|
|
248
|
+
const errors = [];
|
|
249
|
+
if (!result.ok) {
|
|
250
|
+
errors.push(result.stderr.trim() || `npm view failed with exit code ${result.exitCode}.`);
|
|
251
|
+
} else if (!publishedVersion) {
|
|
252
|
+
errors.push('Unable to parse the published npm version for @openprd/cli.');
|
|
253
|
+
}
|
|
254
|
+
const comparison = versionComparisonLabel(packageInfo.version, publishedVersion);
|
|
255
|
+
return {
|
|
256
|
+
ok: errors.length === 0,
|
|
257
|
+
packageName,
|
|
258
|
+
currentVersion: normalizeVersionText(packageInfo.version),
|
|
259
|
+
publishedVersion,
|
|
260
|
+
updateAvailable: comparison === 'behind',
|
|
261
|
+
comparison,
|
|
262
|
+
command: {
|
|
263
|
+
command,
|
|
264
|
+
args,
|
|
265
|
+
display: formatCommand(command, args),
|
|
266
|
+
},
|
|
267
|
+
result,
|
|
268
|
+
errors,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function resolveExecutableVersion(executable, options = {}, deps = {}) {
|
|
273
|
+
const runCommand = deps.runCommand ?? runProcess;
|
|
274
|
+
const result = await runCommand(executable, ['--version'], {
|
|
275
|
+
cwd: options.cwd ?? process.cwd(),
|
|
276
|
+
env: options.env ?? process.env,
|
|
277
|
+
});
|
|
278
|
+
const version = result.ok ? parseVersionOutput(result.stdout || result.stderr) : null;
|
|
279
|
+
const errors = [];
|
|
280
|
+
if (!result.ok) {
|
|
281
|
+
errors.push(result.stderr.trim() || `Version check failed with exit code ${result.exitCode}.`);
|
|
282
|
+
} else if (!version) {
|
|
283
|
+
errors.push('Unable to parse the installed OpenPrd version after update.');
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
ok: errors.length === 0,
|
|
287
|
+
executable,
|
|
288
|
+
version,
|
|
289
|
+
command: {
|
|
290
|
+
command: executable,
|
|
291
|
+
args: ['--version'],
|
|
292
|
+
display: formatCommand(executable, ['--version']),
|
|
293
|
+
},
|
|
294
|
+
result,
|
|
295
|
+
errors,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function collectRefreshCandidates(targetVersion, options = {}, deps = {}) {
|
|
300
|
+
const normalizedTargetVersion = normalizeVersionText(targetVersion);
|
|
301
|
+
if (!normalizedTargetVersion) {
|
|
302
|
+
return {
|
|
303
|
+
ok: true,
|
|
304
|
+
targetVersion: null,
|
|
305
|
+
registryPath: null,
|
|
306
|
+
staleCount: 0,
|
|
307
|
+
total: 0,
|
|
308
|
+
projects: [],
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const registry = await (deps.readWorkspaceRegistry ?? readWorkspaceRegistry)({
|
|
312
|
+
openprdHome: options.openprdHome,
|
|
313
|
+
});
|
|
314
|
+
const projects = registry.entries
|
|
315
|
+
.filter((entry) => {
|
|
316
|
+
if (!entry.openprdVersion) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
return compareVersions(entry.openprdVersion, normalizedTargetVersion) === -1;
|
|
320
|
+
})
|
|
321
|
+
.map((entry) => ({
|
|
322
|
+
workspaceRoot: entry.workspaceRoot,
|
|
323
|
+
workspaceName: entry.workspaceName ?? path.basename(entry.workspaceRoot),
|
|
324
|
+
currentVersion: normalizeVersionText(entry.openprdVersion),
|
|
325
|
+
targetVersion: normalizedTargetVersion,
|
|
326
|
+
suggestedAction: '刷新 OpenPrD',
|
|
327
|
+
note: refreshCandidateNote(entry.workspaceRoot),
|
|
328
|
+
}))
|
|
329
|
+
.sort((left, right) => left.workspaceRoot.localeCompare(right.workspaceRoot));
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
ok: true,
|
|
333
|
+
targetVersion: normalizedTargetVersion,
|
|
334
|
+
registryPath: registry.registryPath,
|
|
335
|
+
staleCount: registry.staleEntries.length,
|
|
336
|
+
total: projects.length,
|
|
337
|
+
projects,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
104
341
|
function buildSelfUpdatePlan(options = {}) {
|
|
105
342
|
const source = options.source ?? DEFAULT_SELF_UPDATE_SOURCE;
|
|
106
343
|
const command = options.packageManager ?? 'npm';
|
|
@@ -164,9 +401,41 @@ export function createSelfUpdateWorkspace(deps = {}) {
|
|
|
164
401
|
const runCommand = deps.runCommand ?? runProcess;
|
|
165
402
|
const packageRoot = deps.packageRoot ?? DEFAULT_PACKAGE_ROOT;
|
|
166
403
|
|
|
404
|
+
async function checkSelfUpdateWorkspace(options = {}) {
|
|
405
|
+
const packageInfo = await (deps.readPackageInfo ?? readPackageInfo)(packageRoot);
|
|
406
|
+
const versionCheck = await inspectPublishedVersion(packageInfo, options, {
|
|
407
|
+
...deps,
|
|
408
|
+
runCommand,
|
|
409
|
+
});
|
|
410
|
+
const refreshTargetVersion = versionCheck.updateAvailable
|
|
411
|
+
? versionCheck.publishedVersion
|
|
412
|
+
: packageInfo.version;
|
|
413
|
+
const refreshCandidates = await collectRefreshCandidates(refreshTargetVersion, options, deps);
|
|
414
|
+
return {
|
|
415
|
+
ok: versionCheck.ok,
|
|
416
|
+
action: 'self-update-check',
|
|
417
|
+
checkOnly: true,
|
|
418
|
+
source: 'npm-published-version',
|
|
419
|
+
package: packageInfo,
|
|
420
|
+
currentVersion: normalizeVersionText(packageInfo.version),
|
|
421
|
+
publishedVersion: versionCheck.publishedVersion,
|
|
422
|
+
updateAvailable: versionCheck.updateAvailable,
|
|
423
|
+
comparison: versionCheck.comparison,
|
|
424
|
+
versionCheck,
|
|
425
|
+
refreshCandidates,
|
|
426
|
+
errors: [...versionCheck.errors],
|
|
427
|
+
nextActions: versionCheck.updateAvailable
|
|
428
|
+
? ['Run `openprd self-update` to install the published CLI version.']
|
|
429
|
+
: [],
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
167
433
|
async function selfUpdateWorkspace(options = {}) {
|
|
168
434
|
const packageInfo = await (deps.readPackageInfo ?? readPackageInfo)(packageRoot);
|
|
169
435
|
const localCheckout = await (deps.isLocalSourceCheckout ?? isLocalSourceCheckout)(packageRoot);
|
|
436
|
+
if (options.check) {
|
|
437
|
+
return checkSelfUpdateWorkspace(options);
|
|
438
|
+
}
|
|
170
439
|
const plan = buildSelfUpdatePlan(options);
|
|
171
440
|
const base = {
|
|
172
441
|
ok: true,
|
|
@@ -178,6 +447,15 @@ export function createSelfUpdateWorkspace(deps = {}) {
|
|
|
178
447
|
installCommand: plan.install,
|
|
179
448
|
result: null,
|
|
180
449
|
resolvedExecutable: null,
|
|
450
|
+
installedVersion: null,
|
|
451
|
+
refreshCandidates: {
|
|
452
|
+
ok: true,
|
|
453
|
+
targetVersion: null,
|
|
454
|
+
registryPath: null,
|
|
455
|
+
staleCount: 0,
|
|
456
|
+
total: 0,
|
|
457
|
+
projects: [],
|
|
458
|
+
},
|
|
181
459
|
errors: [],
|
|
182
460
|
nextActions: [],
|
|
183
461
|
};
|
|
@@ -213,19 +491,36 @@ export function createSelfUpdateWorkspace(deps = {}) {
|
|
|
213
491
|
const resolvedExecutable = installResult.ok
|
|
214
492
|
? await resolveOpenPrdExecutable(deps)
|
|
215
493
|
: null;
|
|
494
|
+
const installedVersion = installResult.ok && resolvedExecutable?.ok
|
|
495
|
+
? await resolveExecutableVersion(resolvedExecutable.executable, options, {
|
|
496
|
+
...deps,
|
|
497
|
+
runCommand,
|
|
498
|
+
})
|
|
499
|
+
: null;
|
|
500
|
+
const refreshCandidates = installedVersion?.ok
|
|
501
|
+
? await collectRefreshCandidates(installedVersion.version, options, deps)
|
|
502
|
+
: base.refreshCandidates;
|
|
216
503
|
|
|
217
504
|
return {
|
|
218
505
|
...base,
|
|
219
|
-
ok: installResult.ok && (resolvedExecutable?.ok ?? false),
|
|
506
|
+
ok: installResult.ok && (resolvedExecutable?.ok ?? false) && (installedVersion?.ok ?? false),
|
|
220
507
|
result: installResult,
|
|
221
508
|
resolvedExecutable,
|
|
509
|
+
installedVersion,
|
|
510
|
+
refreshCandidates,
|
|
222
511
|
errors: [
|
|
223
512
|
...(installResult.ok ? [] : [installResult.stderr.trim() || `Self-update command failed with exit code ${installResult.exitCode}.`]),
|
|
224
513
|
...(installResult.ok && !resolvedExecutable?.ok ? [resolvedExecutable?.error ?? 'Unable to resolve updated openprd executable.'] : []),
|
|
514
|
+
...(installedVersion?.errors ?? []),
|
|
515
|
+
],
|
|
516
|
+
nextActions: [
|
|
517
|
+
...(installResult.ok && !resolvedExecutable?.ok
|
|
518
|
+
? ['Check that the global npm bin directory is on PATH, then run openprd update <project>.']
|
|
519
|
+
: []),
|
|
520
|
+
...((refreshCandidates?.total ?? 0) > 0
|
|
521
|
+
? ['If you want to refresh older workspaces too, review the refreshCandidates list before running fleet or per-project updates.']
|
|
522
|
+
: []),
|
|
225
523
|
],
|
|
226
|
-
nextActions: installResult.ok && !resolvedExecutable?.ok
|
|
227
|
-
? ['Check that the global npm bin directory is on PATH, then run openprd update <project>.']
|
|
228
|
-
: [],
|
|
229
524
|
};
|
|
230
525
|
}
|
|
231
526
|
|
|
@@ -318,6 +613,7 @@ export function createSelfUpdateWorkspace(deps = {}) {
|
|
|
318
613
|
}
|
|
319
614
|
|
|
320
615
|
return {
|
|
616
|
+
checkSelfUpdateWorkspace,
|
|
321
617
|
selfUpdateWorkspace,
|
|
322
618
|
upgradeWorkspace,
|
|
323
619
|
};
|
|
@@ -325,5 +621,6 @@ export function createSelfUpdateWorkspace(deps = {}) {
|
|
|
325
621
|
|
|
326
622
|
const defaultWorkspace = createSelfUpdateWorkspace();
|
|
327
623
|
|
|
624
|
+
export const checkSelfUpdateWorkspace = defaultWorkspace.checkSelfUpdateWorkspace;
|
|
328
625
|
export const selfUpdateWorkspace = defaultWorkspace.selfUpdateWorkspace;
|
|
329
626
|
export const upgradeWorkspace = defaultWorkspace.upgradeWorkspace;
|
package/src/standards.js
CHANGED
|
@@ -370,9 +370,36 @@ function validateTextSections(relativePath, text, sections, errors) {
|
|
|
370
370
|
}
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
-
function
|
|
374
|
-
const
|
|
375
|
-
const
|
|
373
|
+
function normalizeModuleName(raw, fallback = 'project') {
|
|
374
|
+
const candidate = String(raw ?? '').trim().split('/').filter(Boolean).pop() ?? '';
|
|
375
|
+
const normalized = candidate.replace(/[^a-zA-Z0-9_-]+/g, '-').replace(/^-+|-+$/g, '');
|
|
376
|
+
return normalized || fallback;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async function resolveSourceManualModuleName(projectRoot, config = {}, fallbackModuleName = '') {
|
|
380
|
+
const configuredName = typeof config?.folderManual?.moduleName === 'string'
|
|
381
|
+
? config.folderManual.moduleName.trim()
|
|
382
|
+
: '';
|
|
383
|
+
if (configuredName) {
|
|
384
|
+
return normalizeModuleName(configuredName, normalizeModuleName(path.basename(projectRoot)));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const packageJson = await readJson(cjoin(projectRoot, 'package.json')).catch(() => null);
|
|
388
|
+
const packageName = typeof packageJson?.name === 'string' ? packageJson.name.trim() : '';
|
|
389
|
+
if (packageName) {
|
|
390
|
+
return normalizeModuleName(packageName, normalizeModuleName(path.basename(projectRoot)));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const fallbackName = typeof fallbackModuleName === 'string' ? fallbackModuleName.trim() : '';
|
|
394
|
+
if (fallbackName) {
|
|
395
|
+
return normalizeModuleName(fallbackName, normalizeModuleName(path.basename(projectRoot)));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return normalizeModuleName(path.basename(projectRoot));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function sourceManualReadmeName(moduleName, dirPath) {
|
|
402
|
+
const folderName = path.basename(dirPath).replace(/[^a-zA-Z0-9_-]+/g, '-') || moduleName;
|
|
376
403
|
return `${moduleName}_${folderName}_README.md`;
|
|
377
404
|
}
|
|
378
405
|
|
|
@@ -602,6 +629,11 @@ async function validateSourceManuals(projectRoot, errors, checks, options = {})
|
|
|
602
629
|
const sourceFiles = await collectSourceFiles(projectRoot, projectRoot, [], options.ignorePatterns, discovery);
|
|
603
630
|
const filesMissingManualRaw = [];
|
|
604
631
|
const foldersMissingManualRaw = [];
|
|
632
|
+
const moduleName = await resolveSourceManualModuleName(
|
|
633
|
+
projectRoot,
|
|
634
|
+
options.config ?? {},
|
|
635
|
+
options.folderManualModuleName,
|
|
636
|
+
);
|
|
605
637
|
|
|
606
638
|
for (const filePath of sourceFiles) {
|
|
607
639
|
const relativePath = path.relative(projectRoot, filePath);
|
|
@@ -613,7 +645,8 @@ async function validateSourceManuals(projectRoot, errors, checks, options = {})
|
|
|
613
645
|
|
|
614
646
|
const sourceDirsRaw = new Set(sourceFiles.map((filePath) => path.dirname(filePath)));
|
|
615
647
|
for (const dirPath of sourceDirsRaw) {
|
|
616
|
-
const
|
|
648
|
+
const folderLabel = dirPath === projectRoot ? moduleName : null;
|
|
649
|
+
const expectedPath = cjoin(dirPath, sourceManualReadmeName(moduleName, folderLabel ?? dirPath));
|
|
617
650
|
const relativePath = path.relative(projectRoot, expectedPath);
|
|
618
651
|
const text = await readText(expectedPath).catch(() => null);
|
|
619
652
|
if (!text || !hasAllManualSections(text)) {
|
|
@@ -785,6 +818,9 @@ export async function checkStandardsWorkspace(projectRoot, options = {}) {
|
|
|
785
818
|
if (config.docsRoot !== 'docs/basic') {
|
|
786
819
|
errors.push(`${OPENPRD_STANDARDS_CONFIG} docsRoot must be docs/basic.`);
|
|
787
820
|
}
|
|
821
|
+
if (config?.folderManual?.moduleName != null && (typeof config.folderManual.moduleName !== 'string' || !config.folderManual.moduleName.trim())) {
|
|
822
|
+
errors.push(`${OPENPRD_STANDARDS_CONFIG} folderManual.moduleName must be a non-empty string when provided.`);
|
|
823
|
+
}
|
|
788
824
|
validateDevelopmentStandardsConfig(config, errors);
|
|
789
825
|
validateGrowthConfig(config, errors);
|
|
790
826
|
for (const externalPath of normalizeStringList(config?.externalReferences?.paths)) {
|
|
@@ -847,7 +883,12 @@ export async function checkStandardsWorkspace(projectRoot, options = {}) {
|
|
|
847
883
|
&& config?.fileManual?.enabled !== false
|
|
848
884
|
&& config?.folderManual?.enabled !== false;
|
|
849
885
|
const manualReport = enforceSourceManuals
|
|
850
|
-
? await validateSourceManuals(projectRoot, errors, checks, {
|
|
886
|
+
? await validateSourceManuals(projectRoot, errors, checks, {
|
|
887
|
+
ignorePatterns,
|
|
888
|
+
externalReferencePaths,
|
|
889
|
+
config,
|
|
890
|
+
folderManualModuleName: options.folderManualModuleName,
|
|
891
|
+
})
|
|
851
892
|
: {
|
|
852
893
|
ignorePatterns,
|
|
853
894
|
externalReferencePaths,
|