@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/codex-runtime.js
CHANGED
|
@@ -89,6 +89,29 @@ function runProcess(command, args = [], options = {}) {
|
|
|
89
89
|
});
|
|
90
90
|
let stdout = '';
|
|
91
91
|
let stderr = '';
|
|
92
|
+
let settled = false;
|
|
93
|
+
let timedOut = false;
|
|
94
|
+
const timeoutMs = Number.isFinite(options.timeoutMs)
|
|
95
|
+
? Math.max(1, Number(options.timeoutMs))
|
|
96
|
+
: 15000;
|
|
97
|
+
const killGraceMs = Number.isFinite(options.killGraceMs)
|
|
98
|
+
? Math.max(1, Number(options.killGraceMs))
|
|
99
|
+
: 1000;
|
|
100
|
+
let killTimer = null;
|
|
101
|
+
const timeoutTimer = setTimeout(() => {
|
|
102
|
+
timedOut = true;
|
|
103
|
+
child.kill('SIGTERM');
|
|
104
|
+
killTimer = setTimeout(() => child.kill('SIGKILL'), killGraceMs);
|
|
105
|
+
killTimer.unref?.();
|
|
106
|
+
}, timeoutMs);
|
|
107
|
+
timeoutTimer.unref?.();
|
|
108
|
+
const finish = (payload) => {
|
|
109
|
+
if (settled) return;
|
|
110
|
+
settled = true;
|
|
111
|
+
clearTimeout(timeoutTimer);
|
|
112
|
+
if (killTimer) clearTimeout(killTimer);
|
|
113
|
+
resolve(payload);
|
|
114
|
+
};
|
|
92
115
|
child.stdout.on('data', (chunk) => {
|
|
93
116
|
stdout = `${stdout}${chunk}`.slice(-64000);
|
|
94
117
|
});
|
|
@@ -96,7 +119,7 @@ function runProcess(command, args = [], options = {}) {
|
|
|
96
119
|
stderr = `${stderr}${chunk}`.slice(-64000);
|
|
97
120
|
});
|
|
98
121
|
child.on('error', (error) => {
|
|
99
|
-
|
|
122
|
+
finish({
|
|
100
123
|
ok: false,
|
|
101
124
|
command,
|
|
102
125
|
args,
|
|
@@ -104,19 +127,22 @@ function runProcess(command, args = [], options = {}) {
|
|
|
104
127
|
exitCode: null,
|
|
105
128
|
stdout,
|
|
106
129
|
stderr,
|
|
130
|
+
timedOut,
|
|
107
131
|
error: error.message,
|
|
108
132
|
});
|
|
109
133
|
});
|
|
110
|
-
child.on('close', (exitCode) => {
|
|
111
|
-
|
|
112
|
-
ok: exitCode === 0,
|
|
134
|
+
child.on('close', (exitCode, signal) => {
|
|
135
|
+
finish({
|
|
136
|
+
ok: !timedOut && exitCode === 0,
|
|
113
137
|
command,
|
|
114
138
|
args,
|
|
115
139
|
display: invocation.display,
|
|
116
140
|
exitCode,
|
|
141
|
+
signal,
|
|
117
142
|
stdout,
|
|
118
143
|
stderr,
|
|
119
|
-
|
|
144
|
+
timedOut,
|
|
145
|
+
error: timedOut ? `Command timed out after ${timeoutMs}ms.` : null,
|
|
120
146
|
});
|
|
121
147
|
});
|
|
122
148
|
});
|
|
@@ -131,6 +157,8 @@ function normalizeRunResult(command, args, result = {}) {
|
|
|
131
157
|
exitCode: result.exitCode ?? result.status ?? null,
|
|
132
158
|
stdout: String(result.stdout ?? ''),
|
|
133
159
|
stderr: String(result.stderr ?? ''),
|
|
160
|
+
timedOut: Boolean(result.timedOut),
|
|
161
|
+
signal: result.signal ? String(result.signal) : null,
|
|
134
162
|
error: result.error ? String(result.error) : null,
|
|
135
163
|
};
|
|
136
164
|
}
|
|
@@ -178,6 +206,17 @@ export function diagnoseCodexVersionFailure(result, options = {}) {
|
|
|
178
206
|
};
|
|
179
207
|
}
|
|
180
208
|
|
|
209
|
+
if (result.timedOut) {
|
|
210
|
+
return {
|
|
211
|
+
type: 'version-check-timeout',
|
|
212
|
+
summary: `Codex CLI 健康检查超时:codex --version 在 ${options.timeoutMs ?? 15000}ms 内没有返回。`,
|
|
213
|
+
missingPackage: null,
|
|
214
|
+
repairCommand,
|
|
215
|
+
manualCommand: repairCommand.display,
|
|
216
|
+
output,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
181
220
|
if (
|
|
182
221
|
result.exitCode === null
|
|
183
222
|
&& /(ENOENT|not found|command not found|no such file|spawn .*enoent)/i.test(output)
|
|
@@ -212,6 +251,8 @@ export async function checkCodexCliHealth(options = {}) {
|
|
|
212
251
|
await runner(command, args, {
|
|
213
252
|
cwd: options.cwd,
|
|
214
253
|
env: options.env ?? process.env,
|
|
254
|
+
timeoutMs: options.timeoutMs ?? 15000,
|
|
255
|
+
killGraceMs: options.killGraceMs,
|
|
215
256
|
}),
|
|
216
257
|
);
|
|
217
258
|
const repairCommand = buildCodexRepairCommand(options);
|
|
@@ -257,6 +298,8 @@ export async function repairCodexCli(options = {}) {
|
|
|
257
298
|
await runner(commandInfo.command, commandInfo.args, {
|
|
258
299
|
cwd: options.cwd,
|
|
259
300
|
env: options.env ?? process.env,
|
|
301
|
+
timeoutMs: options.repairTimeoutMs ?? 300000,
|
|
302
|
+
killGraceMs: options.killGraceMs,
|
|
260
303
|
}),
|
|
261
304
|
);
|
|
262
305
|
const recheck = result.ok ? await checkCodexCliHealth(options) : null;
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
buildContextRequestFingerprint,
|
|
7
|
+
compileContextCapsule,
|
|
8
|
+
verifyContextCapsule,
|
|
9
|
+
} from './compiler.js';
|
|
10
|
+
import {
|
|
11
|
+
CONTEXT_CACHE_SCHEMA_VERSION,
|
|
12
|
+
CONTEXT_COMPILER_VERSION,
|
|
13
|
+
} from './constants.js';
|
|
14
|
+
import { cloneStable, compareStableText, digestStable } from './stable.js';
|
|
15
|
+
|
|
16
|
+
function assertCacheKey(key) {
|
|
17
|
+
const normalized = String(key ?? '');
|
|
18
|
+
if (!/^context-v1-[a-f0-9]{64}$/.test(normalized)) {
|
|
19
|
+
throw new TypeError(`Invalid context cache key: ${normalized}`);
|
|
20
|
+
}
|
|
21
|
+
return normalized;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function buildContextCacheKey(input, { previousCapsule = null } = {}) {
|
|
25
|
+
const fingerprint = buildContextRequestFingerprint(input, { previousCapsule });
|
|
26
|
+
const hash = digestStable({
|
|
27
|
+
compilerVersion: CONTEXT_COMPILER_VERSION,
|
|
28
|
+
fingerprint,
|
|
29
|
+
}).slice('sha256:'.length);
|
|
30
|
+
return `context-v1-${hash}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function contextCachePath(cacheDir, key) {
|
|
34
|
+
return path.join(cacheDir, `${assertCacheKey(key)}.json`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function cacheEnvelope(key, capsule) {
|
|
38
|
+
if (!verifyContextCapsule(capsule)) {
|
|
39
|
+
throw new TypeError('Refusing to cache an invalid ContextCapsule.');
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
schemaVersion: CONTEXT_CACHE_SCHEMA_VERSION,
|
|
43
|
+
key: assertCacheKey(key),
|
|
44
|
+
revision: capsule.revision,
|
|
45
|
+
capsuleDigest: capsule.digest,
|
|
46
|
+
capsule,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function validEnvelope(value, key) {
|
|
51
|
+
return Boolean(
|
|
52
|
+
value
|
|
53
|
+
&& value.schemaVersion === CONTEXT_CACHE_SCHEMA_VERSION
|
|
54
|
+
&& value.key === key
|
|
55
|
+
&& value.capsuleDigest === value.capsule?.digest
|
|
56
|
+
&& verifyContextCapsule(value.capsule),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function readContextCache(cacheDir, key) {
|
|
61
|
+
const normalizedKey = assertCacheKey(key);
|
|
62
|
+
try {
|
|
63
|
+
const parsed = JSON.parse(await fs.readFile(contextCachePath(cacheDir, normalizedKey), 'utf8'));
|
|
64
|
+
return validEnvelope(parsed, normalizedKey) ? parsed.capsule : null;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if (error?.code === 'ENOENT' || error instanceof SyntaxError) return null;
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function writeContextCache(cacheDir, key, capsule) {
|
|
72
|
+
const normalizedKey = assertCacheKey(key);
|
|
73
|
+
const target = contextCachePath(cacheDir, normalizedKey);
|
|
74
|
+
const temporary = `${target}.${process.pid}.${randomUUID()}.tmp`;
|
|
75
|
+
const envelope = cacheEnvelope(normalizedKey, capsule);
|
|
76
|
+
await fs.mkdir(cacheDir, { recursive: true });
|
|
77
|
+
try {
|
|
78
|
+
await fs.writeFile(temporary, `${JSON.stringify(envelope)}\n`, 'utf8');
|
|
79
|
+
await fs.rename(temporary, target);
|
|
80
|
+
} finally {
|
|
81
|
+
await fs.rm(temporary, { force: true }).catch(() => {});
|
|
82
|
+
}
|
|
83
|
+
return target;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function normalizeInvalidationSelector(selector = {}) {
|
|
87
|
+
const keys = new Set();
|
|
88
|
+
if (selector.key) keys.add(assertCacheKey(selector.key));
|
|
89
|
+
for (const key of selector.keys ?? []) keys.add(assertCacheKey(key));
|
|
90
|
+
return {
|
|
91
|
+
keys,
|
|
92
|
+
revision: selector.revision == null ? null : String(selector.revision),
|
|
93
|
+
all: selector.all === true,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function invalidateContextCache(cacheDir, selector = {}) {
|
|
98
|
+
const normalized = normalizeInvalidationSelector(selector);
|
|
99
|
+
let names;
|
|
100
|
+
try {
|
|
101
|
+
names = await fs.readdir(cacheDir);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error?.code === 'ENOENT') return { removed: [], kept: [], errors: [] };
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
const removed = [];
|
|
107
|
+
const kept = [];
|
|
108
|
+
const errors = [];
|
|
109
|
+
for (const name of names.filter((item) => /^context-v1-[a-f0-9]{64}\.json$/.test(item)).sort()) {
|
|
110
|
+
const key = name.slice(0, -'.json'.length);
|
|
111
|
+
let shouldRemove = normalized.all || normalized.keys.has(key);
|
|
112
|
+
if (!shouldRemove && normalized.revision != null) {
|
|
113
|
+
try {
|
|
114
|
+
const parsed = JSON.parse(await fs.readFile(path.join(cacheDir, name), 'utf8'));
|
|
115
|
+
shouldRemove = parsed.revision === normalized.revision;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
errors.push({ key, error: error?.code ?? error?.name ?? 'read-error' });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (!shouldRemove) {
|
|
121
|
+
kept.push(key);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
await fs.rm(path.join(cacheDir, name), { force: true });
|
|
126
|
+
removed.push(key);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
errors.push({ key, error: error?.code ?? error?.name ?? 'remove-error' });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return { removed, kept, errors };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function createMemoryContextCache() {
|
|
135
|
+
const records = new Map();
|
|
136
|
+
const counters = { reads: 0, writes: 0, hits: 0, misses: 0, invalidations: 0 };
|
|
137
|
+
return {
|
|
138
|
+
async read(key) {
|
|
139
|
+
const normalizedKey = assertCacheKey(key);
|
|
140
|
+
counters.reads += 1;
|
|
141
|
+
if (!records.has(normalizedKey)) {
|
|
142
|
+
counters.misses += 1;
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
counters.hits += 1;
|
|
146
|
+
return cloneStable(records.get(normalizedKey).capsule);
|
|
147
|
+
},
|
|
148
|
+
async write(key, capsule) {
|
|
149
|
+
const normalizedKey = assertCacheKey(key);
|
|
150
|
+
records.set(normalizedKey, cacheEnvelope(normalizedKey, cloneStable(capsule)));
|
|
151
|
+
counters.writes += 1;
|
|
152
|
+
},
|
|
153
|
+
async invalidate(selector = {}) {
|
|
154
|
+
const normalized = normalizeInvalidationSelector(selector);
|
|
155
|
+
const removed = [];
|
|
156
|
+
const kept = [];
|
|
157
|
+
for (const [key, envelope] of [...records.entries()].sort(([left], [right]) => compareStableText(left, right))) {
|
|
158
|
+
const shouldRemove = normalized.all
|
|
159
|
+
|| normalized.keys.has(key)
|
|
160
|
+
|| (normalized.revision != null && envelope.revision === normalized.revision);
|
|
161
|
+
if (shouldRemove) {
|
|
162
|
+
records.delete(key);
|
|
163
|
+
removed.push(key);
|
|
164
|
+
} else {
|
|
165
|
+
kept.push(key);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
counters.invalidations += removed.length;
|
|
169
|
+
return { removed, kept, errors: [] };
|
|
170
|
+
},
|
|
171
|
+
stats() {
|
|
172
|
+
return { ...counters, size: records.size };
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function createFileContextCache(cacheDir) {
|
|
178
|
+
const counters = { reads: 0, writes: 0, hits: 0, misses: 0, invalidations: 0 };
|
|
179
|
+
return {
|
|
180
|
+
async read(key) {
|
|
181
|
+
counters.reads += 1;
|
|
182
|
+
const capsule = await readContextCache(cacheDir, key);
|
|
183
|
+
if (capsule) counters.hits += 1;
|
|
184
|
+
else counters.misses += 1;
|
|
185
|
+
return capsule;
|
|
186
|
+
},
|
|
187
|
+
async write(key, capsule) {
|
|
188
|
+
counters.writes += 1;
|
|
189
|
+
return writeContextCache(cacheDir, key, capsule);
|
|
190
|
+
},
|
|
191
|
+
async invalidate(selector = {}) {
|
|
192
|
+
const result = await invalidateContextCache(cacheDir, selector);
|
|
193
|
+
counters.invalidations += result.removed.length;
|
|
194
|
+
return result;
|
|
195
|
+
},
|
|
196
|
+
stats() {
|
|
197
|
+
return { ...counters };
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function compileContextWithCache(input, {
|
|
203
|
+
cache,
|
|
204
|
+
previousCapsule = null,
|
|
205
|
+
} = {}) {
|
|
206
|
+
if (!cache || typeof cache.read !== 'function' || typeof cache.write !== 'function') {
|
|
207
|
+
throw new TypeError('compileContextWithCache requires a cache with read() and write().');
|
|
208
|
+
}
|
|
209
|
+
const cacheKey = buildContextCacheKey(input, { previousCapsule });
|
|
210
|
+
const cached = await cache.read(cacheKey);
|
|
211
|
+
if (cached && verifyContextCapsule(cached)) {
|
|
212
|
+
return { capsule: cached, cacheKey, cacheStatus: 'hit', compiled: false };
|
|
213
|
+
}
|
|
214
|
+
const capsule = compileContextCapsule(input, { previousCapsule });
|
|
215
|
+
await cache.write(cacheKey, capsule);
|
|
216
|
+
return { capsule, cacheKey, cacheStatus: 'miss', compiled: true };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* A structural cold/warm benchmark. It deliberately reports operation counts
|
|
221
|
+
* rather than wall-clock thresholds, which keeps CI stable across machines.
|
|
222
|
+
*/
|
|
223
|
+
export async function runContextCacheBenchmark(input, {
|
|
224
|
+
cache = createMemoryContextCache(),
|
|
225
|
+
previousCapsule = null,
|
|
226
|
+
} = {}) {
|
|
227
|
+
const cold = await compileContextWithCache(input, { cache, previousCapsule });
|
|
228
|
+
const warm = await compileContextWithCache(input, { cache, previousCapsule });
|
|
229
|
+
return {
|
|
230
|
+
cold: {
|
|
231
|
+
cacheStatus: cold.cacheStatus,
|
|
232
|
+
compiled: cold.compiled,
|
|
233
|
+
digest: cold.capsule.digest,
|
|
234
|
+
includedCount: cold.capsule.entries.length,
|
|
235
|
+
},
|
|
236
|
+
warm: {
|
|
237
|
+
cacheStatus: warm.cacheStatus,
|
|
238
|
+
compiled: warm.compiled,
|
|
239
|
+
digest: warm.capsule.digest,
|
|
240
|
+
includedCount: warm.capsule.entries.length,
|
|
241
|
+
},
|
|
242
|
+
sameDigest: cold.capsule.digest === warm.capsule.digest,
|
|
243
|
+
cacheStats: typeof cache.stats === 'function' ? cache.stats() : null,
|
|
244
|
+
};
|
|
245
|
+
}
|