@openprd/cli 0.1.19 → 0.1.22
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/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 +183 -119
- 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 +7 -6
- package/README.md +31 -29
- package/README_EN.md +36 -39
- package/package.json +1 -1
- package/skills/openprd-frontend-design/SKILL.md +16 -0
- package/skills/openprd-harness/SKILL.md +7 -7
- package/skills/openprd-quality/SKILL.md +1 -1
- package/skills/openprd-requirement-intake/SKILL.md +1 -1
- package/skills/openprd-shared/SKILL.md +6 -6
- 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 +4 -2
- package/src/agent-integration.js +169 -46
- 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 +129 -199
- 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 +432 -38
- 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/run-harness.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { detectRuntime } from './adapters/index.js';
|
|
4
|
+
import {
|
|
5
|
+
compileContextCapsule,
|
|
6
|
+
createContextTelemetryEvent,
|
|
7
|
+
digestStable,
|
|
8
|
+
verifyContextCapsule,
|
|
9
|
+
} from './context/index.js';
|
|
3
10
|
import { labelExecutionMode, taskExecutionStrategy, describeExecutionStrategy } from './execution-strategy.js';
|
|
4
11
|
import { appendJsonl, appendText, cjoin, exists, readJson, readJsonl, writeJson, writeText } from './fs-utils.js';
|
|
12
|
+
import { appendJsonlAtomic, atomicWriteJson, readJsonIfExists, withFileLock } from './kernel/index.js';
|
|
5
13
|
import {
|
|
6
14
|
OPENPRD_HARNESS_TURN_STATE,
|
|
7
15
|
recordKnowledgeReviewSignal,
|
|
8
|
-
recordKnowledgeSkillAdoption,
|
|
9
16
|
resolveKnowledgeSkillMatches,
|
|
10
17
|
reviewKnowledgeWorkspace,
|
|
11
18
|
} from './knowledge.js';
|
|
@@ -26,6 +33,8 @@ const OPENPRD_HARNESS_REQUIREMENT_GATE = cjoin(OPENPRD_HARNESS_DIR, 'requirement
|
|
|
26
33
|
const OPENPRD_HARNESS_REQUIREMENT_GATES_DIR = cjoin(OPENPRD_HARNESS_DIR, 'requirement-gates');
|
|
27
34
|
const OPENPRD_HARNESS_SESSION_BINDINGS_DIR = cjoin(OPENPRD_HARNESS_DIR, 'session-bindings');
|
|
28
35
|
const OPENPRD_HARNESS_EVENTS = cjoin(OPENPRD_HARNESS_DIR, 'events.jsonl');
|
|
36
|
+
const OPENPRD_HARNESS_CONTEXT_CACHE_DIR = cjoin(OPENPRD_HARNESS_DIR, 'context-cache');
|
|
37
|
+
const OPENPRD_HARNESS_CONTEXT_TELEMETRY = cjoin(OPENPRD_HARNESS_DIR, 'context-telemetry.jsonl');
|
|
29
38
|
const OPENPRD_WORK_UNITS_DIR = cjoin('.openprd', 'engagements', 'work-units');
|
|
30
39
|
const OPENPRD_PARALLEL_WORKER_IMPLEMENTATION_TASK_THRESHOLD = 3;
|
|
31
40
|
const OPENPRD_LOOP_REQUIRED_IMPLEMENTATION_TASK_THRESHOLD = 10;
|
|
@@ -85,13 +94,46 @@ async function ensureRunHarness(projectRoot) {
|
|
|
85
94
|
}
|
|
86
95
|
}
|
|
87
96
|
|
|
97
|
+
function normalizeAdapterClient(value) {
|
|
98
|
+
const client = String(value ?? '').trim().toLowerCase();
|
|
99
|
+
if (client === 'claude-code') return 'claude';
|
|
100
|
+
return ['codex', 'claude', 'cursor'].includes(client) ? client : null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function runtimeEvidenceCandidate(value, source) {
|
|
104
|
+
const client = normalizeAdapterClient(value?.activeClient ?? value?.client ?? value?.agentType);
|
|
105
|
+
if (!client) return null;
|
|
106
|
+
return {
|
|
107
|
+
source,
|
|
108
|
+
client,
|
|
109
|
+
surface: value?.surface ?? 'unknown',
|
|
110
|
+
executionMode: value?.executionMode ?? 'interactive',
|
|
111
|
+
sessionId: value?.sessionId ?? null,
|
|
112
|
+
threadId: value?.threadId ?? null,
|
|
113
|
+
observedCapabilities: Array.isArray(value?.observedCapabilities) ? value.observedCapabilities : [],
|
|
114
|
+
confidence: Number.isFinite(value?.confidence) ? value.confidence : null,
|
|
115
|
+
turnId: value?.turnId ?? null,
|
|
116
|
+
agentType: value?.agentType ?? null,
|
|
117
|
+
model: value?.model ?? null,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
88
121
|
async function readRuntimeEnvironmentContext(projectRoot) {
|
|
89
122
|
const [runtimeEnvironment, installManifest, hookState] = await Promise.all([
|
|
90
123
|
readJson(harnessFile(projectRoot, OPENPRD_HARNESS_RUNTIME_ENVIRONMENT)).catch(() => null),
|
|
91
124
|
readJson(harnessFile(projectRoot, OPENPRD_HARNESS_INSTALL_MANIFEST)).catch(() => null),
|
|
92
125
|
readJson(harnessFile(projectRoot, OPENPRD_HARNESS_HOOK_STATE)).catch(() => null),
|
|
93
126
|
]);
|
|
94
|
-
const
|
|
127
|
+
const candidates = [
|
|
128
|
+
runtimeEvidenceCandidate(hookState?.runtimeEnvironment, 'hook-payload'),
|
|
129
|
+
runtimeEvidenceCandidate(runtimeEnvironment?.active, 'session-binding'),
|
|
130
|
+
].filter(Boolean);
|
|
131
|
+
const detectedRuntime = detectRuntime(candidates);
|
|
132
|
+
const selected = candidates.find((candidate) => (
|
|
133
|
+
candidate.client === detectedRuntime.activeClient
|
|
134
|
+
&& (detectedRuntime.sessionId == null || candidate.sessionId === detectedRuntime.sessionId)
|
|
135
|
+
)) ?? null;
|
|
136
|
+
const fallbackActive = runtimeEnvironment?.active
|
|
95
137
|
?? hookState?.runtimeEnvironment
|
|
96
138
|
?? {
|
|
97
139
|
activeClient: 'unknown',
|
|
@@ -101,8 +143,26 @@ async function readRuntimeEnvironmentContext(projectRoot) {
|
|
|
101
143
|
evidence: [],
|
|
102
144
|
observedCapabilities: [],
|
|
103
145
|
};
|
|
146
|
+
const active = detectedRuntime.activeClient === 'unknown'
|
|
147
|
+
? fallbackActive
|
|
148
|
+
: {
|
|
149
|
+
...fallbackActive,
|
|
150
|
+
activeClient: detectedRuntime.activeClient,
|
|
151
|
+
surface: detectedRuntime.surface,
|
|
152
|
+
executionMode: detectedRuntime.executionMode,
|
|
153
|
+
sessionId: detectedRuntime.sessionId,
|
|
154
|
+
threadId: detectedRuntime.threadId,
|
|
155
|
+
confidence: detectedRuntime.confidence,
|
|
156
|
+
evidence: detectedRuntime.evidence,
|
|
157
|
+
observedCapabilities: selected?.observedCapabilities ?? [],
|
|
158
|
+
capabilityEnvelope: detectedRuntime.capabilityEnvelope,
|
|
159
|
+
turnId: selected?.turnId ?? fallbackActive?.turnId ?? null,
|
|
160
|
+
agentType: selected?.agentType ?? fallbackActive?.agentType ?? null,
|
|
161
|
+
model: selected?.model ?? fallbackActive?.model ?? null,
|
|
162
|
+
};
|
|
104
163
|
return {
|
|
105
164
|
active,
|
|
165
|
+
detectedRuntime,
|
|
106
166
|
runtimeDetection: installManifest?.runtimeDetection ?? runtimeEnvironment?.runtimeDetection ?? null,
|
|
107
167
|
platformCapabilityPacks: installManifest?.platformCapabilityPacks ?? runtimeEnvironment?.platformCapabilityPacks ?? [],
|
|
108
168
|
files: {
|
|
@@ -113,8 +173,10 @@ async function readRuntimeEnvironmentContext(projectRoot) {
|
|
|
113
173
|
};
|
|
114
174
|
}
|
|
115
175
|
|
|
116
|
-
async function readRunState(projectRoot) {
|
|
117
|
-
|
|
176
|
+
async function readRunState(projectRoot, options = {}) {
|
|
177
|
+
if (options.ensure !== false) {
|
|
178
|
+
await ensureRunHarness(projectRoot);
|
|
179
|
+
}
|
|
118
180
|
return readJson(harnessFile(projectRoot, OPENPRD_HARNESS_RUN_STATE)).catch(() => ({
|
|
119
181
|
version: 1,
|
|
120
182
|
active: true,
|
|
@@ -122,6 +184,349 @@ async function readRunState(projectRoot) {
|
|
|
122
184
|
}));
|
|
123
185
|
}
|
|
124
186
|
|
|
187
|
+
function compactContextLine(label, value) {
|
|
188
|
+
const text = String(value ?? '').replace(/\s+/g, ' ').trim();
|
|
189
|
+
return text ? `${label}: ${text}` : null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function boundedContextText(value, maxLength = 240) {
|
|
193
|
+
const text = String(value ?? '').replace(/\s+/g, ' ').trim();
|
|
194
|
+
if (!text) return null;
|
|
195
|
+
return text.length > maxLength ? `${text.slice(0, Math.max(1, maxLength - 1))}…` : text;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function runContextRevision(context, message) {
|
|
199
|
+
return digestStable({
|
|
200
|
+
messageDigest: message ? digestStable(String(message)) : null,
|
|
201
|
+
valid: context.validation?.valid ?? false,
|
|
202
|
+
latestPrd: context.latestPrd,
|
|
203
|
+
activeChange: context.activeChange,
|
|
204
|
+
activeRequirementGate: context.activeRequirementGate,
|
|
205
|
+
focus: context.focus,
|
|
206
|
+
taskSummary: context.taskSummary,
|
|
207
|
+
nextTask: context.nextTask,
|
|
208
|
+
discovery: context.discovery,
|
|
209
|
+
lane: context.lane,
|
|
210
|
+
recommendation: context.recommendation,
|
|
211
|
+
runtimeEnvironment: context.runtimeEnvironment?.active ?? null,
|
|
212
|
+
knowledgeSkills: (context.knowledgeSkills?.matched ?? []).map((skill) => ({
|
|
213
|
+
skillName: skill.skillName,
|
|
214
|
+
path: skill.path,
|
|
215
|
+
score: skill.score,
|
|
216
|
+
sourceRef: skill.sourceRef,
|
|
217
|
+
})),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function buildRunContextCapsule(context, options = {}) {
|
|
222
|
+
const activeClient = context.runtimeEnvironment?.active?.activeClient ?? 'unknown';
|
|
223
|
+
const surface = context.runtimeEnvironment?.active?.surface ?? 'unknown';
|
|
224
|
+
const taskProgress = context.taskSummary
|
|
225
|
+
? `${context.taskSummary.completed}/${context.taskSummary.total} 完成,${context.taskSummary.pending} 待处理,${context.taskSummary.blocked} 阻塞`
|
|
226
|
+
: null;
|
|
227
|
+
const knowledge = (context.knowledgeSkills?.matched ?? []).slice(0, 3).map((skill) => ({
|
|
228
|
+
name: skill.skillName,
|
|
229
|
+
useWhen: boundedContextText(skill.useWhen ?? skill.description, 180),
|
|
230
|
+
reviewFirst: (skill.reviewFirst ?? []).slice(0, 2).map((item) => boundedContextText(item, 120)),
|
|
231
|
+
antiPatterns: (skill.antiPatterns ?? []).slice(0, 1).map((item) => boundedContextText(item, 120)),
|
|
232
|
+
sourceRef: skill.sourceRef ?? null,
|
|
233
|
+
}));
|
|
234
|
+
const activeCapabilityPack = (context.runtimeEnvironment?.platformCapabilityPacks ?? [])
|
|
235
|
+
.find((pack) => normalizeAdapterClient(pack?.client ?? pack?.id) === activeClient);
|
|
236
|
+
const compactNextTask = context.nextTask
|
|
237
|
+
? {
|
|
238
|
+
id: context.nextTask.id ?? context.nextTask.taskId ?? null,
|
|
239
|
+
taskHandle: context.nextTask.taskHandle ?? null,
|
|
240
|
+
title: boundedContextText(context.nextTask.title ?? context.nextTask.summary, 180),
|
|
241
|
+
status: context.nextTask.status ?? null,
|
|
242
|
+
}
|
|
243
|
+
: null;
|
|
244
|
+
const confirmation = context.recommendation?.executionConfirmationChecklist;
|
|
245
|
+
const compactConfirmation = confirmation?.required
|
|
246
|
+
? {
|
|
247
|
+
required: true,
|
|
248
|
+
objective: boundedContextText(confirmation.objective, 180),
|
|
249
|
+
scope: (confirmation.scope ?? []).slice(0, 2).map((item) => boundedContextText(item, 140)),
|
|
250
|
+
implementation: (confirmation.implementationItems ?? []).slice(0, 2).map((item) => boundedContextText(item, 140)),
|
|
251
|
+
verification: (confirmation.verification ?? []).slice(0, 2).map((item) => boundedContextText(item, 140)),
|
|
252
|
+
risks: (confirmation.risks ?? []).slice(0, 2).map((item) => boundedContextText(item, 140)),
|
|
253
|
+
}
|
|
254
|
+
: null;
|
|
255
|
+
const clarifyReplyRequired = (context.recommendation?.nextAction ?? '') === 'clarify-user'
|
|
256
|
+
|| context.recommendation?.title === 'clarify-user'
|
|
257
|
+
|| context.recommendation?.title === '继续本轮需求入口澄清';
|
|
258
|
+
const candidates = [
|
|
259
|
+
{
|
|
260
|
+
id: 'requirement-gate',
|
|
261
|
+
authority: 'user-confirmed',
|
|
262
|
+
freshness: 'current',
|
|
263
|
+
relevance: context.activeRequirementGate ? 100 : 0,
|
|
264
|
+
value: context.activeRequirementGate
|
|
265
|
+
? {
|
|
266
|
+
status: context.activeRequirementGate.status,
|
|
267
|
+
relevance: context.activeRequirementGate.relevance,
|
|
268
|
+
promptPreview: context.activeRequirementGate.promptPreview,
|
|
269
|
+
}
|
|
270
|
+
: null,
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
id: 'current-focus',
|
|
274
|
+
authority: 'project-canonical',
|
|
275
|
+
freshness: 'current',
|
|
276
|
+
relevance: 95,
|
|
277
|
+
value: {
|
|
278
|
+
activeChange: context.activeChange,
|
|
279
|
+
focus: {
|
|
280
|
+
changeId: context.focus?.changeId ?? null,
|
|
281
|
+
workUnitId: context.focus?.workUnitId ?? null,
|
|
282
|
+
taskHandle: context.focus?.taskHandle ?? null,
|
|
283
|
+
sessionId: context.focus?.sessionId ?? null,
|
|
284
|
+
source: context.focus?.source ?? null,
|
|
285
|
+
},
|
|
286
|
+
latestPrd: context.latestPrd
|
|
287
|
+
? {
|
|
288
|
+
versionId: context.latestPrd.versionId ?? null,
|
|
289
|
+
workUnitId: context.latestPrd.workUnitId ?? null,
|
|
290
|
+
title: boundedContextText(context.latestPrd.title, 180),
|
|
291
|
+
status: context.latestPrd.status ?? null,
|
|
292
|
+
}
|
|
293
|
+
: null,
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
id: 'project-knowledge',
|
|
298
|
+
authority: 'project-canonical',
|
|
299
|
+
freshness: 'latest',
|
|
300
|
+
relevance: knowledge.length > 0 ? 90 : 0,
|
|
301
|
+
value: knowledge.length > 0 ? knowledge : null,
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
id: 'applicable-scoped-gates',
|
|
305
|
+
authority: 'project-canonical',
|
|
306
|
+
freshness: 'current',
|
|
307
|
+
relevance: (context.scopedGates ?? []).length > 0 ? 100 : 0,
|
|
308
|
+
value: (context.scopedGates ?? []).length > 0 ? context.scopedGates : null,
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
id: 'agent-interaction-contract',
|
|
312
|
+
authority: 'project-canonical',
|
|
313
|
+
freshness: 'current',
|
|
314
|
+
relevance: 92,
|
|
315
|
+
value: {
|
|
316
|
+
expression: '面向用户使用普通人能理解的结果、影响和下一步;内部编号、命令和路径只在确有帮助时出现。',
|
|
317
|
+
replyBoundary: clarifyReplyRequired
|
|
318
|
+
? '本轮只输出需求摘要或追问一个最高价值澄清点,不承诺直接实现。'
|
|
319
|
+
: null,
|
|
320
|
+
approvalBoundary: context.recommendation?.executionConfirmationChecklist?.required
|
|
321
|
+
? '执行前必须使用当前任务绑定的确认清单,不能复用其他任务的泛化确认。'
|
|
322
|
+
: null,
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
id: 'runtime-environment',
|
|
327
|
+
authority: 'runtime-observed',
|
|
328
|
+
freshness: 'current',
|
|
329
|
+
relevance: activeClient === 'unknown' ? 20 : 90,
|
|
330
|
+
value: {
|
|
331
|
+
activeClient,
|
|
332
|
+
surface,
|
|
333
|
+
executionMode: context.runtimeEnvironment?.active?.executionMode ?? null,
|
|
334
|
+
confidence: context.runtimeEnvironment?.active?.confidence ?? null,
|
|
335
|
+
observedCapabilities: context.runtimeEnvironment?.active?.observedCapabilities ?? [],
|
|
336
|
+
capabilityPack: activeCapabilityPack
|
|
337
|
+
? {
|
|
338
|
+
id: activeCapabilityPack.id,
|
|
339
|
+
capabilities: (activeCapabilityPack.capabilities ?? []).map((capability) => ({
|
|
340
|
+
id: capability.id,
|
|
341
|
+
status: capability.status,
|
|
342
|
+
})),
|
|
343
|
+
}
|
|
344
|
+
: null,
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
id: 'task-progress',
|
|
349
|
+
authority: 'project-derived',
|
|
350
|
+
freshness: 'current',
|
|
351
|
+
relevance: context.taskSummary ? 85 : 10,
|
|
352
|
+
value: context.taskSummary
|
|
353
|
+
? { summary: taskProgress, nextTask: compactNextTask, lane: boundedContextText(context.lane?.summary, 180) }
|
|
354
|
+
: { lane: boundedContextText(context.lane?.summary, 180), nextTask: compactNextTask },
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
id: 'recommended-next-step',
|
|
358
|
+
authority: 'project-derived',
|
|
359
|
+
freshness: 'current',
|
|
360
|
+
relevance: 100,
|
|
361
|
+
value: {
|
|
362
|
+
title: boundedContextText(context.recommendation?.title, 180),
|
|
363
|
+
reason: boundedContextText(context.recommendation?.reason, 260),
|
|
364
|
+
nextAction: context.recommendation?.nextAction ?? null,
|
|
365
|
+
executionMode: context.recommendation?.executionMode ?? null,
|
|
366
|
+
executionCommand: boundedContextText(
|
|
367
|
+
context.recommendation?.executionCommand ?? context.recommendation?.preparationCommand,
|
|
368
|
+
260,
|
|
369
|
+
),
|
|
370
|
+
verifyCommand: boundedContextText(context.recommendation?.verifyCommand, 220),
|
|
371
|
+
confirmation: compactConfirmation,
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
id: 'project-health',
|
|
376
|
+
authority: 'project-derived',
|
|
377
|
+
freshness: 'current',
|
|
378
|
+
relevance: context.validation?.valid ? 50 : 100,
|
|
379
|
+
value: {
|
|
380
|
+
valid: context.validation?.valid ?? false,
|
|
381
|
+
errors: (context.validation?.errors ?? []).slice(0, 3).map((item) => boundedContextText(item, 180)),
|
|
382
|
+
warnings: (context.validation?.warnings ?? []).slice(0, 3).map((item) => boundedContextText(item, 180)),
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
];
|
|
386
|
+
const request = {
|
|
387
|
+
workspaceId: context.projectRoot,
|
|
388
|
+
revision: runContextRevision(context, options.message),
|
|
389
|
+
taskId: context.focus?.taskHandle ?? context.nextTask?.taskHandle ?? context.nextTask?.id ?? null,
|
|
390
|
+
intent: options.message ?? null,
|
|
391
|
+
delivery: options.previousCapsule ? 'delta' : 'full',
|
|
392
|
+
tokenBudget: options.tokenBudget ?? 800,
|
|
393
|
+
deltaTokenBudget: options.deltaTokenBudget ?? 250,
|
|
394
|
+
candidates,
|
|
395
|
+
};
|
|
396
|
+
const capsule = compileContextCapsule(request, { previousCapsule: options.previousCapsule ?? null });
|
|
397
|
+
const renderedLines = [
|
|
398
|
+
`OpenPrd ContextCapsule v1 · ${capsule.delivery} · ${capsule.budget.estimatedTokens}/${capsule.budget.limitTokens} tokens · ${String(capsule.digest).slice(-12)}`,
|
|
399
|
+
...capsule.entries.map((entry) => {
|
|
400
|
+
if (entry.id === 'recommended-next-step') {
|
|
401
|
+
return [
|
|
402
|
+
compactContextLine('建议下一步', entry.value?.title),
|
|
403
|
+
compactContextLine('这样安排的原因', entry.value?.reason),
|
|
404
|
+
compactContextLine('内部执行参考', entry.value?.executionCommand ?? entry.value?.preparationCommand),
|
|
405
|
+
].filter(Boolean).join('\n');
|
|
406
|
+
}
|
|
407
|
+
if (entry.id === 'task-progress') {
|
|
408
|
+
return [
|
|
409
|
+
compactContextLine('当前处理路径', entry.value?.lane),
|
|
410
|
+
compactContextLine('任务进度', entry.value?.summary),
|
|
411
|
+
].filter(Boolean).join('\n');
|
|
412
|
+
}
|
|
413
|
+
if (entry.id === 'runtime-environment') return compactContextLine('当前 Agent 环境', `${entry.value?.activeClient ?? 'unknown'} / ${entry.value?.surface ?? 'unknown'}`);
|
|
414
|
+
if (entry.id === 'requirement-gate') return compactContextLine('当前处理阶段', `${entry.value?.status ?? 'active'}(${entry.value?.relevance ?? 'primary'})`);
|
|
415
|
+
if (entry.id === 'project-health') return compactContextLine('基础检查', entry.value?.valid ? '通过' : '失败');
|
|
416
|
+
if (entry.id === 'current-focus') return compactContextLine('当前聚焦事项', entry.value?.focus?.changeId ?? entry.value?.activeChange ?? entry.value?.latestPrd?.title);
|
|
417
|
+
if (entry.id === 'project-knowledge') return compactContextLine('项目经验', `${entry.value?.length ?? 0} 条已按作用域匹配`);
|
|
418
|
+
if (entry.id === 'applicable-scoped-gates') {
|
|
419
|
+
return compactContextLine('适用门禁', (entry.value ?? []).map((gate) => (
|
|
420
|
+
`${gate.operation}@${gate.scope?.type}:${gate.scope?.id}${gate.latestDecision?.status ? `=${gate.latestDecision.status}` : ''}`
|
|
421
|
+
)).join(';'));
|
|
422
|
+
}
|
|
423
|
+
if (entry.id === 'agent-interaction-contract') {
|
|
424
|
+
return [
|
|
425
|
+
compactContextLine('表达约束', entry.value?.expression),
|
|
426
|
+
compactContextLine('当前回复边界', entry.value?.replyBoundary),
|
|
427
|
+
compactContextLine('批准边界', entry.value?.approvalBoundary),
|
|
428
|
+
].filter(Boolean).join('\n');
|
|
429
|
+
}
|
|
430
|
+
return null;
|
|
431
|
+
}).filter(Boolean),
|
|
432
|
+
];
|
|
433
|
+
if (capsule.delivery === 'delta' && capsule.entries.length === 0) {
|
|
434
|
+
renderedLines.push(`上下文无变化,沿用 ${String(capsule.delta?.previousCapsuleDigest ?? '上一份 ContextCapsule').slice(-12)}`);
|
|
435
|
+
} else if (capsule.delivery === 'delta' && capsule.delta.removed.length > 0) {
|
|
436
|
+
renderedLines.push(`已移出上下文: ${capsule.delta.removed.join('、')}`);
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
capsule,
|
|
440
|
+
renderedText: renderedLines.join('\n'),
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function scopedGateMatchesContext(gate, projectRoot, context) {
|
|
445
|
+
const scope = gate?.scope;
|
|
446
|
+
if (!scope?.type || !scope?.id) return false;
|
|
447
|
+
if (scope.type === 'project') return true;
|
|
448
|
+
const ids = new Set([
|
|
449
|
+
context.activeChange,
|
|
450
|
+
context.focus?.changeId,
|
|
451
|
+
context.focus?.workUnitId,
|
|
452
|
+
context.focus?.taskHandle,
|
|
453
|
+
context.nextTask?.id,
|
|
454
|
+
context.nextTask?.taskId,
|
|
455
|
+
context.nextTask?.taskHandle,
|
|
456
|
+
context.lane?.kind,
|
|
457
|
+
context.lane?.id,
|
|
458
|
+
context.recommendation?.nextAction,
|
|
459
|
+
context.recommendation?.title,
|
|
460
|
+
path.resolve(projectRoot),
|
|
461
|
+
path.basename(path.resolve(projectRoot)),
|
|
462
|
+
].filter(Boolean).map(String));
|
|
463
|
+
return ids.has(String(scope.id));
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
async function readApplicableScopedGates(projectRoot, context) {
|
|
467
|
+
const state = await readJson(path.join(projectRoot, '.openprd', 'gates', 'state.json')).catch(() => null);
|
|
468
|
+
if (!state?.gates || typeof state.gates !== 'object') return [];
|
|
469
|
+
const decisions = Array.isArray(state.decisions) ? state.decisions : [];
|
|
470
|
+
return Object.values(state.gates)
|
|
471
|
+
.filter((gate) => scopedGateMatchesContext(gate, projectRoot, context))
|
|
472
|
+
.map((gate) => ({
|
|
473
|
+
gateId: gate.gateId,
|
|
474
|
+
scope: gate.scope,
|
|
475
|
+
operation: gate.operation,
|
|
476
|
+
risk: gate.risk,
|
|
477
|
+
requiredEvidence: gate.requiredEvidence,
|
|
478
|
+
approval: gate.approval,
|
|
479
|
+
latestDecision: [...decisions].reverse().find((decision) => decision.gateId === gate.gateId) ?? null,
|
|
480
|
+
}))
|
|
481
|
+
.sort((left, right) => String(left.gateId).localeCompare(String(right.gateId)));
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function runContextCacheFile(projectRoot, context) {
|
|
485
|
+
const active = context.runtimeEnvironment?.active ?? {};
|
|
486
|
+
const identity = digestStable({
|
|
487
|
+
projectRoot: path.resolve(projectRoot),
|
|
488
|
+
client: active.activeClient ?? 'unknown',
|
|
489
|
+
sessionId: active.sessionId ?? context.focus?.sessionId ?? null,
|
|
490
|
+
threadId: active.threadId ?? null,
|
|
491
|
+
}).replace(/^sha256:/, '').slice(0, 24);
|
|
492
|
+
return harnessFile(projectRoot, cjoin(OPENPRD_HARNESS_CONTEXT_CACHE_DIR, `last-${identity}.json`));
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async function compileRunContextDelivery(projectRoot, context, options = {}) {
|
|
496
|
+
if (!options.hookInject) {
|
|
497
|
+
return {
|
|
498
|
+
...buildRunContextCapsule(context, options),
|
|
499
|
+
cacheStatus: 'not-used',
|
|
500
|
+
cachePath: null,
|
|
501
|
+
telemetry: null,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
const cachePath = runContextCacheFile(projectRoot, context);
|
|
505
|
+
return withFileLock(`${cachePath}.lock`, async () => {
|
|
506
|
+
const cached = await readJsonIfExists(cachePath, null).catch(() => null);
|
|
507
|
+
const cachedCapsule = verifyContextCapsule(cached?.capsule) ? cached.capsule : null;
|
|
508
|
+
const explicitPrevious = verifyContextCapsule(options.previousCapsule) ? options.previousCapsule : null;
|
|
509
|
+
const previousCapsule = explicitPrevious ?? cachedCapsule;
|
|
510
|
+
const delivery = buildRunContextCapsule(context, { ...options, previousCapsule });
|
|
511
|
+
const cacheStatus = explicitPrevious ? 'explicit-previous' : cachedCapsule ? 'session-hit' : 'session-miss';
|
|
512
|
+
const telemetry = createContextTelemetryEvent({ capsule: delivery.capsule, cacheStatus });
|
|
513
|
+
await atomicWriteJson(cachePath, {
|
|
514
|
+
schemaVersion: 1,
|
|
515
|
+
workspaceId: path.resolve(projectRoot),
|
|
516
|
+
client: context.runtimeEnvironment?.active?.activeClient ?? 'unknown',
|
|
517
|
+
sessionId: context.runtimeEnvironment?.active?.sessionId ?? context.focus?.sessionId ?? null,
|
|
518
|
+
capsule: delivery.capsule,
|
|
519
|
+
});
|
|
520
|
+
await appendJsonlAtomic(harnessFile(projectRoot, OPENPRD_HARNESS_CONTEXT_TELEMETRY), telemetry);
|
|
521
|
+
return {
|
|
522
|
+
...delivery,
|
|
523
|
+
cacheStatus,
|
|
524
|
+
cachePath,
|
|
525
|
+
telemetry,
|
|
526
|
+
};
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
125
530
|
async function readActiveRequirementGate(projectRoot) {
|
|
126
531
|
const gate = await readJson(harnessFile(projectRoot, OPENPRD_HARNESS_REQUIREMENT_GATE)).catch(() => null);
|
|
127
532
|
return gate?.active ? gate : null;
|
|
@@ -256,7 +661,7 @@ function executionGate() {
|
|
|
256
661
|
confirmationChecklistRequired: true,
|
|
257
662
|
allowedIntents: ['开发', '实现', '修复', '继续任务', '落地执行', '深度调研', '深度对标', '复刻落地', '提交'],
|
|
258
663
|
readOnlyIntents: ['看看', '规划', '梳理', '分析', '评估', '预计动哪些文件', '怎么改', '代码审查'],
|
|
259
|
-
rule: '
|
|
664
|
+
rule: '用户明确要求实现、继续、深度调研、对标、部署、发布或提交时才运行 executionCommand。requirement 摘要、capture/classify/synthesize、review、change、tasks、设计合同和验证材料由 Agent 在后台自动维护,不得要求用户补固定口令、digest、work-unit id 或命令,也不得仅因材料缺失阻断正常实现。云端、生产、支付、删除、账号切换、兜底、写死、权益或额度等信号只用于提醒 Agent 优先选择配置化、迁移、标准状态机、幂等脚本或可回滚发布路径;OpenPrd 不承担授权审批、不阻断工具调用、不索取用户确认,是否需要确认由当前 Agent 依据宿主安全规则和真实上下文自行判断。规划、分析、文件影响范围和审查类请求保持只读,并基于证据回答。',
|
|
260
665
|
};
|
|
261
666
|
}
|
|
262
667
|
|
|
@@ -1846,8 +2251,7 @@ async function buildRunContext(projectRoot, dependencies, options = {}) {
|
|
|
1846
2251
|
resumeOpenSpecDiscoveryWorkspace,
|
|
1847
2252
|
validateWorkspace,
|
|
1848
2253
|
} = dependencies;
|
|
1849
|
-
await
|
|
1850
|
-
const runState = await readRunState(projectRoot);
|
|
2254
|
+
const runState = await readRunState(projectRoot, { ensure: false });
|
|
1851
2255
|
const currentTurn = await readJson(harnessFile(projectRoot, OPENPRD_HARNESS_TURN_STATE)).catch(() => null);
|
|
1852
2256
|
const runtimeEnvironment = await readRuntimeEnvironmentContext(projectRoot);
|
|
1853
2257
|
const currentTurnPrompt = String(currentTurn?.prompt ?? '').trim();
|
|
@@ -1866,7 +2270,7 @@ async function buildRunContext(projectRoot, dependencies, options = {}) {
|
|
|
1866
2270
|
warnings: [],
|
|
1867
2271
|
checks: [],
|
|
1868
2272
|
}));
|
|
1869
|
-
const next = await nextWorkspace(projectRoot).catch(() => null);
|
|
2273
|
+
const next = await nextWorkspace(projectRoot, { readOnly: true }).catch(() => null);
|
|
1870
2274
|
const requirementGate = await readActiveRequirementGate(projectRoot);
|
|
1871
2275
|
const changes = await listOpenPrdChangesWorkspace(projectRoot).catch(() => null);
|
|
1872
2276
|
const activeChange = changes?.activeChange ?? null;
|
|
@@ -1980,32 +2384,9 @@ async function buildRunContext(projectRoot, dependencies, options = {}) {
|
|
|
1980
2384
|
activeChange ? `.openprd/changes/${activeChange}` : null,
|
|
1981
2385
|
].filter(Boolean),
|
|
1982
2386
|
limit: options.hookInject ? 4 : 3,
|
|
2387
|
+
readOnly: true,
|
|
1983
2388
|
}).catch(() => ({ matched: [], summary: { matched: 0 } }));
|
|
1984
|
-
const
|
|
1985
|
-
? await recordKnowledgeSkillAdoption(projectRoot, {
|
|
1986
|
-
matches: knowledgeSkillMatches.matched,
|
|
1987
|
-
stages: options.hookInject ? ['hit', 'referenced', 'injected'] : ['hit', 'referenced'],
|
|
1988
|
-
source: options.hookInject ? 'run-context-hook' : 'run-context',
|
|
1989
|
-
sessionId: lane.target?.sessionId ?? resolvedTarget?.sessionId ?? requirementGate?.sessionId ?? null,
|
|
1990
|
-
promptPreview: effectiveMessage,
|
|
1991
|
-
}).catch(() => null)
|
|
1992
|
-
: null;
|
|
1993
|
-
const knowledgeStageBump = {
|
|
1994
|
-
hitCount: options.hookInject ? 1 : 1,
|
|
1995
|
-
referencedCount: 1,
|
|
1996
|
-
injectedCount: options.hookInject ? 1 : 0,
|
|
1997
|
-
};
|
|
1998
|
-
const renderedKnowledgeSkills = (knowledgeSkillMatches.matched ?? []).map((skill) => ({
|
|
1999
|
-
...skill,
|
|
2000
|
-
adoption: skill.adoption
|
|
2001
|
-
? {
|
|
2002
|
-
...skill.adoption,
|
|
2003
|
-
hitCount: Number(skill.adoption.hitCount ?? 0) + knowledgeStageBump.hitCount,
|
|
2004
|
-
referencedCount: Number(skill.adoption.referencedCount ?? 0) + knowledgeStageBump.referencedCount,
|
|
2005
|
-
injectedCount: Number(skill.adoption.injectedCount ?? 0) + knowledgeStageBump.injectedCount,
|
|
2006
|
-
}
|
|
2007
|
-
: skill.adoption,
|
|
2008
|
-
}));
|
|
2389
|
+
const renderedKnowledgeSkills = knowledgeSkillMatches.matched ?? [];
|
|
2009
2390
|
|
|
2010
2391
|
const context = {
|
|
2011
2392
|
ok: validation.valid,
|
|
@@ -2078,7 +2459,8 @@ async function buildRunContext(projectRoot, dependencies, options = {}) {
|
|
|
2078
2459
|
hookInjected: Boolean(options.hookInject && renderedKnowledgeSkills.length > 0),
|
|
2079
2460
|
reviewRequired: Boolean(knowledgeSkillMatches.mandatoryCheck?.required),
|
|
2080
2461
|
reviewMode: knowledgeSkillMatches.mandatoryCheck?.mode ?? null,
|
|
2081
|
-
adoption:
|
|
2462
|
+
adoption: null,
|
|
2463
|
+
recorded: false,
|
|
2082
2464
|
},
|
|
2083
2465
|
},
|
|
2084
2466
|
files: {
|
|
@@ -2089,11 +2471,23 @@ async function buildRunContext(projectRoot, dependencies, options = {}) {
|
|
|
2089
2471
|
},
|
|
2090
2472
|
};
|
|
2091
2473
|
|
|
2092
|
-
await
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2474
|
+
context.scopedGates = await readApplicableScopedGates(projectRoot, context);
|
|
2475
|
+
const contextDelivery = await compileRunContextDelivery(projectRoot, context, {
|
|
2476
|
+
hookInject: options.hookInject,
|
|
2477
|
+
message: effectiveMessage,
|
|
2478
|
+
previousCapsule: options.previousCapsule ?? null,
|
|
2479
|
+
tokenBudget: options.tokenBudget,
|
|
2480
|
+
deltaTokenBudget: options.deltaTokenBudget,
|
|
2096
2481
|
});
|
|
2482
|
+
context.contextCapsule = contextDelivery.capsule;
|
|
2483
|
+
context.contextText = contextDelivery.renderedText;
|
|
2484
|
+
context.contextCache = {
|
|
2485
|
+
status: contextDelivery.cacheStatus,
|
|
2486
|
+
path: contextDelivery.cachePath,
|
|
2487
|
+
};
|
|
2488
|
+
context.contextTelemetry = contextDelivery.telemetry;
|
|
2489
|
+
context.readOnly = true;
|
|
2490
|
+
context.sideEffects = options.hookInject ? ['context-cache', 'context-telemetry'] : [];
|
|
2097
2491
|
|
|
2098
2492
|
return context;
|
|
2099
2493
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Lane/Task Runtime 文件夹说明书
|
|
2
|
+
|
|
3
|
+
## 核心功能
|
|
4
|
+
|
|
5
|
+
为同一 OpenPrd 项目中的多 Agent、多线程和多工作树提供可持久化、可并发控制的 Lane/Task 运行时。
|
|
6
|
+
|
|
7
|
+
## 输入
|
|
8
|
+
|
|
9
|
+
接收项目级 lane、task、claim 身份、租约、write-set、revision 与验证证据。
|
|
10
|
+
|
|
11
|
+
## 输出
|
|
12
|
+
|
|
13
|
+
输出原子 task claim、心跳续租、释放、完成、过期恢复、CAS 冲突和 write-set 冲突结果。
|
|
14
|
+
|
|
15
|
+
## 定位
|
|
16
|
+
|
|
17
|
+
本目录是 OpenPrd 项目控制面的独立运行时内核;当前不直接接管 CLI、loop、session 或平台 Adapter。
|
|
18
|
+
|
|
19
|
+
## 依赖
|
|
20
|
+
|
|
21
|
+
仅依赖 Node.js 20 内建模块和目录内纯 JavaScript 模块,不依赖第三方包。
|
|
22
|
+
|
|
23
|
+
## 维护规则
|
|
24
|
+
|
|
25
|
+
- 所有状态 read-modify-write 必须通过 `LaneStore` 原子事务。
|
|
26
|
+
- claim token、lease 与 revision 校验不得被调用端绕过。
|
|
27
|
+
- write-set 不能证明互斥时必须保守判冲突。
|
|
28
|
+
- CLI、Hook、loop 和平台 Adapter 接线应从 `index.js` 入口导入,不复制 runtime 状态逻辑。
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ## 核心功能
|
|
3
|
+
* 为 Lane/Task Runtime 提供稳定、可机器判断的结构化错误契约。
|
|
4
|
+
*
|
|
5
|
+
* ## 输入
|
|
6
|
+
* 接收错误码、面向人的错误说明,以及冲突任务、claim 或 revision 等诊断细节。
|
|
7
|
+
*
|
|
8
|
+
* ## 输出
|
|
9
|
+
* 导出带 code/details/toJSON 的 OpenPrdRuntimeError 与三类并发冲突构造器。
|
|
10
|
+
*
|
|
11
|
+
* ## 定位
|
|
12
|
+
* 位于 runtime 子系统的协议边界,不负责存储、重试或冲突判定。
|
|
13
|
+
*
|
|
14
|
+
* ## 依赖
|
|
15
|
+
* 仅依赖 JavaScript Error 基类,不依赖第三方包。
|
|
16
|
+
*
|
|
17
|
+
* ## 维护规则
|
|
18
|
+
* 错误码属于跨 CLI、Hook 与 Adapter 的稳定协议;新增错误时保持旧 code 与 details 字段兼容。
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export const OPENPRD_TASK_ALREADY_CLAIMED = 'OPENPRD_TASK_ALREADY_CLAIMED';
|
|
22
|
+
export const OPENPRD_WRITE_SET_CONFLICT = 'OPENPRD_WRITE_SET_CONFLICT';
|
|
23
|
+
export const OPENPRD_REVISION_CONFLICT = 'OPENPRD_REVISION_CONFLICT';
|
|
24
|
+
|
|
25
|
+
export class OpenPrdRuntimeError extends Error {
|
|
26
|
+
constructor(code, message, details = {}, options = {}) {
|
|
27
|
+
super(message, options);
|
|
28
|
+
this.name = 'OpenPrdRuntimeError';
|
|
29
|
+
this.code = code;
|
|
30
|
+
this.details = structuredClone(details ?? {});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
toJSON() {
|
|
34
|
+
return {
|
|
35
|
+
name: this.name,
|
|
36
|
+
code: this.code,
|
|
37
|
+
message: this.message,
|
|
38
|
+
details: structuredClone(this.details),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function taskAlreadyClaimedError(details = {}) {
|
|
44
|
+
const taskId = details.taskId ? ` ${details.taskId}` : '';
|
|
45
|
+
return new OpenPrdRuntimeError(
|
|
46
|
+
OPENPRD_TASK_ALREADY_CLAIMED,
|
|
47
|
+
`OpenPrd task${taskId} is already claimed or the supplied claim is no longer current.`,
|
|
48
|
+
details,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function writeSetConflictError(details = {}) {
|
|
53
|
+
return new OpenPrdRuntimeError(
|
|
54
|
+
OPENPRD_WRITE_SET_CONFLICT,
|
|
55
|
+
'OpenPrd cannot claim this task because its write-set overlaps an active claim.',
|
|
56
|
+
details,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function revisionConflictError(details = {}) {
|
|
61
|
+
return new OpenPrdRuntimeError(
|
|
62
|
+
OPENPRD_REVISION_CONFLICT,
|
|
63
|
+
`OpenPrd revision conflict: expected ${details.expectedRevision}, current ${details.actualRevision}.`,
|
|
64
|
+
details,
|
|
65
|
+
);
|
|
66
|
+
}
|