@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
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 核心功能
|
|
3
|
+
* 原子持久化 scoped Gate、Evidence、Approval 与 Decision,并在同一锁内消费一次性审批。
|
|
4
|
+
*
|
|
5
|
+
* 输入
|
|
6
|
+
* 接收已规范化的门禁记录和 evaluate 请求。
|
|
7
|
+
*
|
|
8
|
+
* 输出
|
|
9
|
+
* 提供 record/list/evaluate API;所有状态保存在项目 `.openprd/gates/state.json`。
|
|
10
|
+
*
|
|
11
|
+
* 定位
|
|
12
|
+
* 位于纯策略与 CLI/高风险操作之间,负责本地单项目并发一致性。
|
|
13
|
+
*
|
|
14
|
+
* 维护规则
|
|
15
|
+
* 相同 ID 不同内容必须冲突;消费审批和写入 decision 必须在同一文件锁事务中完成。
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import { atomicWriteJson, readJsonIfExists, withFileLock } from '../kernel/atomic-store.js';
|
|
20
|
+
import { canonicalJson } from '../kernel/event-envelope.js';
|
|
21
|
+
import {
|
|
22
|
+
createApprovalRecord,
|
|
23
|
+
createEvidenceRecord,
|
|
24
|
+
createGateDefinition,
|
|
25
|
+
evaluateScopedGate,
|
|
26
|
+
} from './scoped-gates.js';
|
|
27
|
+
|
|
28
|
+
const GATE_STORE_SCHEMA_VERSION = 1;
|
|
29
|
+
|
|
30
|
+
function gateStorePath(projectRoot) {
|
|
31
|
+
return path.join(path.resolve(projectRoot), '.openprd', 'gates', 'state.json');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function emptyState() {
|
|
35
|
+
return {
|
|
36
|
+
schemaVersion: GATE_STORE_SCHEMA_VERSION,
|
|
37
|
+
revision: 0,
|
|
38
|
+
gates: {},
|
|
39
|
+
evidence: {},
|
|
40
|
+
approvals: {},
|
|
41
|
+
decisions: [],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function same(left, right) {
|
|
46
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function openGateStore(projectRoot, options = {}) {
|
|
50
|
+
const filePath = gateStorePath(projectRoot);
|
|
51
|
+
const lockPath = `${filePath}.lock`;
|
|
52
|
+
|
|
53
|
+
async function read() {
|
|
54
|
+
return { ...emptyState(), ...(await readJsonIfExists(filePath, null) ?? {}) };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function transact(operation) {
|
|
58
|
+
return withFileLock(lockPath, async () => {
|
|
59
|
+
const state = await read();
|
|
60
|
+
const result = await operation(state);
|
|
61
|
+
if (result?.changed !== false) {
|
|
62
|
+
state.revision += 1;
|
|
63
|
+
await atomicWriteJson(filePath, state);
|
|
64
|
+
}
|
|
65
|
+
return { state, result: result?.value ?? null };
|
|
66
|
+
}, options);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function upsert(collection, idField, record) {
|
|
70
|
+
return transact((state) => {
|
|
71
|
+
const id = record[idField];
|
|
72
|
+
const existing = state[collection][id];
|
|
73
|
+
if (existing && !same(existing, record)) {
|
|
74
|
+
const error = new Error(`${collection} ID ${id} 已存在且内容不同。`);
|
|
75
|
+
error.code = 'OPENPRD_GATE_ID_CONFLICT';
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
if (existing) return { changed: false, value: existing };
|
|
79
|
+
state[collection][id] = record;
|
|
80
|
+
return { value: record };
|
|
81
|
+
}).then(({ result }) => result);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
filePath,
|
|
86
|
+
read,
|
|
87
|
+
recordGate(input) {
|
|
88
|
+
const gate = createGateDefinition(input);
|
|
89
|
+
return upsert('gates', 'gateId', gate);
|
|
90
|
+
},
|
|
91
|
+
recordEvidence(input) {
|
|
92
|
+
const evidence = createEvidenceRecord(input, options);
|
|
93
|
+
return upsert('evidence', 'evidenceId', evidence);
|
|
94
|
+
},
|
|
95
|
+
recordApproval(input) {
|
|
96
|
+
const approval = createApprovalRecord(input, options);
|
|
97
|
+
return upsert('approvals', 'approvalId', approval);
|
|
98
|
+
},
|
|
99
|
+
async evaluate(input) {
|
|
100
|
+
return transact((state) => {
|
|
101
|
+
const gate = state.gates[input.gateId];
|
|
102
|
+
if (!gate) {
|
|
103
|
+
const error = new Error(`Gate 不存在:${input.gateId}。`);
|
|
104
|
+
error.code = 'OPENPRD_GATE_NOT_FOUND';
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
const decision = evaluateScopedGate({
|
|
108
|
+
gate,
|
|
109
|
+
evidence: Object.values(state.evidence),
|
|
110
|
+
approvals: Object.values(state.approvals),
|
|
111
|
+
approvalId: input.approvalId ?? null,
|
|
112
|
+
}, options);
|
|
113
|
+
const existing = state.decisions.find((item) => item.decisionId === decision.decisionId);
|
|
114
|
+
if (existing) return { changed: false, value: existing };
|
|
115
|
+
state.decisions.push(decision);
|
|
116
|
+
if (decision.approvalConsumption) {
|
|
117
|
+
const approval = state.approvals[decision.approvalConsumption.approvalId];
|
|
118
|
+
if (approval) state.approvals[approval.approvalId] = { ...approval, status: 'consumed' };
|
|
119
|
+
}
|
|
120
|
+
return { value: decision };
|
|
121
|
+
}).then(({ result }) => result);
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export { GATE_STORE_SCHEMA_VERSION, gateStorePath, openGateStore };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { ensureProjectManifest, readProjectManifest } from '../kernel/index.js';
|
|
4
|
+
import { openGateStore } from './store.js';
|
|
5
|
+
|
|
6
|
+
async function readPayload(projectRoot, jsonFile) {
|
|
7
|
+
if (!jsonFile) return {};
|
|
8
|
+
const filePath = path.isAbsolute(jsonFile) ? jsonFile : path.resolve(projectRoot, jsonFile);
|
|
9
|
+
return JSON.parse(await fs.readFile(filePath, 'utf8'));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function gateWorkspace(projectRoot, options = {}) {
|
|
13
|
+
const action = options.action ?? 'status';
|
|
14
|
+
try {
|
|
15
|
+
if (action === 'status') await readProjectManifest(projectRoot, { mode: 'read' });
|
|
16
|
+
else await ensureProjectManifest(projectRoot);
|
|
17
|
+
const payload = await readPayload(projectRoot, options.jsonFile);
|
|
18
|
+
const store = openGateStore(projectRoot);
|
|
19
|
+
let result;
|
|
20
|
+
if (action === 'status') result = await store.read();
|
|
21
|
+
else if (action === 'define') result = await store.recordGate(payload.gate ?? payload);
|
|
22
|
+
else if (action === 'evidence') result = await store.recordEvidence(payload.evidence ?? payload);
|
|
23
|
+
else if (action === 'approve') result = await store.recordApproval(payload.approval ?? payload);
|
|
24
|
+
else if (action === 'evaluate') {
|
|
25
|
+
result = await store.evaluate({
|
|
26
|
+
gateId: options.gateId ?? payload.gateId,
|
|
27
|
+
approvalId: options.approvalId ?? payload.approvalId ?? null,
|
|
28
|
+
});
|
|
29
|
+
} else throw new TypeError(`Unsupported gate action: ${action}`);
|
|
30
|
+
return { ok: true, action: `gate-${action}`, projectRoot: path.resolve(projectRoot), result, file: store.filePath };
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return {
|
|
33
|
+
ok: false,
|
|
34
|
+
action: `gate-${action}`,
|
|
35
|
+
projectRoot: path.resolve(projectRoot),
|
|
36
|
+
error: { code: error?.code ?? 'OPENPRD_GATE_ERROR', message: error instanceof Error ? error.message : String(error) },
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { gateWorkspace };
|