@openprd/cli 0.1.18 → 0.1.21
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/changes/openprd-control-plane-v020/.openprd.yaml +2 -0
- package/.openprd/changes/openprd-control-plane-v020/design.md +78 -0
- package/.openprd/changes/openprd-control-plane-v020/proposal.md +54 -0
- package/.openprd/changes/openprd-control-plane-v020/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/openprd-control-plane-v020/task-events.jsonl +27 -0
- package/.openprd/changes/openprd-control-plane-v020/tasks-002.md +35 -0
- package/.openprd/changes/openprd-control-plane-v020/tasks.md +427 -0
- package/.openprd/changes/openprd-generated-change/.openprd.yaml +2 -0
- package/.openprd/changes/openprd-generated-change/design.md +50 -0
- package/.openprd/changes/openprd-generated-change/proposal.md +37 -0
- package/.openprd/changes/openprd-generated-change/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/openprd-generated-change/task-events.jsonl +21 -0
- package/.openprd/changes/openprd-generated-change/tasks.md +357 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/.openprd.yaml +2 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/design.md +52 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/proposal.md +35 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/task-events.jsonl +1 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/tasks.md +170 -0
- package/.openprd/design/active/asset-spec.md +19 -14
- package/.openprd/design/active/direction-plan.md +19 -3
- package/.openprd/design/active/facts-sheet.md +16 -7
- package/.openprd/design/active/image-preflight.md +6 -5
- package/.openprd/design/active/review-studio-v020-directions/compare-plan.json +34 -0
- package/.openprd/design/active/review-studio-v020-directions/contact-sheet.jpg +0 -0
- package/.openprd/design/active/review-studio-v020-directions/crops/01.png +0 -0
- package/.openprd/design/active/review-studio-v020-directions/crops/02.png +0 -0
- package/.openprd/design/active/review-studio-v020-directions/crops/03.png +0 -0
- package/.openprd/design/active/review-studio-v020-directions/focus-board.template.json +69 -0
- package/.openprd/design/active/review-studio-v020-directions/parallel-board.template.json +45 -0
- package/.openprd/design/active/review-studio-v020-directions/reference-set.json +143 -0
- package/.openprd/design/active/review-studio-v020-directions/source.png +0 -0
- package/.openprd/design/active/selected-direction.md +23 -9
- package/.openprd/engagements/active/control-plane-architecture.json +203 -0
- package/.openprd/engagements/active/control-plane-intake.json +418 -0
- package/.openprd/engagements/active/prd.md +186 -100
- package/.openprd/engagements/active/review-presentation-v0018.json +176 -0
- package/.openprd/i18n-config.json +12 -0
- package/.openprd/ledger/events.jsonl +6 -0
- package/.openprd/ledger/heads/ebea1a71a9daa566f1c91b53.json +10 -0
- package/.openprd/manifest.json +21 -0
- package/AGENTS.md +1 -0
- package/README.md +22 -0
- package/README_EN.md +27 -0
- package/package.json +1 -1
- package/skills/openprd-frontend-design/SKILL.md +16 -0
- package/skills/openprd-harness/SKILL.md +1 -1
- package/skills/openprd-quality/SKILL.md +1 -1
- package/skills/openprd-requirement-intake/SKILL.md +1 -1
- package/skills/openprd-shared/SKILL.md +1 -1
- package/src/adapters/adapter-spi.js +193 -0
- package/src/adapters/capability-envelope.js +98 -0
- package/src/adapters/event-normalizer.js +55 -0
- package/src/adapters/index.js +4 -0
- package/src/adapters/install-safety.js +249 -0
- package/src/agent-canonical-content.js +6 -2
- package/src/agent-integration.js +147 -42
- package/src/canvas-app-client-app.js +838 -0
- package/src/canvas-app-client-helpers.js +1138 -0
- package/src/canvas-app-styles.js +898 -0
- package/src/canvas-app.html.js +6 -2781
- package/src/canvas-binding.js +414 -0
- package/src/canvas-codex-relay.js +430 -0
- package/src/canvas-i18n.js +8 -0
- package/src/canvas-scene.js +377 -0
- package/src/canvas-session-store.js +148 -0
- package/src/canvas-workspace.js +59 -1066
- package/src/cli/args.js +63 -4
- package/src/cli/gate-print.js +17 -0
- package/src/cli/quality-commands.js +18 -0
- package/src/cli/quality-print.js +10 -0
- package/src/cli/runtime-print.js +24 -0
- package/src/codex-hook-runner-template.mjs +87 -77
- package/src/codex-runtime.js +48 -5
- package/src/context/cache.js +245 -0
- package/src/context/compiler.js +438 -0
- package/src/context/constants.js +30 -0
- package/src/context/index.js +39 -0
- package/src/context/redaction.js +84 -0
- package/src/context/stable.js +69 -0
- package/src/context/telemetry.js +42 -0
- package/src/dev-standards.js +57 -0
- package/src/fleet.js +112 -95
- package/src/gates/index.js +2 -0
- package/src/gates/scoped-gates.js +256 -0
- package/src/gates/store.js +126 -0
- package/src/gates/workspace.js +41 -0
- package/src/html-artifacts.js +725 -28
- package/src/kernel/atomic-store.js +299 -0
- package/src/kernel/event-envelope.js +166 -0
- package/src/kernel/index.js +4 -0
- package/src/kernel/project-ledger.js +467 -0
- package/src/kernel/project-manifest.js +205 -0
- package/src/knowledge-v3/index.js +1 -0
- package/src/knowledge-v3/lifecycle.js +290 -0
- package/src/knowledge.js +14 -7
- package/src/openprd.js +71 -2
- package/src/review-model.js +413 -0
- package/src/review-presentation.js +1 -1
- package/src/run-harness.js +431 -37
- package/src/runtime/cli_runtime_README.md +28 -0
- package/src/runtime/errors.js +66 -0
- package/src/runtime/index.js +44 -0
- package/src/runtime/lane-schema.js +141 -0
- package/src/runtime/lane-store.js +279 -0
- package/src/runtime/task-runtime.js +449 -0
- package/src/runtime/workspace.js +179 -0
- package/src/runtime/write-set.js +206 -0
- package/src/session-binding.js +16 -3
- package/src/session-registry.js +59 -1
- package/src/upgrade/fleet-mutation.js +166 -0
- package/src/upgrade/fleet-transaction.js +398 -0
- package/src/upgrade/transaction-store.js +416 -0
- package/src/visual-compare-core.js +66 -27
- package/src/visual-compare.js +18 -12
- package/src/workspace-core.js +109 -7
- package/src/workspace-registry.js +39 -1
- package/src/workspace-workflow.js +18 -15
package/src/visual-compare.js
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
DEFAULT_PARALLEL_CARD_WIDTH,
|
|
18
18
|
DEFAULT_PARALLEL_COLUMNS,
|
|
19
19
|
FOCUS_COLORS,
|
|
20
|
+
VISUAL_BOARD_PALETTE,
|
|
20
21
|
defaultOutputPath,
|
|
21
22
|
encodePipeline,
|
|
22
23
|
escapeXml,
|
|
@@ -75,7 +76,7 @@ async function renderStandardComparison(projectRoot, comparison, options = {}) {
|
|
|
75
76
|
width: canvasWidth,
|
|
76
77
|
height: canvasHeight,
|
|
77
78
|
channels: 3,
|
|
78
|
-
background:
|
|
79
|
+
background: VISUAL_BOARD_PALETTE.canvas,
|
|
79
80
|
},
|
|
80
81
|
}).composite([
|
|
81
82
|
{ input: referencePanel.input, left: referenceLeft, top: referenceTop },
|
|
@@ -278,7 +279,7 @@ async function renderFocusBoard(projectRoot, board, options = {}) {
|
|
|
278
279
|
width: contentWidth,
|
|
279
280
|
height: canvasHeight,
|
|
280
281
|
channels: 3,
|
|
281
|
-
background:
|
|
282
|
+
background: VISUAL_BOARD_PALETTE.canvas,
|
|
282
283
|
},
|
|
283
284
|
}).composite(composites);
|
|
284
285
|
|
|
@@ -398,7 +399,7 @@ async function renderParallelBoard(projectRoot, board, options = {}) {
|
|
|
398
399
|
width: canvasWidth,
|
|
399
400
|
height: canvasHeight,
|
|
400
401
|
channels: 3,
|
|
401
|
-
background:
|
|
402
|
+
background: VISUAL_BOARD_PALETTE.canvas,
|
|
402
403
|
},
|
|
403
404
|
}).composite(composites);
|
|
404
405
|
|
|
@@ -458,11 +459,12 @@ async function renderVerificationBoard(projectRoot, board, options = {}) {
|
|
|
458
459
|
}
|
|
459
460
|
const cardWidth = parsePositiveInteger(board.payload.cardWidth ?? options.maxPanelWidth, DEFAULT_PARALLEL_CARD_WIDTH, 'cardWidth');
|
|
460
461
|
const columns = Math.max(1, Math.min(parsePositiveInteger(board.payload.columns, Math.min(DEFAULT_PARALLEL_COLUMNS, items.length), 'columns'), 4));
|
|
461
|
-
const
|
|
462
|
-
const
|
|
462
|
+
const compactResult = board.payload.presentation !== 'detailed';
|
|
463
|
+
const margin = compactResult ? 16 : 24;
|
|
464
|
+
const gap = compactResult ? 16 : 24;
|
|
463
465
|
const route = String(board.payload.route ?? board.payload.flow ?? board.payload.method ?? '').trim();
|
|
464
466
|
const summary = String(board.payload.summary ?? copy.verificationSummary(route));
|
|
465
|
-
const titleBlock = titleBlockSvg(
|
|
467
|
+
const titleBlock = compactResult ? null : titleBlockSvg(
|
|
466
468
|
columns * cardWidth + (columns - 1) * gap,
|
|
467
469
|
String(board.payload.title ?? copy.verificationTitle),
|
|
468
470
|
summary,
|
|
@@ -475,6 +477,7 @@ async function renderVerificationBoard(projectRoot, board, options = {}) {
|
|
|
475
477
|
cardWidth,
|
|
476
478
|
eyebrow: copy.verificationCardEyebrow,
|
|
477
479
|
locale,
|
|
480
|
+
compactEvidence: compactResult,
|
|
478
481
|
}));
|
|
479
482
|
}
|
|
480
483
|
|
|
@@ -483,11 +486,12 @@ async function renderVerificationBoard(projectRoot, board, options = {}) {
|
|
|
483
486
|
rowHeights.push(Math.max(...renderedCards.slice(index, index + columns).map((card) => card.height)));
|
|
484
487
|
}
|
|
485
488
|
const canvasWidth = margin * 2 + columns * cardWidth + (columns - 1) * gap;
|
|
486
|
-
const
|
|
487
|
-
const
|
|
489
|
+
const headerHeight = titleBlock ? titleBlock.height + 18 : 0;
|
|
490
|
+
const canvasHeight = margin * 2 + headerHeight + rowHeights.reduce((sum, value) => sum + value, 0) + Math.max(0, rowHeights.length - 1) * gap;
|
|
491
|
+
const composites = titleBlock ? [
|
|
488
492
|
{ input: titleBlock.input, left: margin, top: margin },
|
|
489
|
-
];
|
|
490
|
-
let currentTop = margin +
|
|
493
|
+
] : [];
|
|
494
|
+
let currentTop = margin + headerHeight;
|
|
491
495
|
for (let row = 0; row < rowHeights.length; row += 1) {
|
|
492
496
|
const rowCards = renderedCards.slice(row * columns, row * columns + columns);
|
|
493
497
|
for (const [column, card] of rowCards.entries()) {
|
|
@@ -505,7 +509,7 @@ async function renderVerificationBoard(projectRoot, board, options = {}) {
|
|
|
505
509
|
width: canvasWidth,
|
|
506
510
|
height: canvasHeight,
|
|
507
511
|
channels: 3,
|
|
508
|
-
background:
|
|
512
|
+
background: VISUAL_BOARD_PALETTE.canvas,
|
|
509
513
|
},
|
|
510
514
|
}).composite(composites);
|
|
511
515
|
|
|
@@ -522,6 +526,8 @@ async function renderVerificationBoard(projectRoot, board, options = {}) {
|
|
|
522
526
|
format,
|
|
523
527
|
boardSource: toWorkspacePath(projectRoot, board.sourcePath),
|
|
524
528
|
title: String(board.payload.title ?? copy.verificationTitle),
|
|
529
|
+
presentation: compactResult ? 'compact-result' : 'detailed',
|
|
530
|
+
alignmentGuidesApplied: false,
|
|
525
531
|
route: route || null,
|
|
526
532
|
layout: {
|
|
527
533
|
columns,
|
|
@@ -896,7 +902,7 @@ async function renderAlignmentBoard(projectRoot, board, options = {}) {
|
|
|
896
902
|
width: contentWidth,
|
|
897
903
|
height: canvasHeight,
|
|
898
904
|
channels: 3,
|
|
899
|
-
background:
|
|
905
|
+
background: VISUAL_BOARD_PALETTE.canvas,
|
|
900
906
|
},
|
|
901
907
|
}).composite(composites);
|
|
902
908
|
|
package/src/workspace-core.js
CHANGED
|
@@ -9,6 +9,7 @@ import { migrateLegacyOpenPrdArtifacts } from './openspec/migration.js';
|
|
|
9
9
|
import { analyzePrdSnapshot, buildPrdSnapshot, formatVersionId, getRequiredFieldDescriptors, renderPrdMarkdown, summarizeSnapshot } from './prd-core.js';
|
|
10
10
|
import { normalizeReleaseLedger } from './release-ledger.js';
|
|
11
11
|
import { appendJsonl, appendText, cjoin, exists, readJson, readText, readYaml, stringifyYaml, writeJson, writeText, writeYaml } from './fs-utils.js';
|
|
12
|
+
import { openProjectLedger } from './kernel/index.js';
|
|
12
13
|
import { OPENSPEC_TASK_MAX_ITEMS_PER_FILE } from './openspec/constants.js';
|
|
13
14
|
import { checkStandardsWorkspace, STANDARD_DOCS } from './standards.js';
|
|
14
15
|
import { timestamp } from './time.js';
|
|
@@ -100,11 +101,6 @@ const WORKSPACE_SEED_REFRESH_FILES = [
|
|
|
100
101
|
'design/templates/content-home.html',
|
|
101
102
|
'design/templates/product-launch.html',
|
|
102
103
|
'design/templates/ops-dashboard.html',
|
|
103
|
-
'design/active/facts-sheet.md',
|
|
104
|
-
'design/active/asset-spec.md',
|
|
105
|
-
'design/active/image-preflight.md',
|
|
106
|
-
'design/active/direction-plan.md',
|
|
107
|
-
'design/active/selected-direction.md',
|
|
108
104
|
'schema/prd.schema.yaml',
|
|
109
105
|
'schema/diagram-architecture.schema.yaml',
|
|
110
106
|
'schema/diagram-product-flow.schema.yaml',
|
|
@@ -138,6 +134,7 @@ const WORKSPACE_SEED_COPY_IGNORE = new Set([
|
|
|
138
134
|
'benchmarks/inbox',
|
|
139
135
|
'changes',
|
|
140
136
|
'discovery',
|
|
137
|
+
'design/active',
|
|
141
138
|
'growth',
|
|
142
139
|
'harness',
|
|
143
140
|
'knowledge',
|
|
@@ -162,6 +159,76 @@ const WORKSPACE_SEED_COPY_IGNORE = new Set([
|
|
|
162
159
|
'engagements/active/review.html',
|
|
163
160
|
'engagements/active/verification.md',
|
|
164
161
|
]);
|
|
162
|
+
const ACTIVE_DESIGN_TEMPLATE_FILES = {
|
|
163
|
+
'facts-sheet.md': `# Facts Sheet
|
|
164
|
+
|
|
165
|
+
| 字段 | 当前值 | 来源 | verifiedAt | 状态 |
|
|
166
|
+
| --- | --- | --- | --- | --- |
|
|
167
|
+
| 产品 / 项目名 | 待填写 | 待确认 | - | pending |
|
|
168
|
+
| 版本 / 发布状态 | 待填写 | 待确认 | - | pending |
|
|
169
|
+
| 关键事实 1 | 待填写 | 待确认 | - | pending |
|
|
170
|
+
| 关键事实 2 | 待填写 | 待确认 | - | pending |
|
|
171
|
+
|
|
172
|
+
## 备注
|
|
173
|
+
|
|
174
|
+
- 没核实到就保留 \`pending\`
|
|
175
|
+
- 不要把记忆当来源
|
|
176
|
+
`,
|
|
177
|
+
'asset-spec.md': `# Asset Spec
|
|
178
|
+
|
|
179
|
+
| 类型 | 资产 | 来源 | 状态 | 备注 |
|
|
180
|
+
| --- | --- | --- | --- | --- |
|
|
181
|
+
| logo | 待填写 | 待确认 | pending | |
|
|
182
|
+
| 产品图 | 待填写 | 待确认 | pending | |
|
|
183
|
+
| UI 图 | 待填写 | 待确认 | pending | |
|
|
184
|
+
| 功能图标 | 新入口/按钮/空态默认配图标 | UI 图标 Phosphor;实现库 Lucide/Tabler/React Icons;AI 品牌 LobeHub;技术栈 Tech Icons | pending | 开发新功能时 Agent 自动找图标并登记,不等用户要求 |
|
|
185
|
+
| 摄影 / 插图 | 待填写 | 待确认 | pending | |
|
|
186
|
+
| 色板 / 字体 | 待填写 | 待确认 | pending | |
|
|
187
|
+
| 动效节奏 | 待填写 | 待确认 | pending | |
|
|
188
|
+
| 背景 / 表面 | 待填写 | 待确认 | pending | |
|
|
189
|
+
| 构图记忆点 | 待填写 | 待确认 | pending | |
|
|
190
|
+
|
|
191
|
+
## 冻结变量
|
|
192
|
+
|
|
193
|
+
- lens: 待填写
|
|
194
|
+
- theme: 待填写
|
|
195
|
+
- layout: 待填写
|
|
196
|
+
- aesthetic: 待填写
|
|
197
|
+
- memory-point: 待填写
|
|
198
|
+
`,
|
|
199
|
+
'image-preflight.md': `# Image Preflight
|
|
200
|
+
|
|
201
|
+
| 问题 | 回答 |
|
|
202
|
+
| --- | --- |
|
|
203
|
+
| 真实图片是不是页面成立前提 | 待填写 |
|
|
204
|
+
| 必需图片类型 | 待填写 |
|
|
205
|
+
| 计划来源 | 待填写 |
|
|
206
|
+
| 缺失风险 | 待填写 |
|
|
207
|
+
| 降级方案 | 待填写 |
|
|
208
|
+
`,
|
|
209
|
+
'direction-plan.md': `# Direction Plan
|
|
210
|
+
|
|
211
|
+
| 方向 | 生成逻辑 | lens | theme | layout | 审美主张 | 记忆点 | 为什么不同 | 适用场景 | 主要风险 |
|
|
212
|
+
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
|
213
|
+
| 方向 1 | contrast | 待填写 | 待填写 | 待填写 | 待填写 | 待填写 | 待填写 | 待填写 | 待填写 |
|
|
214
|
+
| 方向 2 | reference-transfer | 待填写 | 待填写 | 待填写 | 待填写 | 待填写 | 待填写 | 待填写 | 待填写 |
|
|
215
|
+
| 方向 3 | design-lens | 待填写 | 待填写 | 待填写 | 待填写 | 待填写 | 待填写 | 待填写 | 待填写 |
|
|
216
|
+
`,
|
|
217
|
+
'selected-direction.md': `# Selected Direction
|
|
218
|
+
|
|
219
|
+
- selected: 待填写
|
|
220
|
+
- reason: 待填写
|
|
221
|
+
- lens: 待填写
|
|
222
|
+
- theme: 待填写
|
|
223
|
+
- layout: 待填写
|
|
224
|
+
- aesthetic: 待填写
|
|
225
|
+
- memory-point: 待填写
|
|
226
|
+
- components:
|
|
227
|
+
- 待填写
|
|
228
|
+
- follow-up risks:
|
|
229
|
+
- 待填写
|
|
230
|
+
`,
|
|
231
|
+
};
|
|
165
232
|
const OPERATIONAL_GITIGNORE_ENTRIES = [
|
|
166
233
|
'.openprd/harness/',
|
|
167
234
|
'.openprd/learning/',
|
|
@@ -662,10 +729,32 @@ function buildWorkflowTaskGraph(snapshot = null, analysis = null, options = {})
|
|
|
662
729
|
}
|
|
663
730
|
|
|
664
731
|
async function appendWorkflowEvent(ws, type, payload = {}) {
|
|
732
|
+
const at = timestamp();
|
|
733
|
+
const eventTypeSuffix = String(type ?? 'event')
|
|
734
|
+
.toLowerCase()
|
|
735
|
+
.replace(/[^a-z0-9]+/g, '.')
|
|
736
|
+
.replace(/^\.+|\.+$/g, '') || 'event';
|
|
737
|
+
const jsonPayload = JSON.parse(JSON.stringify(payload ?? {}));
|
|
738
|
+
const ledger = openProjectLedger(ws.projectRoot);
|
|
739
|
+
await ledger.append(
|
|
740
|
+
`workflow:${ws.data?.currentSessionId ?? 'workspace'}`,
|
|
741
|
+
{
|
|
742
|
+
eventType: `workflow.${eventTypeSuffix}`,
|
|
743
|
+
actor: { type: 'openprd-cli' },
|
|
744
|
+
payload: {
|
|
745
|
+
type,
|
|
746
|
+
at,
|
|
747
|
+
...jsonPayload,
|
|
748
|
+
},
|
|
749
|
+
metadata: {
|
|
750
|
+
compatibilityProjection: path.relative(ws.projectRoot, ws.paths.eventsLog).split(path.sep).join('/'),
|
|
751
|
+
},
|
|
752
|
+
},
|
|
753
|
+
);
|
|
665
754
|
await appendJsonl(ws.paths.eventsLog, {
|
|
666
755
|
type,
|
|
667
|
-
at
|
|
668
|
-
...
|
|
756
|
+
at,
|
|
757
|
+
...jsonPayload,
|
|
669
758
|
});
|
|
670
759
|
}
|
|
671
760
|
|
|
@@ -751,6 +840,12 @@ async function ensureWorkspaceSkeleton(projectRoot, options = {}) {
|
|
|
751
840
|
await fs.mkdir(cjoin(workspaceRoot, 'design', 'assets'), { recursive: true });
|
|
752
841
|
await fs.mkdir(cjoin(workspaceRoot, 'design', 'templates'), { recursive: true });
|
|
753
842
|
await fs.mkdir(cjoin(workspaceRoot, 'design', 'active'), { recursive: true });
|
|
843
|
+
for (const [filename, content] of Object.entries(ACTIVE_DESIGN_TEMPLATE_FILES)) {
|
|
844
|
+
const targetPath = cjoin(workspaceRoot, 'design', 'active', filename);
|
|
845
|
+
if (options.force || !(await exists(targetPath))) {
|
|
846
|
+
await writeText(targetPath, content);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
754
849
|
await fs.mkdir(cjoin(workspaceRoot, 'growth'), { recursive: true });
|
|
755
850
|
await fs.mkdir(cjoin(workspaceRoot, 'knowledge', 'incidents'), { recursive: true });
|
|
756
851
|
await fs.mkdir(cjoin(workspaceRoot, 'knowledge', 'patterns'), { recursive: true });
|
|
@@ -1082,6 +1177,13 @@ async function migrateWorkspaceSkeleton(projectRoot, options = {}) {
|
|
|
1082
1177
|
await fs.mkdir(cjoin(workspaceRoot, 'design', 'assets'), { recursive: true });
|
|
1083
1178
|
await fs.mkdir(cjoin(workspaceRoot, 'design', 'templates'), { recursive: true });
|
|
1084
1179
|
await fs.mkdir(cjoin(workspaceRoot, 'design', 'active'), { recursive: true });
|
|
1180
|
+
for (const [filename, content] of Object.entries(ACTIVE_DESIGN_TEMPLATE_FILES)) {
|
|
1181
|
+
const targetPath = cjoin(workspaceRoot, 'design', 'active', filename);
|
|
1182
|
+
if (!(await exists(targetPath))) {
|
|
1183
|
+
await writeText(targetPath, content);
|
|
1184
|
+
changes.push({ path: cjoin('.openprd', 'design', 'active', filename), status: 'created' });
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1085
1187
|
await fs.mkdir(cjoin(workspaceRoot, 'growth'), { recursive: true });
|
|
1086
1188
|
await fs.mkdir(cjoin(workspaceRoot, 'knowledge', 'incidents'), { recursive: true });
|
|
1087
1189
|
await fs.mkdir(cjoin(workspaceRoot, 'knowledge', 'patterns'), { recursive: true });
|
|
@@ -2,6 +2,7 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { appendJsonl, cjoin, exists, readJson, readJsonl } from './fs-utils.js';
|
|
5
|
+
import { atomicWriteText, withFileLock } from './kernel/atomic-store.js';
|
|
5
6
|
import { timestamp } from './time.js';
|
|
6
7
|
|
|
7
8
|
const OPENPRD_WORKSPACE_MARKER = '.openprd';
|
|
@@ -56,9 +57,13 @@ function normalizeWorkspaceRegistryEntry(entry) {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
function resolveOpenPrdHome(options = {}) {
|
|
60
|
+
const testHome = process.env.NODE_TEST_CONTEXT
|
|
61
|
+
? cjoin(os.tmpdir(), 'openprd-node-test-home', String(process.pid))
|
|
62
|
+
: null;
|
|
59
63
|
return path.resolve(
|
|
60
64
|
options.openprdHome
|
|
61
65
|
?? process.env.OPENPRD_HOME
|
|
66
|
+
?? testHome
|
|
62
67
|
?? cjoin(os.homedir(), '.openprd'),
|
|
63
68
|
);
|
|
64
69
|
}
|
|
@@ -144,7 +149,7 @@ async function upsertWorkspaceRegistryEntry(projectRoot, options = {}) {
|
|
|
144
149
|
lastAction: snapshot.lastAction ?? existing?.lastAction ?? 'detected',
|
|
145
150
|
};
|
|
146
151
|
|
|
147
|
-
await appendJsonl(registry.registryPath, entry);
|
|
152
|
+
await withFileLock(`${registry.registryPath}.lock`, () => appendJsonl(registry.registryPath, entry));
|
|
148
153
|
return {
|
|
149
154
|
home: registry.home,
|
|
150
155
|
registryPath: registry.registryPath,
|
|
@@ -154,9 +159,42 @@ async function upsertWorkspaceRegistryEntry(projectRoot, options = {}) {
|
|
|
154
159
|
};
|
|
155
160
|
}
|
|
156
161
|
|
|
162
|
+
function registryBackupName(prefix = 'workspaces') {
|
|
163
|
+
return `${prefix}-${new Date().toISOString().replace(/[:.]/g, '-')}.jsonl`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function compactWorkspaceRegistry(options = {}) {
|
|
167
|
+
const registryPath = workspaceRegistryFilePath(options);
|
|
168
|
+
return withFileLock(`${registryPath}.lock`, async () => {
|
|
169
|
+
const registry = await readWorkspaceRegistry(options);
|
|
170
|
+
const staleRoots = new Set((registry.staleEntries ?? []).map((entry) => entry.workspaceRoot));
|
|
171
|
+
const entries = options.pruneStale === false
|
|
172
|
+
? registry.entries
|
|
173
|
+
: registry.entries.filter((entry) => !staleRoots.has(entry.workspaceRoot));
|
|
174
|
+
const backupDir = path.resolve(options.backupDir ?? cjoin(registry.home, 'registry', 'backups'));
|
|
175
|
+
await fs.mkdir(backupDir, { recursive: true });
|
|
176
|
+
const backupPath = cjoin(backupDir, registryBackupName('workspaces'));
|
|
177
|
+
if (await exists(registryPath)) await fs.copyFile(registryPath, backupPath);
|
|
178
|
+
else await atomicWriteText(backupPath, '');
|
|
179
|
+
const body = entries.length > 0 ? `${entries.map((entry) => JSON.stringify(entry)).join('\n')}\n` : '';
|
|
180
|
+
await atomicWriteText(registryPath, body);
|
|
181
|
+
return {
|
|
182
|
+
ok: true,
|
|
183
|
+
registryPath,
|
|
184
|
+
backupPath,
|
|
185
|
+
beforeEvents: registry.totalEvents,
|
|
186
|
+
afterEvents: entries.length,
|
|
187
|
+
kept: entries.length,
|
|
188
|
+
pruned: registry.entries.length - entries.length,
|
|
189
|
+
stale: registry.staleEntries.length,
|
|
190
|
+
};
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
157
194
|
export {
|
|
158
195
|
readWorkspaceRegistry,
|
|
159
196
|
resolveOpenPrdHome,
|
|
197
|
+
compactWorkspaceRegistry,
|
|
160
198
|
upsertWorkspaceRegistryEntry,
|
|
161
199
|
workspaceRegistryFilePath,
|
|
162
200
|
};
|
|
@@ -2350,7 +2350,7 @@ async function computeWorkspaceGuidance(ws, options = {}) {
|
|
|
2350
2350
|
}
|
|
2351
2351
|
|
|
2352
2352
|
|
|
2353
|
-
async function nextWorkspace(projectRoot) {
|
|
2353
|
+
async function nextWorkspace(projectRoot, options = {}) {
|
|
2354
2354
|
const ws = await loadWorkspace(projectRoot);
|
|
2355
2355
|
if (!(await exists(ws.workspaceRoot))) {
|
|
2356
2356
|
throw new Error(`Missing workspace: ${ws.workspaceRoot}`);
|
|
@@ -2373,22 +2373,24 @@ async function nextWorkspace(projectRoot) {
|
|
|
2373
2373
|
gates,
|
|
2374
2374
|
} = guidance;
|
|
2375
2375
|
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2376
|
+
if (!options.readOnly) {
|
|
2377
|
+
await writeJson(ws.paths.taskGraph, taskGraph);
|
|
2378
|
+
await appendWorkflowEvent(ws, 'next', {
|
|
2379
|
+
nextAction,
|
|
2380
|
+
reason,
|
|
2381
|
+
missingRequiredFields: analysis.missingRequiredFields,
|
|
2382
|
+
});
|
|
2383
|
+
if (analysis.missingRequiredFields > 0) {
|
|
2384
|
+
await appendOpenQuestions(ws, [
|
|
2385
|
+
`还有 ${analysis.missingRequiredFields} 个关键信息需要确认。`,
|
|
2386
|
+
...analysis.suggestedQuestions,
|
|
2387
|
+
]);
|
|
2388
|
+
}
|
|
2389
|
+
await appendProgress(ws, [
|
|
2390
|
+
`建议下一步: ${nextAction}。`,
|
|
2391
|
+
`原因: ${reason}`,
|
|
2386
2392
|
]);
|
|
2387
2393
|
}
|
|
2388
|
-
await appendProgress(ws, [
|
|
2389
|
-
`建议下一步: ${nextAction}。`,
|
|
2390
|
-
`原因: ${reason}`,
|
|
2391
|
-
]);
|
|
2392
2394
|
|
|
2393
2395
|
return {
|
|
2394
2396
|
ws,
|
|
@@ -2411,6 +2413,7 @@ async function nextWorkspace(projectRoot) {
|
|
|
2411
2413
|
},
|
|
2412
2414
|
brainstormSuggestion: guidance.brainstormSuggestion,
|
|
2413
2415
|
workflow: ['brainstorm', 'clarify', 'classify', 'interview', 'synthesize', 'diagram', 'review', 'freeze', 'handoff'],
|
|
2416
|
+
readOnly: Boolean(options.readOnly),
|
|
2414
2417
|
};
|
|
2415
2418
|
}
|
|
2416
2419
|
|