@markstanden/opencode-test-container 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +263 -2
- package/container/Containerfile +24 -0
- package/container/README.md +29 -0
- package/dist/adapters/opencode/events.d.ts +90 -0
- package/dist/adapters/opencode/events.js +213 -0
- package/dist/adapters/opencode/runtime.d.ts +87 -0
- package/dist/adapters/opencode/runtime.js +136 -0
- package/dist/adapters/podman/container.d.ts +62 -0
- package/dist/adapters/podman/container.js +119 -0
- package/dist/adapters/podman/image.d.ts +50 -0
- package/dist/adapters/podman/image.js +124 -0
- package/dist/adapters/process.d.ts +38 -0
- package/dist/adapters/process.js +71 -0
- package/dist/campaign/assess.d.ts +45 -0
- package/dist/campaign/assess.js +208 -0
- package/dist/campaign/close.d.ts +17 -0
- package/dist/campaign/close.js +112 -0
- package/dist/campaign/evidence-guard.d.ts +24 -0
- package/dist/campaign/evidence-guard.js +49 -0
- package/dist/campaign/freeze.d.ts +94 -0
- package/dist/campaign/freeze.js +297 -0
- package/dist/campaign/inventory.d.ts +62 -0
- package/dist/campaign/inventory.js +192 -0
- package/dist/campaign/judge.d.ts +209 -0
- package/dist/campaign/judge.js +1285 -0
- package/dist/campaign/ledger.d.ts +64 -0
- package/dist/campaign/ledger.js +149 -0
- package/dist/campaign/prepare.d.ts +129 -0
- package/dist/campaign/prepare.js +483 -0
- package/dist/campaign/primary.d.ts +96 -0
- package/dist/campaign/primary.js +189 -0
- package/dist/campaign/run.d.ts +151 -0
- package/dist/campaign/run.js +562 -0
- package/dist/campaign/status.d.ts +76 -0
- package/dist/campaign/status.js +237 -0
- package/dist/cli/otc.d.ts +51 -0
- package/dist/cli/otc.js +435 -0
- package/dist/contracts/canonical.d.ts +21 -0
- package/dist/contracts/canonical.js +44 -0
- package/dist/contracts/types.d.ts +261 -0
- package/dist/contracts/types.js +9 -0
- package/dist/contracts/validate.d.ts +72 -0
- package/dist/contracts/validate.js +1021 -0
- package/dist/contracts/version.d.ts +44 -0
- package/dist/contracts/version.js +147 -0
- package/dist/evaluators/docs/analysis/accounting.d.ts +140 -0
- package/dist/evaluators/docs/analysis/accounting.js +237 -0
- package/dist/evaluators/docs/analysis/blinding.d.ts +40 -0
- package/dist/evaluators/docs/analysis/blinding.js +80 -0
- package/dist/evaluators/docs/analysis/bounds.d.ts +40 -0
- package/dist/evaluators/docs/analysis/bounds.js +31 -0
- package/dist/evaluators/docs/analysis/budget.d.ts +137 -0
- package/dist/evaluators/docs/analysis/budget.js +305 -0
- package/dist/evaluators/docs/analysis/closure.d.ts +175 -0
- package/dist/evaluators/docs/analysis/closure.js +251 -0
- package/dist/evaluators/docs/analysis/decision.d.ts +59 -0
- package/dist/evaluators/docs/analysis/decision.js +161 -0
- package/dist/evaluators/docs/analysis/freeze.d.ts +201 -0
- package/dist/evaluators/docs/analysis/freeze.js +395 -0
- package/dist/evaluators/docs/analysis/guardrails.d.ts +51 -0
- package/dist/evaluators/docs/analysis/guardrails.js +194 -0
- package/dist/evaluators/docs/analysis/journal.d.ts +150 -0
- package/dist/evaluators/docs/analysis/journal.js +475 -0
- package/dist/evaluators/docs/analysis/ledger.d.ts +78 -0
- package/dist/evaluators/docs/analysis/ledger.js +145 -0
- package/dist/evaluators/docs/analysis/measures.d.ts +302 -0
- package/dist/evaluators/docs/analysis/measures.js +1069 -0
- package/dist/evaluators/docs/analysis/model.d.ts +236 -0
- package/dist/evaluators/docs/analysis/model.js +740 -0
- package/dist/evaluators/docs/analysis/pairing.d.ts +68 -0
- package/dist/evaluators/docs/analysis/pairing.js +95 -0
- package/dist/evaluators/docs/analysis/records.d.ts +229 -0
- package/dist/evaluators/docs/analysis/records.js +935 -0
- package/dist/evaluators/docs/analysis/recovery.d.ts +225 -0
- package/dist/evaluators/docs/analysis/recovery.js +507 -0
- package/dist/evaluators/docs/analysis/render.d.ts +235 -0
- package/dist/evaluators/docs/analysis/render.js +566 -0
- package/dist/evaluators/docs/analysis/replay.d.ts +15 -0
- package/dist/evaluators/docs/analysis/replay.js +1272 -0
- package/dist/evaluators/docs/analysis/screening/inventory.d.ts +136 -0
- package/dist/evaluators/docs/analysis/screening/inventory.js +307 -0
- package/dist/evaluators/docs/analysis/screening/noteVersion.d.ts +15 -0
- package/dist/evaluators/docs/analysis/screening/noteVersion.js +46 -0
- package/dist/evaluators/docs/analysis/screening/reconcile.d.ts +38 -0
- package/dist/evaluators/docs/analysis/screening/reconcile.js +40 -0
- package/dist/evaluators/docs/analysis/screening/rules.d.ts +163 -0
- package/dist/evaluators/docs/analysis/screening/rules.js +330 -0
- package/dist/evaluators/docs/analysis/screening/scan.d.ts +72 -0
- package/dist/evaluators/docs/analysis/screening/scan.js +362 -0
- package/dist/evaluators/docs/analysis/selector.d.ts +68 -0
- package/dist/evaluators/docs/analysis/selector.js +123 -0
- package/dist/evaluators/docs/assess.d.ts +129 -0
- package/dist/evaluators/docs/assess.js +433 -0
- package/dist/evaluators/docs/campaign.d.ts +133 -0
- package/dist/evaluators/docs/campaign.js +222 -0
- package/dist/evaluators/docs/index.d.ts +25 -0
- package/dist/evaluators/docs/index.js +25 -0
- package/dist/evaluators/docs/journal.d.ts +150 -0
- package/dist/evaluators/docs/journal.js +475 -0
- package/dist/evaluators/docs/scoring.d.ts +367 -0
- package/dist/evaluators/docs/scoring.js +662 -0
- package/dist/evaluators/skills/assess.d.ts +94 -0
- package/dist/evaluators/skills/assess.js +207 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +29 -0
- package/dist/legacy/prod014.d.ts +86 -0
- package/dist/legacy/prod014.js +209 -0
- package/dist/reporting/report.d.ts +79 -0
- package/dist/reporting/report.js +242 -0
- package/docs/architecture.md +106 -0
- package/docs/capability-matrix.md +84 -0
- package/docs/offline-rehearsal.md +75 -0
- package/docs/parity-record.md +81 -0
- package/docs/pr-description.md +103 -0
- package/docs/schema.md +97 -0
- package/examples/skills/README.md +13 -0
- package/examples/skills/campaign.json +73 -0
- package/examples/skills/skills/otel-helper/SKILL.md +10 -0
- package/examples/skills/suite.json +29 -0
- package/package.json +45 -5
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
/** The analyser implementation identity. */
|
|
2
|
+
export const ANALYSER_VERSION = "analysis-framework/1";
|
|
3
|
+
/** The report template this renderer follows. */
|
|
4
|
+
export const REPORT_TEMPLATE_VERSION = "trial-feedback-report/2026-11-10";
|
|
5
|
+
/**
|
|
6
|
+
* Escape one Markdown table cell.
|
|
7
|
+
* @param value The raw cell value.
|
|
8
|
+
* @returns The escaped, single-line value.
|
|
9
|
+
*/
|
|
10
|
+
export function escapeCell(value) {
|
|
11
|
+
return String(value).replace(/\|/g, "\\|").replace(/\r?\n/g, " ").trim();
|
|
12
|
+
}
|
|
13
|
+
/** Render one table from headers and rows. */
|
|
14
|
+
function table(headers, rows) {
|
|
15
|
+
const lines = [
|
|
16
|
+
`| ${headers.map(escapeCell).join(" | ")} |`,
|
|
17
|
+
`| ${headers.map(() => "---").join(" | ")} |`,
|
|
18
|
+
...rows.map((row) => `| ${row.map(escapeCell).join(" | ")} |`),
|
|
19
|
+
];
|
|
20
|
+
return lines.join("\n");
|
|
21
|
+
}
|
|
22
|
+
/** A number or an em dash for an absent/unknown value. */
|
|
23
|
+
function num(value) {
|
|
24
|
+
return value === null || value === undefined ? "—" : String(value);
|
|
25
|
+
}
|
|
26
|
+
/** Outcome count summary. */
|
|
27
|
+
const OUTCOME_ORDER = [
|
|
28
|
+
"useful-create",
|
|
29
|
+
"useful-update",
|
|
30
|
+
"transient",
|
|
31
|
+
"duplicate",
|
|
32
|
+
"misleading",
|
|
33
|
+
"none",
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* Render the setup section.
|
|
37
|
+
* @param result The analysis result.
|
|
38
|
+
* @returns Markdown.
|
|
39
|
+
*/
|
|
40
|
+
function setupSection(result) {
|
|
41
|
+
const selection = result.selection;
|
|
42
|
+
return [
|
|
43
|
+
"## Frozen setup",
|
|
44
|
+
"",
|
|
45
|
+
table(["Item", "Value"], [
|
|
46
|
+
["Manifest SHA-256", result.manifestSha256],
|
|
47
|
+
["Evidence layout", result.layout],
|
|
48
|
+
["Judgement selection policy", result.selectionPolicy],
|
|
49
|
+
["Policy identity", result.policyId],
|
|
50
|
+
[
|
|
51
|
+
"Weights",
|
|
52
|
+
`useful ${result.weights.useful}, transient ${result.weights.transient}, duplicate ${result.weights.duplicate}, misleading ${result.weights.misleading}, taskFailure ${result.weights.taskFailure}`,
|
|
53
|
+
],
|
|
54
|
+
[
|
|
55
|
+
"Selection rule",
|
|
56
|
+
selection === null
|
|
57
|
+
? "Not defined"
|
|
58
|
+
: `control ${selection.controlArm}, candidate ${selection.candidateArm}, threshold ${selection.threshold}, complete coverage ${selection.requireCompleteCoverage}`,
|
|
59
|
+
],
|
|
60
|
+
[
|
|
61
|
+
"Guardrails",
|
|
62
|
+
result.guardrails.length === 0
|
|
63
|
+
? "Not defined"
|
|
64
|
+
: result.guardrails
|
|
65
|
+
.map((guardrail) => `${guardrail.id} (${guardrail.kind})`)
|
|
66
|
+
.join("; "),
|
|
67
|
+
],
|
|
68
|
+
]),
|
|
69
|
+
"",
|
|
70
|
+
].join("\n");
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Render the accounting section.
|
|
74
|
+
* @param result The analysis result.
|
|
75
|
+
* @returns Markdown.
|
|
76
|
+
*/
|
|
77
|
+
function accountingSection(result) {
|
|
78
|
+
const rows = Object.entries(result.arms).map(([arm, report]) => {
|
|
79
|
+
const counts = report.accounting;
|
|
80
|
+
return [
|
|
81
|
+
arm,
|
|
82
|
+
num(counts.planned),
|
|
83
|
+
num(counts.recordedLaunches),
|
|
84
|
+
num(counts.completed),
|
|
85
|
+
num(counts.documented),
|
|
86
|
+
num(counts.assessed),
|
|
87
|
+
`${num(counts.gate.pass)}/${num(counts.gate.blocked)}`,
|
|
88
|
+
`${num(counts.primary.pass)}/${num(counts.primary.fail)}/${num(counts.primary.unknown)}`,
|
|
89
|
+
num(counts.utilityTotal),
|
|
90
|
+
`${num(report.interval.low)}..${num(report.interval.high)}`,
|
|
91
|
+
];
|
|
92
|
+
});
|
|
93
|
+
const outcomeRows = Object.entries(result.arms).map(([arm, report]) => [
|
|
94
|
+
arm,
|
|
95
|
+
...OUTCOME_ORDER.map((outcome) => num(report.accounting.outcomes[outcome])),
|
|
96
|
+
]);
|
|
97
|
+
return [
|
|
98
|
+
"## Accounting",
|
|
99
|
+
"",
|
|
100
|
+
table([
|
|
101
|
+
"Arm",
|
|
102
|
+
"Planned",
|
|
103
|
+
"Recorded",
|
|
104
|
+
"Completed",
|
|
105
|
+
"Documented",
|
|
106
|
+
"Scored",
|
|
107
|
+
"Gate pass/blocked",
|
|
108
|
+
"Primary P/F/U",
|
|
109
|
+
"Utility",
|
|
110
|
+
"Interval",
|
|
111
|
+
], rows),
|
|
112
|
+
"",
|
|
113
|
+
"Intervals are worst-case missing-data bounds, not confidence intervals.",
|
|
114
|
+
"",
|
|
115
|
+
table(["Arm", ...OUTCOME_ORDER], outcomeRows),
|
|
116
|
+
"",
|
|
117
|
+
].join("\n");
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Render the per-scenario section.
|
|
121
|
+
* @param result The analysis result.
|
|
122
|
+
* @returns Markdown.
|
|
123
|
+
*/
|
|
124
|
+
function scenarioSection(result) {
|
|
125
|
+
const rows = [];
|
|
126
|
+
for (const [arm, scenarios] of Object.entries(result.scenarios)) {
|
|
127
|
+
for (const [scenario, counts] of Object.entries(scenarios)) {
|
|
128
|
+
rows.push([
|
|
129
|
+
arm,
|
|
130
|
+
scenario,
|
|
131
|
+
num(counts.planned),
|
|
132
|
+
num(counts.documented),
|
|
133
|
+
num(counts.assessed),
|
|
134
|
+
`${num(counts.gate.pass)}/${num(counts.gate.blocked)}`,
|
|
135
|
+
num(counts.utilityTotal),
|
|
136
|
+
]);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return [
|
|
140
|
+
"## Per scenario",
|
|
141
|
+
"",
|
|
142
|
+
table([
|
|
143
|
+
"Arm",
|
|
144
|
+
"Scenario",
|
|
145
|
+
"Planned",
|
|
146
|
+
"Documented",
|
|
147
|
+
"Scored",
|
|
148
|
+
"Gate P/B",
|
|
149
|
+
"Utility",
|
|
150
|
+
], rows),
|
|
151
|
+
"",
|
|
152
|
+
].join("\n");
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Render the pair section.
|
|
156
|
+
* @param result The analysis result.
|
|
157
|
+
* @returns Markdown.
|
|
158
|
+
*/
|
|
159
|
+
function pairSection(result) {
|
|
160
|
+
const lines = [
|
|
161
|
+
"## Complete pairs and unmatched observations",
|
|
162
|
+
"",
|
|
163
|
+
`Declared pairs: ${num(result.pairing.pairs)}; complete pairs: ${num(result.pairing.completePairs)}; unmatched observations: ${num(result.pairing.unmatched.length)}; unpaired evidence: ${num(result.pairing.unpaired)}.`,
|
|
164
|
+
"",
|
|
165
|
+
];
|
|
166
|
+
if (result.pairing.unmatched.length > 0) {
|
|
167
|
+
lines.push(table(["Stamp", "Arm", "Scenario", "Observation score"], result.pairing.unmatched.map((row) => [
|
|
168
|
+
row.stamp,
|
|
169
|
+
row.arm,
|
|
170
|
+
row.scenario,
|
|
171
|
+
String(row.score),
|
|
172
|
+
])), "");
|
|
173
|
+
}
|
|
174
|
+
if (result.pairing.problems.length > 0) {
|
|
175
|
+
lines.push("Pairing problems:", "");
|
|
176
|
+
lines.push(...result.pairing.problems.map((problem) => `- ${problem}`));
|
|
177
|
+
lines.push("");
|
|
178
|
+
}
|
|
179
|
+
if (result.paired !== null) {
|
|
180
|
+
lines.push(`Paired comparison: ${num(result.paired.completePairs)} pairs, control better ${num(result.paired.controlBetter)}, candidate better ${num(result.paired.candidateBetter)}, ties ${num(result.paired.ties)}; delta ${num(result.paired.delta)} (interval ${num(result.paired.interval.low)}..${num(result.paired.interval.high)}).`, "");
|
|
181
|
+
}
|
|
182
|
+
if (result.bounds.delta !== null) {
|
|
183
|
+
lines.push(`Full-population candidate-minus-control interval: ${num(result.bounds.delta.low)}..${num(result.bounds.delta.high)} (worst case, not a confidence interval).`, "");
|
|
184
|
+
}
|
|
185
|
+
return lines.join("\n");
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Render the guardrail section.
|
|
189
|
+
* @param result The analysis result.
|
|
190
|
+
* @returns Markdown.
|
|
191
|
+
*/
|
|
192
|
+
function guardrailSection(result) {
|
|
193
|
+
const rows = [];
|
|
194
|
+
for (const [arm, report] of Object.entries(result.arms)) {
|
|
195
|
+
for (const summary of report.guardrails) {
|
|
196
|
+
rows.push([
|
|
197
|
+
arm,
|
|
198
|
+
summary.guardrailId,
|
|
199
|
+
summary.status,
|
|
200
|
+
`pass ${summary.coverage.pass}, fail ${summary.coverage.fail}, unknown ${summary.coverage.unknown}, n/a ${summary.coverage["not-applicable"]}`,
|
|
201
|
+
summary.evidence.slice(0, 3).join("; ") || "—",
|
|
202
|
+
]);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return [
|
|
206
|
+
"## Guardrails",
|
|
207
|
+
"",
|
|
208
|
+
table(["Arm", "Guardrail", "Status", "Coverage", "Evidence"], rows),
|
|
209
|
+
"",
|
|
210
|
+
].join("\n");
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Render the static-screen section.
|
|
214
|
+
* @param result The analysis result.
|
|
215
|
+
* @returns Markdown.
|
|
216
|
+
*/
|
|
217
|
+
function screeningSection(result) {
|
|
218
|
+
const counted = { referenced: 0, "not-referenced": 0, unresolved: 0 };
|
|
219
|
+
for (const row of result.screening.reconciliation)
|
|
220
|
+
counted[row.status] += 1;
|
|
221
|
+
const rows = result.screening.findings
|
|
222
|
+
.slice(0, 40)
|
|
223
|
+
.map((finding) => [
|
|
224
|
+
finding.ruleId,
|
|
225
|
+
finding.trial.stamp,
|
|
226
|
+
finding.role,
|
|
227
|
+
finding.source.reference,
|
|
228
|
+
finding.excerpt === "" ? "(no excerpt)" : finding.excerpt,
|
|
229
|
+
finding.explanation,
|
|
230
|
+
]);
|
|
231
|
+
return [
|
|
232
|
+
"## Static screen (advisory; never changes outcomes)",
|
|
233
|
+
"",
|
|
234
|
+
`The screening output republishes the one personal-trial decision: **${result.screening.personalTrial.recommendation}**.`,
|
|
235
|
+
"",
|
|
236
|
+
`Rules ${result.screening.rulesVersion}: ${num(result.screening.findings.length)} finding(s). Judge reconciliation: referenced ${num(counted.referenced)}, not-referenced ${num(counted["not-referenced"])}, unresolved ${num(counted.unresolved)}.`,
|
|
237
|
+
"",
|
|
238
|
+
rows.length === 0
|
|
239
|
+
? "No findings."
|
|
240
|
+
: table(["Rule", "Trial", "Role", "Source", "Excerpt", "Explanation"], rows),
|
|
241
|
+
"",
|
|
242
|
+
"A missing keyword does not prove a lost fact; a matching keyword does not prove truth. Findings are screened independently of the judge and never alter eligibility, outcomes, scores or guardrails.",
|
|
243
|
+
"",
|
|
244
|
+
].join("\n");
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Render the sensitivity section.
|
|
248
|
+
* @param result The analysis result.
|
|
249
|
+
* @returns Markdown.
|
|
250
|
+
*/
|
|
251
|
+
function sensitivitySection(result) {
|
|
252
|
+
if (result.sensitivity === null || result.sensitivity.length === 0) {
|
|
253
|
+
return "## Sensitivity\n\nNot defined.\n";
|
|
254
|
+
}
|
|
255
|
+
const arms = Object.keys(result.arms).sort();
|
|
256
|
+
const rows = result.sensitivity.map((cell) => [
|
|
257
|
+
num(cell.useful),
|
|
258
|
+
num(cell.transient),
|
|
259
|
+
...arms.map((arm) => num(cell.totals[arm])),
|
|
260
|
+
cell.ranking.join(" > "),
|
|
261
|
+
]);
|
|
262
|
+
return [
|
|
263
|
+
"## Sensitivity",
|
|
264
|
+
"",
|
|
265
|
+
table(["Useful", "Transient", ...arms, "Ranking"], rows),
|
|
266
|
+
"",
|
|
267
|
+
"The grid describes this sample; it is not a set of experiments and it does not repair missing assessments.",
|
|
268
|
+
"",
|
|
269
|
+
].join("\n");
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Render one ledger summary line, or an explicit absence.
|
|
273
|
+
* @param ledger The ledger summary, when one was declared.
|
|
274
|
+
* @returns Markdown lines.
|
|
275
|
+
*/
|
|
276
|
+
function ledgerLines(ledger) {
|
|
277
|
+
if (ledger === null) {
|
|
278
|
+
return ["Attempt ledger: not declared."];
|
|
279
|
+
}
|
|
280
|
+
if (ledger.state !== "available") {
|
|
281
|
+
return [
|
|
282
|
+
`Attempt ledger ${ledger.reference}: ${ledger.state}. Launch and judge-attempt coverage is unknown, not clean.`,
|
|
283
|
+
];
|
|
284
|
+
}
|
|
285
|
+
const events = Object.entries(ledger.events)
|
|
286
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
287
|
+
.map(([event, count]) => `${event} ${count}`)
|
|
288
|
+
.join(", ");
|
|
289
|
+
const lines = [
|
|
290
|
+
`Attempt ledger ${ledger.reference}: ${String(ledger.scheduled)} scheduled, ${String(ledger.launched)} launched, ${String(ledger.judged)} judge-attempted; events: ${events}.`,
|
|
291
|
+
];
|
|
292
|
+
if (ledger.missingLaunches.length > 0) {
|
|
293
|
+
lines.push(`Missing launches: ${ledger.missingLaunches.join(", ")}.`);
|
|
294
|
+
}
|
|
295
|
+
if (ledger.missingJudgeAttempts.length > 0) {
|
|
296
|
+
lines.push(`Missing judge attempts: ${ledger.missingJudgeAttempts.join(", ")}.`);
|
|
297
|
+
}
|
|
298
|
+
if (ledger.unknownStamps.length > 0) {
|
|
299
|
+
lines.push(`Unknown ledger stamps: ${ledger.unknownStamps.join(", ")}.`);
|
|
300
|
+
}
|
|
301
|
+
if (ledger.duplicateLaunches.length > 0) {
|
|
302
|
+
lines.push(`Duplicate launches: ${ledger.duplicateLaunches.join(", ")}.`);
|
|
303
|
+
}
|
|
304
|
+
for (const problem of ledger.problems) {
|
|
305
|
+
lines.push(`Ledger problem: ${problem}`);
|
|
306
|
+
}
|
|
307
|
+
return lines;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Render the capture, provenance, integrity and cost section.
|
|
311
|
+
* @param result The analysis result.
|
|
312
|
+
* @returns Markdown.
|
|
313
|
+
*/
|
|
314
|
+
function integritySection(result) {
|
|
315
|
+
const usage = result.usage;
|
|
316
|
+
return [
|
|
317
|
+
"## Capture, integrity and costs",
|
|
318
|
+
"",
|
|
319
|
+
`Judge models recorded: ${result.integrity.judgedBy.length === 0 ? "Not defined" : result.integrity.judgedBy.join(", ")}.`,
|
|
320
|
+
`Hashed input sources: ${num(result.integrity.sources.length)}. The full consumed-input list and the rendering identity are in analysis-receipt.json.`,
|
|
321
|
+
"",
|
|
322
|
+
table(["Measure", "Captured", "Coverage", "Unknown"], [
|
|
323
|
+
[
|
|
324
|
+
"Input tokens",
|
|
325
|
+
num(usage.input.total),
|
|
326
|
+
`${num(usage.input.measuredSteps)} observed step(s) measured`,
|
|
327
|
+
`${num(usage.input.unknownSteps)} observed step(s) without this field`,
|
|
328
|
+
],
|
|
329
|
+
[
|
|
330
|
+
"Output tokens",
|
|
331
|
+
num(usage.output.total),
|
|
332
|
+
`${num(usage.output.measuredSteps)} observed step(s) measured`,
|
|
333
|
+
`${num(usage.output.unknownSteps)} observed step(s) without this field`,
|
|
334
|
+
],
|
|
335
|
+
[
|
|
336
|
+
"Reasoning tokens",
|
|
337
|
+
num(usage.reasoning.total),
|
|
338
|
+
`${num(usage.reasoning.measuredSteps)} observed step(s) measured`,
|
|
339
|
+
`${num(usage.reasoning.unknownSteps)} observed step(s) without this field`,
|
|
340
|
+
],
|
|
341
|
+
[
|
|
342
|
+
"Cache-read tokens",
|
|
343
|
+
num(usage.cacheRead.total),
|
|
344
|
+
`${num(usage.cacheRead.measuredSteps)} observed step(s) measured`,
|
|
345
|
+
`${num(usage.cacheRead.unknownSteps)} observed step(s) without this field`,
|
|
346
|
+
],
|
|
347
|
+
[
|
|
348
|
+
"Steps observed",
|
|
349
|
+
num(usage.steps),
|
|
350
|
+
`${num(usage.measuredTrials)} readable trail(s)`,
|
|
351
|
+
`${num(usage.unknownTrials)} unreadable trail(s)`,
|
|
352
|
+
],
|
|
353
|
+
[
|
|
354
|
+
"Monetary cost",
|
|
355
|
+
"—",
|
|
356
|
+
"not recorded",
|
|
357
|
+
`${num(usage.measuredTrials + usage.unknownTrials)} trial(s)`,
|
|
358
|
+
],
|
|
359
|
+
]),
|
|
360
|
+
"",
|
|
361
|
+
"Token fields keep independent coverage: a step reporting only some fields leaves the rest unknown, explicit zeros are measured, and a step without token fields still counts as an observed step.",
|
|
362
|
+
"",
|
|
363
|
+
`Cost telemetry: ${result.cost.available ? "recorded" : "unavailable"}. ${result.cost.note}`,
|
|
364
|
+
"",
|
|
365
|
+
...ledgerLines(result.ledger),
|
|
366
|
+
"",
|
|
367
|
+
].join("\n");
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Render the limitations section.
|
|
371
|
+
* @param result The analysis result.
|
|
372
|
+
* @returns Markdown.
|
|
373
|
+
*/
|
|
374
|
+
function limitationSection(result) {
|
|
375
|
+
return [
|
|
376
|
+
"## Limitations",
|
|
377
|
+
"",
|
|
378
|
+
...result.limitations.map((limitation) => `- ${limitation}`),
|
|
379
|
+
"",
|
|
380
|
+
"Authored interpretation lives in INTERPRETATION.md and is never overwritten by a rerender.",
|
|
381
|
+
"",
|
|
382
|
+
].join("\n");
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Render the Markdown report from one structured result.
|
|
386
|
+
* @param result The analysis result.
|
|
387
|
+
* @returns The complete report.
|
|
388
|
+
*/
|
|
389
|
+
/**
|
|
390
|
+
* Render the assessment-selection section.
|
|
391
|
+
* @param result The analysis result.
|
|
392
|
+
* @returns Markdown.
|
|
393
|
+
*/
|
|
394
|
+
function selectionSection(result) {
|
|
395
|
+
const selection = result.assessmentSelection;
|
|
396
|
+
const recovered = selection.slots.filter((slot) => slot.recovered);
|
|
397
|
+
const rows = selection.slots.map((slot) => [
|
|
398
|
+
slot.stamp,
|
|
399
|
+
slot.selectedAttemptId ?? "unrecorded (manifest selector)",
|
|
400
|
+
slot.recovered
|
|
401
|
+
? "recovered"
|
|
402
|
+
: slot.selectedAttemptId === null
|
|
403
|
+
? "manifest"
|
|
404
|
+
: "initial",
|
|
405
|
+
]);
|
|
406
|
+
return [
|
|
407
|
+
"## Assessment selection",
|
|
408
|
+
"",
|
|
409
|
+
`${String(selection.recovered)} recovered, ${String(selection.initial)} initial, ${String(selection.unrecorded)} unrecorded.`,
|
|
410
|
+
"",
|
|
411
|
+
recovered.length === 0
|
|
412
|
+
? "No slot used a recovery attempt."
|
|
413
|
+
: `Recovered: ${recovered.map((slot) => `${slot.stamp} → ${slot.selectedAttemptId}`).join(", ")}.`,
|
|
414
|
+
"",
|
|
415
|
+
table(["Slot", "Selected attempt", "Kind"], rows),
|
|
416
|
+
].join("\n");
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Render the full analysis report.
|
|
420
|
+
* @param result The analysis result.
|
|
421
|
+
* @returns Markdown.
|
|
422
|
+
*/
|
|
423
|
+
export function renderReport(result) {
|
|
424
|
+
const screenHeadline = result.measures.status === "available"
|
|
425
|
+
? `**Personal-trial screen:** ${result.measures.personalTrial.recommendation} — ${escapeCell(result.measures.personalTrial.reasons.join("; ") || "no criterion reported")}.`
|
|
426
|
+
: `**Personal-trial screen:** not applicable — the measurement declaration is ${escapeCell(result.measures.status)}; only the utility threshold is available.`;
|
|
427
|
+
return [
|
|
428
|
+
`# Trial analysis: ${escapeCell(result.campaign.id)}`,
|
|
429
|
+
"",
|
|
430
|
+
screenHeadline,
|
|
431
|
+
"",
|
|
432
|
+
`**Utility threshold (component, not the recommendation):** ${result.decision.overall} — ${escapeCell(result.decision.reasons.join("; ") || result.decision.guardrails.detail)}.`,
|
|
433
|
+
"",
|
|
434
|
+
result.campaign.question === null
|
|
435
|
+
? ""
|
|
436
|
+
: `Question: ${escapeCell(result.campaign.question)}`,
|
|
437
|
+
"",
|
|
438
|
+
setupSection(result),
|
|
439
|
+
accountingSection(result),
|
|
440
|
+
measuresSection(result),
|
|
441
|
+
scenarioSection(result),
|
|
442
|
+
pairSection(result),
|
|
443
|
+
selectionSection(result),
|
|
444
|
+
guardrailSection(result),
|
|
445
|
+
screeningSection(result),
|
|
446
|
+
sensitivitySection(result),
|
|
447
|
+
integritySection(result),
|
|
448
|
+
limitationSection(result),
|
|
449
|
+
]
|
|
450
|
+
.filter((section) => section !== "")
|
|
451
|
+
.join("\n");
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Render one arm's opportunity-count row.
|
|
455
|
+
* @param arm The arm id.
|
|
456
|
+
* @param measures The measures report.
|
|
457
|
+
* @returns The table row.
|
|
458
|
+
*/
|
|
459
|
+
function countRow(arm, measures) {
|
|
460
|
+
const one = measures.arms[arm];
|
|
461
|
+
const recording = one?.recording ?? {
|
|
462
|
+
success: 0,
|
|
463
|
+
planned: 0,
|
|
464
|
+
unknown: 0,
|
|
465
|
+
notRun: 0,
|
|
466
|
+
};
|
|
467
|
+
const retrieval = one?.retrieval ?? {
|
|
468
|
+
applied: 0,
|
|
469
|
+
planned: 0,
|
|
470
|
+
unknown: 0,
|
|
471
|
+
notRun: 0,
|
|
472
|
+
};
|
|
473
|
+
return [
|
|
474
|
+
arm,
|
|
475
|
+
`${String(recording.success)}/${String(recording.planned)}`,
|
|
476
|
+
String(recording.unknown),
|
|
477
|
+
String(recording.notRun),
|
|
478
|
+
`${String(retrieval.applied)}/${String(retrieval.planned)}`,
|
|
479
|
+
String(retrieval.unknown),
|
|
480
|
+
String(retrieval.notRun),
|
|
481
|
+
];
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Render one eligible trial's measure row.
|
|
485
|
+
* @param trial The trial measures.
|
|
486
|
+
* @returns The table row.
|
|
487
|
+
*/
|
|
488
|
+
function measureRow(trial) {
|
|
489
|
+
const recording = trial.recording.eligible
|
|
490
|
+
? `${trial.recording.success ? "success" : "miss"} (${trial.recording.disposition})`
|
|
491
|
+
: "—";
|
|
492
|
+
const retrieval = trial.retrieval.eligible
|
|
493
|
+
? `${trial.retrieval.state}${trial.retrieval.read ? " (read)" : " (no read)"}`
|
|
494
|
+
: "—";
|
|
495
|
+
return [
|
|
496
|
+
trial.stamp,
|
|
497
|
+
trial.scenario,
|
|
498
|
+
trial.arm,
|
|
499
|
+
recording,
|
|
500
|
+
retrieval,
|
|
501
|
+
[...trial.recording.evidence, ...trial.retrieval.evidence].join(" ") ||
|
|
502
|
+
"—",
|
|
503
|
+
];
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Render the opportunity measures and the personal-trial screen.
|
|
507
|
+
* @param result The analysis result.
|
|
508
|
+
* @returns Markdown.
|
|
509
|
+
*/
|
|
510
|
+
function measuresSection(result) {
|
|
511
|
+
const measures = result.measures;
|
|
512
|
+
const header = [
|
|
513
|
+
"## Opportunity measures",
|
|
514
|
+
"",
|
|
515
|
+
`Declaration: ${escapeCell(measures.status)}${measures.declarationSha256 === null ? "" : ` (sha256 ${measures.declarationSha256})`}.`,
|
|
516
|
+
"",
|
|
517
|
+
];
|
|
518
|
+
const arms = Object.keys(measures.arms).sort();
|
|
519
|
+
const counts = table([
|
|
520
|
+
"Arm",
|
|
521
|
+
"Recording",
|
|
522
|
+
"Recording unknown",
|
|
523
|
+
"Recording not run",
|
|
524
|
+
"Retrieval",
|
|
525
|
+
"Retrieval unknown",
|
|
526
|
+
"Retrieval not run",
|
|
527
|
+
], arms.map((arm) => countRow(arm, measures)));
|
|
528
|
+
const eligible = measures.trials.filter((trial) => trial.recording.eligible || trial.retrieval.eligible);
|
|
529
|
+
const rows = eligible.map(measureRow);
|
|
530
|
+
const screen = measures.personalTrial;
|
|
531
|
+
return [
|
|
532
|
+
...header,
|
|
533
|
+
counts,
|
|
534
|
+
"",
|
|
535
|
+
"### Eligible trials",
|
|
536
|
+
"",
|
|
537
|
+
table(["Stamp", "Scenario", "Arm", "Recording", "Retrieval", "Evidence"], rows),
|
|
538
|
+
"",
|
|
539
|
+
"### Personal-trial screen",
|
|
540
|
+
"",
|
|
541
|
+
`Recommendation: **${screen.recommendation}**.`,
|
|
542
|
+
"",
|
|
543
|
+
...screen.reasons.map((reason) => `- ${reason}`),
|
|
544
|
+
"",
|
|
545
|
+
`Utility threshold (one component, not the recommendation): ${screen.utilityThreshold.status}; lead ${num(screen.utilityThreshold.lead)}; interval ${screen.utilityThreshold.interval === null
|
|
546
|
+
? "—"
|
|
547
|
+
: `${String(screen.utilityThreshold.interval.low)}..${String(screen.utilityThreshold.interval.high)}`}. ${screen.utilityThreshold.note}`,
|
|
548
|
+
"",
|
|
549
|
+
].join("\n");
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Render the versioned machine-readable analysis.
|
|
553
|
+
* @param result The analysis result.
|
|
554
|
+
* @returns The JSON document with a trailing newline.
|
|
555
|
+
*/
|
|
556
|
+
export function renderAnalysisJson(result) {
|
|
557
|
+
return `${JSON.stringify(result, null, 2)}\n`;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Render the analysis receipt.
|
|
561
|
+
* @param receipt The receipt fields, including the injected timestamp.
|
|
562
|
+
* @returns The JSON document with a trailing newline.
|
|
563
|
+
*/
|
|
564
|
+
export function renderReceipt(receipt) {
|
|
565
|
+
return `${JSON.stringify(receipt, null, 2)}\n`;
|
|
566
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** The CLI outcome. */
|
|
3
|
+
interface CliRun {
|
|
4
|
+
/** The process exit code. */
|
|
5
|
+
status: number;
|
|
6
|
+
/** Messages for stderr/stdout. */
|
|
7
|
+
messages: string[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Run the analyser.
|
|
11
|
+
* @param argv The arguments after the script path.
|
|
12
|
+
* @returns The exit status and messages.
|
|
13
|
+
*/
|
|
14
|
+
export declare function runAnalysis(argv: readonly string[]): CliRun;
|
|
15
|
+
export {};
|