@fusionkit/ensemble 0.1.4 → 0.1.6

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/dist/dashboard.js DELETED
@@ -1,788 +0,0 @@
1
- import { mkdirSync, writeFileSync } from "node:fs";
2
- import { join, resolve } from "node:path";
3
- import { assertHarnessRunResultV1, MODEL_FUSION_SCHEMA_BUNDLE_HASH } from "@fusionkit/protocol";
4
- import { gitText } from "@fusionkit/workspace";
5
- import { claudeCodeHarness, claudeCodeHarnessCredentialSkipReason } from "./claude-code.js";
6
- import { createCommandHarness } from "./command.js";
7
- import { codexHarness, codexHarnessCredentialSkipReason } from "./codex.js";
8
- import { createMockHarness } from "./mock.js";
9
- import { runEnsemble } from "./run.js";
10
- const PRODUCER_GIT_SHA = "0".repeat(40);
11
- const PRODUCER = "handoffkit-ensemble";
12
- const PRODUCER_VERSION = "0.1.0";
13
- const ZERO_GIT_SHA = "0".repeat(40);
14
- const DEFAULT_TIMEOUT_MS = 30_000;
15
- const DEFAULT_PROMPT = "Run the CI-safe harness smoke task and report concise evidence.";
16
- const DEFAULT_COMMAND_SUCCESS = "printf command-ok";
17
- const DEFAULT_COMMAND_FAILURE = "exit 7";
18
- const DEFAULT_OUTPUT_DIR = ".warrant/ensemble-dashboard";
19
- const CLAUDE_LIVE_SMOKE_ENV = "WARRANT_CLAUDE_SMOKE";
20
- const CODEX_LIVE_SMOKE_ENV = "WARRANT_CODEX_SMOKE";
21
- const ALL_LIVE_SMOKE_ENV = "WARRANT_ENSEMBLE_LIVE_SMOKE";
22
- const LIVE_SMOKE_TARGETS = ["claude-code", "codex"];
23
- const CLAUDE_LIVE_SMOKE_PROMPT = "Read README.md if present, then reply exactly CLAUDE_LIVE_SMOKE_OK. Do not modify files.";
24
- const CODEX_LIVE_SMOKE_PROMPT = "Read README.md if present, then reply exactly CODEX_LIVE_SMOKE_OK. Do not modify files.";
25
- function metadata(schema, createdAt) {
26
- return {
27
- schema,
28
- schema_version: "v1",
29
- schema_bundle_hash: MODEL_FUSION_SCHEMA_BUNDLE_HASH,
30
- producer: PRODUCER,
31
- producer_version: PRODUCER_VERSION,
32
- producer_git_sha: PRODUCER_GIT_SHA,
33
- created_at: createdAt
34
- };
35
- }
36
- function assertNever(value) {
37
- throw new Error(`unhandled harness capability target: ${String(value)}`);
38
- }
39
- function safeFileName(value) {
40
- return value.replace(/[^A-Za-z0-9_.:-]/g, "_");
41
- }
42
- function escapeMarkdownCell(value) {
43
- return value.replace(/\|/g, "\\|").replace(/\n/g, " ");
44
- }
45
- function isEnvEnabled(env, name) {
46
- return env[name] === "1" || env[name]?.toLowerCase() === "true";
47
- }
48
- function liveSmokeEnvName(target) {
49
- switch (target) {
50
- case "claude-code":
51
- return CLAUDE_LIVE_SMOKE_ENV;
52
- case "codex":
53
- return CODEX_LIVE_SMOKE_ENV;
54
- default: {
55
- const exhausted = target;
56
- throw new Error(`unsupported live smoke target: ${String(exhausted)}`);
57
- }
58
- }
59
- }
60
- function liveSmokeEnvEnabled(env, target) {
61
- return isEnvEnabled(env, ALL_LIVE_SMOKE_ENV) || isEnvEnabled(env, liveSmokeEnvName(target));
62
- }
63
- function requestedLiveSmokeTargets(options) {
64
- const selected = options.liveSmoke?.length ? options.liveSmoke : LIVE_SMOKE_TARGETS;
65
- return selected.filter((target) => liveSmokeEnvEnabled(options.env, target));
66
- }
67
- function harnessKindFor(target) {
68
- switch (target) {
69
- case "cursor":
70
- return "cursor";
71
- case "claude-code":
72
- return "claude_code";
73
- case "codex":
74
- return "codex";
75
- case "command":
76
- case "mock":
77
- return "generic";
78
- default:
79
- return assertNever(target);
80
- }
81
- }
82
- function displayNameFor(target) {
83
- switch (target) {
84
- case "cursor":
85
- return "Cursor";
86
- case "claude-code":
87
- return "Claude Code";
88
- case "codex":
89
- return "Codex";
90
- case "command":
91
- return "Command";
92
- case "mock":
93
- return "Mock";
94
- default:
95
- return assertNever(target);
96
- }
97
- }
98
- function cursorCapabilities() {
99
- return {
100
- workspace_read: "degraded",
101
- workspace_write: "degraded",
102
- apply_patch: "degraded",
103
- tool_records: "degraded",
104
- verification: "degraded",
105
- proprietary_harness: "unsupported",
106
- adapter_available: "unsupported"
107
- };
108
- }
109
- function dashboardCapabilitiesFor(target) {
110
- switch (target) {
111
- case "cursor":
112
- return {
113
- model_override: "degraded",
114
- transcript_capture: "degraded",
115
- diff_capture: "degraded",
116
- tool_loop_capture: "degraded",
117
- patch_apply_visibility: "degraded",
118
- route_model_observation: "degraded",
119
- verification_hint: "degraded",
120
- replay_support: "unsupported"
121
- };
122
- case "claude-code":
123
- return {
124
- model_override: "supported",
125
- transcript_capture: "supported",
126
- diff_capture: "supported",
127
- tool_loop_capture: "supported",
128
- patch_apply_visibility: "supported",
129
- route_model_observation: "degraded",
130
- verification_hint: "supported",
131
- replay_support: "degraded"
132
- };
133
- case "codex":
134
- return {
135
- model_override: "supported",
136
- transcript_capture: "supported",
137
- diff_capture: "supported",
138
- tool_loop_capture: "degraded",
139
- patch_apply_visibility: "supported",
140
- route_model_observation: "supported",
141
- verification_hint: "supported",
142
- replay_support: "degraded"
143
- };
144
- case "command":
145
- return {
146
- model_override: "supported",
147
- transcript_capture: "supported",
148
- diff_capture: "unsupported",
149
- tool_loop_capture: "supported",
150
- patch_apply_visibility: "unsupported",
151
- route_model_observation: "unsupported",
152
- verification_hint: "supported",
153
- replay_support: "supported"
154
- };
155
- case "mock":
156
- return {
157
- model_override: "supported",
158
- transcript_capture: "supported",
159
- diff_capture: "supported",
160
- tool_loop_capture: "supported",
161
- patch_apply_visibility: "supported",
162
- route_model_observation: "degraded",
163
- verification_hint: "supported",
164
- replay_support: "supported"
165
- };
166
- default:
167
- return assertNever(target);
168
- }
169
- }
170
- function matrixCapabilities(target, capabilities) {
171
- return {
172
- ...capabilities,
173
- ...dashboardCapabilitiesFor(target)
174
- };
175
- }
176
- function descriptorForCapabilities(harness) {
177
- return {
178
- id: "capability_matrix",
179
- harness,
180
- models: [{ id: "capability", model: "capability-model" }],
181
- runtime: { id: "local" },
182
- judge: { id: "none" },
183
- policy: {
184
- id: "capability-policy",
185
- allowedTools: ["read_file"],
186
- sideEffects: "read_only",
187
- timeoutMs: DEFAULT_TIMEOUT_MS
188
- },
189
- prompt: DEFAULT_PROMPT,
190
- sourceRepo: "handoffkit",
191
- baseGitSha: ZERO_GIT_SHA
192
- };
193
- }
194
- function adapterCapabilities(harness) {
195
- return harness.capabilities(descriptorForCapabilities(harness));
196
- }
197
- function matrixRow(input) {
198
- return {
199
- harnessId: input.harnessId,
200
- harnessKind: harnessKindFor(input.harnessId),
201
- displayName: displayNameFor(input.harnessId),
202
- availability: input.availability,
203
- capabilities: input.capabilities,
204
- notes: input.notes
205
- };
206
- }
207
- export function createHarnessCapabilityMatrix(options = {}) {
208
- const env = options.env ?? {};
209
- const rows = [
210
- matrixRow({
211
- harnessId: "cursor",
212
- availability: "missing",
213
- capabilities: matrixCapabilities("cursor", cursorCapabilities()),
214
- notes: ["No CI-safe package adapter; represented as an unsupported result record."]
215
- }),
216
- matrixRow({
217
- harnessId: "claude-code",
218
- availability: "credential_gated",
219
- capabilities: matrixCapabilities("claude-code", adapterCapabilities(claudeCodeHarness({ env }))),
220
- notes: ["Credential-gated; dashboard smoke uses an empty env skip path."]
221
- }),
222
- matrixRow({
223
- harnessId: "codex",
224
- availability: "credential_gated",
225
- capabilities: matrixCapabilities("codex", adapterCapabilities(codexHarness({ env, provider: { kind: "ambient" } }))),
226
- notes: ["Credential-gated; dashboard smoke uses an empty env skip path."]
227
- }),
228
- matrixRow({
229
- harnessId: "command",
230
- availability: "available",
231
- capabilities: matrixCapabilities("command", adapterCapabilities(createCommandHarness({
232
- command: options.commandSuccess ?? DEFAULT_COMMAND_SUCCESS,
233
- cwd: options.repo
234
- }))),
235
- notes: ["Runs local shell commands through the command harness."]
236
- }),
237
- matrixRow({
238
- harnessId: "mock",
239
- availability: "available",
240
- capabilities: matrixCapabilities("mock", adapterCapabilities(createMockHarness())),
241
- notes: ["Pure synthetic fixture harness for CI."]
242
- })
243
- ];
244
- const capabilities = [...new Set(rows.flatMap((row) => Object.keys(row.capabilities)))].sort();
245
- return { capabilities, rows };
246
- }
247
- function currentGitSha(repo) {
248
- try {
249
- const sha = gitText(repo, ["rev-parse", "HEAD"]).trim();
250
- return sha.length > 0 ? sha : ZERO_GIT_SHA;
251
- }
252
- catch {
253
- return ZERO_GIT_SHA;
254
- }
255
- }
256
- function smokeDescriptor(input) {
257
- return {
258
- id: input.id,
259
- harness: input.harness,
260
- models: [input.model],
261
- runtime: { id: "local" },
262
- judge: { id: "none" },
263
- policy: {
264
- id: `${input.id}_policy`,
265
- allowedTools: input.allowedTools,
266
- sideEffects: input.sideEffects,
267
- timeoutMs: input.timeoutMs
268
- },
269
- prompt: input.prompt,
270
- sourceRepo: input.repo,
271
- baseGitSha: input.baseGitSha,
272
- outputRoot: join(input.outputRoot, "runs", input.id)
273
- };
274
- }
275
- function unsupportedCursorResult(input) {
276
- const result = {
277
- ...metadata("harness-run-result.v1", input.createdAt),
278
- result_id: `ensemble_result_${input.taskId}`,
279
- request_id: `ensemble_req_${input.taskId}`,
280
- harness_kind: "cursor",
281
- status: "unsupported",
282
- candidate_ids: [],
283
- output_summary: "Cursor harness unavailable in CI-safe package context.",
284
- capabilities: cursorCapabilities(),
285
- started_at: input.createdAt,
286
- finished_at: input.createdAt,
287
- errors: [
288
- {
289
- kind: "capability_missing",
290
- message: "Cursor proprietary harness is not available from @fusionkit/ensemble.",
291
- retryable: false
292
- }
293
- ],
294
- metadata: {
295
- dashboard_outcome: "missing",
296
- harness_id: "cursor"
297
- }
298
- };
299
- assertHarnessRunResultV1(result);
300
- return result;
301
- }
302
- function liveSmokePreflightFailureResult(input) {
303
- const result = {
304
- ...metadata("harness-run-result.v1", input.createdAt),
305
- result_id: `ensemble_result_${input.taskId}`,
306
- request_id: `ensemble_req_${input.taskId}`,
307
- harness_kind: harnessKindFor(input.harnessId),
308
- status: "failed",
309
- candidate_ids: [],
310
- output_summary: `Explicit live smoke failed before launch: ${input.reason}`,
311
- capabilities: matrixCapabilities(input.harnessId, adapterCapabilities(input.harness)),
312
- started_at: input.createdAt,
313
- finished_at: input.createdAt,
314
- errors: [
315
- {
316
- kind: "capability_missing",
317
- message: input.reason,
318
- retryable: false
319
- }
320
- ],
321
- metadata: {
322
- dashboard_outcome: "failure",
323
- harness_id: input.harnessId,
324
- live_smoke: true,
325
- preflight: "credentials"
326
- }
327
- };
328
- assertHarnessRunResultV1(result);
329
- return result;
330
- }
331
- function failSkippedLiveSmokeResult(result, taskId) {
332
- if (result.status !== "skipped")
333
- return result;
334
- const metadata = {
335
- ...(result.metadata ?? {}),
336
- dashboard_outcome: "failure",
337
- explicit_live_smoke: true,
338
- original_status: "skipped"
339
- };
340
- const promoted = {
341
- ...result,
342
- status: "failed",
343
- output_summary: `Explicit live smoke ${taskId} failed because the adapter returned skipped. ` +
344
- (result.output_summary ?? ""),
345
- errors: [
346
- ...(result.errors ?? []),
347
- {
348
- kind: "capability_missing",
349
- message: "Explicit live smoke was requested but the adapter returned skipped.",
350
- retryable: false
351
- }
352
- ],
353
- metadata
354
- };
355
- assertHarnessRunResultV1(promoted);
356
- return promoted;
357
- }
358
- function writeRunResult(outputRoot, taskId, result) {
359
- const dir = join(outputRoot, "harness-run-results");
360
- mkdirSync(dir, { recursive: true });
361
- const path = join(dir, `${safeFileName(taskId)}.json`);
362
- assertHarnessRunResultV1(result);
363
- writeFileSync(path, JSON.stringify(result, null, 2) + "\n");
364
- return path;
365
- }
366
- async function runSmokeTask(input) {
367
- if (input.run.preflightFailureReason !== undefined) {
368
- const result = liveSmokePreflightFailureResult({
369
- createdAt: input.createdAt,
370
- taskId: input.run.taskId,
371
- harnessId: input.run.harnessId,
372
- harness: input.run.harness,
373
- reason: input.run.preflightFailureReason
374
- });
375
- const resultPath = writeRunResult(input.outputRoot, input.run.taskId, result);
376
- return {
377
- taskId: input.run.taskId,
378
- harnessId: input.run.harnessId,
379
- purpose: input.run.purpose,
380
- outcome: input.run.outcome,
381
- result,
382
- resultPath
383
- };
384
- }
385
- if (input.run.harnessId === "cursor") {
386
- const result = unsupportedCursorResult({
387
- createdAt: input.createdAt,
388
- taskId: input.run.taskId
389
- });
390
- const resultPath = writeRunResult(input.outputRoot, input.run.taskId, result);
391
- return {
392
- taskId: input.run.taskId,
393
- harnessId: input.run.harnessId,
394
- purpose: input.run.purpose,
395
- outcome: input.run.outcome,
396
- result,
397
- resultPath
398
- };
399
- }
400
- const descriptor = smokeDescriptor({
401
- id: input.run.taskId,
402
- harness: input.run.harness,
403
- model: input.run.model,
404
- repo: input.repo,
405
- baseGitSha: input.baseGitSha,
406
- outputRoot: input.outputRoot,
407
- sideEffects: input.run.sideEffects,
408
- allowedTools: input.run.allowedTools,
409
- timeoutMs: input.timeoutMs,
410
- prompt: input.run.prompt ?? DEFAULT_PROMPT
411
- });
412
- const result = await runEnsemble(descriptor);
413
- const harnessRunResult = input.run.purpose === "live"
414
- ? failSkippedLiveSmokeResult(result.harnessRunResult, input.run.taskId)
415
- : result.harnessRunResult;
416
- const resultPath = writeRunResult(input.outputRoot, input.run.taskId, harnessRunResult);
417
- return {
418
- taskId: input.run.taskId,
419
- harnessId: input.run.harnessId,
420
- purpose: input.run.purpose,
421
- outcome: input.run.outcome,
422
- result: harnessRunResult,
423
- resultPath
424
- };
425
- }
426
- function smokeRuns(options) {
427
- return [
428
- {
429
- taskId: "mock-success",
430
- harnessId: "mock",
431
- purpose: "contract",
432
- outcome: "success",
433
- harness: createMockHarness(),
434
- model: { id: "mock", model: "synthetic-mock" },
435
- sideEffects: "read_only",
436
- allowedTools: ["read_file"]
437
- },
438
- {
439
- taskId: "command-success",
440
- harnessId: "command",
441
- purpose: "contract",
442
- outcome: "success",
443
- harness: createCommandHarness({ command: options.commandSuccess }),
444
- model: { id: "command", model: "local-shell" },
445
- sideEffects: "tool_execution",
446
- allowedTools: ["shell_command"]
447
- },
448
- {
449
- taskId: "command-failure",
450
- harnessId: "command",
451
- purpose: "contract",
452
- outcome: "failure",
453
- harness: createCommandHarness({ command: options.commandFailure }),
454
- model: { id: "command", model: "local-shell" },
455
- sideEffects: "tool_execution",
456
- allowedTools: ["shell_command"]
457
- },
458
- {
459
- taskId: "claude-code-skipped",
460
- harnessId: "claude-code",
461
- purpose: "credential-skip",
462
- outcome: "skipped",
463
- harness: claudeCodeHarness({ env: {} }),
464
- model: { id: "claude", model: "claude-sonnet-4-6" },
465
- sideEffects: "writes_workspace",
466
- allowedTools: ["read_file", "write_file", "apply_patch"]
467
- },
468
- {
469
- taskId: "codex-skipped",
470
- harnessId: "codex",
471
- purpose: "credential-skip",
472
- outcome: "skipped",
473
- harness: codexHarness({ env: {}, provider: { kind: "ambient" } }),
474
- model: { id: "codex", model: "gpt-5.5-codex" },
475
- sideEffects: "writes_workspace",
476
- allowedTools: ["read_file", "apply_patch"]
477
- },
478
- {
479
- taskId: "cursor-missing",
480
- harnessId: "cursor",
481
- purpose: "missing",
482
- outcome: "missing",
483
- harness: createMockHarness({ id: "cursor-missing-placeholder" }),
484
- model: { id: "cursor", model: "cursor-proprietary" },
485
- sideEffects: "writes_workspace",
486
- allowedTools: ["read_file", "write_file", "apply_patch"]
487
- }
488
- ];
489
- }
490
- function liveSmokeRuns(options) {
491
- const runs = [];
492
- if (options.targets.includes("claude-code")) {
493
- const harness = options.harnesses?.["claude-code"] ??
494
- claudeCodeHarness({ env: options.env, skipWhenUnavailable: false });
495
- runs.push({
496
- taskId: "claude-code-live",
497
- harnessId: "claude-code",
498
- purpose: "live",
499
- outcome: "success",
500
- harness,
501
- model: {
502
- id: "claude",
503
- model: options.env.WARRANT_CLAUDE_SMOKE_MODEL ?? "claude-sonnet-4-6"
504
- },
505
- sideEffects: "read_only",
506
- allowedTools: ["read_file"],
507
- prompt: CLAUDE_LIVE_SMOKE_PROMPT,
508
- preflightFailureReason: claudeCodeHarnessCredentialSkipReason(options.env)
509
- });
510
- }
511
- if (options.targets.includes("codex")) {
512
- const harness = options.harnesses?.codex ?? codexHarness({ env: options.env });
513
- runs.push({
514
- taskId: "codex-live",
515
- harnessId: "codex",
516
- purpose: "live",
517
- outcome: "success",
518
- harness,
519
- model: {
520
- id: "codex",
521
- model: options.env.WARRANT_CODEX_SMOKE_MODEL ?? "gpt-5.5-codex"
522
- },
523
- sideEffects: "read_only",
524
- allowedTools: ["read_file"],
525
- prompt: CODEX_LIVE_SMOKE_PROMPT,
526
- preflightFailureReason: codexHarnessCredentialSkipReason(options.env)
527
- });
528
- }
529
- return runs;
530
- }
531
- function capabilityCell(capabilities, capability) {
532
- return capabilities[capability] ?? "unknown";
533
- }
534
- function renderCapabilityMatrix(matrix) {
535
- const header = [
536
- "Harness",
537
- "Kind",
538
- "Availability",
539
- ...matrix.capabilities,
540
- "Notes"
541
- ];
542
- const lines = [
543
- "## Capability Matrix",
544
- "",
545
- `| ${header.map(escapeMarkdownCell).join(" | ")} |`,
546
- `| ${header.map(() => "---").join(" | ")} |`
547
- ];
548
- for (const row of matrix.rows) {
549
- const cells = [
550
- row.displayName,
551
- row.harnessKind,
552
- row.availability,
553
- ...matrix.capabilities.map((capability) => capabilityCell(row.capabilities, capability)),
554
- row.notes.join(" ")
555
- ];
556
- lines.push(`| ${cells.map(escapeMarkdownCell).join(" | ")} |`);
557
- }
558
- return lines;
559
- }
560
- function relativePath(path, from) {
561
- return path.startsWith(from) ? path.slice(from.length + 1) : path;
562
- }
563
- function safeArtifactRefs(artifacts) {
564
- if (artifacts === undefined || artifacts.length === 0)
565
- return [];
566
- const refs = [];
567
- let rawWithheld = 0;
568
- for (const artifact of artifacts) {
569
- if (artifact.redaction_status === "raw") {
570
- rawWithheld++;
571
- continue;
572
- }
573
- refs.push(`${artifact.kind}:${artifact.artifact_id}:${artifact.hash}`);
574
- if (refs.length >= 5)
575
- break;
576
- }
577
- if (rawWithheld > 0)
578
- refs.push(`${rawWithheld} raw artifact ref(s) withheld`);
579
- return refs;
580
- }
581
- function credentialStateFor(harnessId, env) {
582
- switch (harnessId) {
583
- case "claude-code":
584
- return claudeCodeHarnessCredentialSkipReason(env) === undefined
585
- ? "credentials available"
586
- : "credentials missing/skipped";
587
- case "codex":
588
- return codexHarnessCredentialSkipReason(env) === undefined
589
- ? "credentials available"
590
- : "credentials missing/skipped";
591
- case "command":
592
- case "mock":
593
- return "not required";
594
- case "cursor":
595
- return "not applicable";
596
- default:
597
- return assertNever(harnessId);
598
- }
599
- }
600
- function contractReadinessFor(harnessId) {
601
- switch (harnessId) {
602
- case "mock":
603
- case "command":
604
- return "contract/mock ready";
605
- case "claude-code":
606
- case "codex":
607
- return "contract/mock ready";
608
- case "cursor":
609
- return "adapter missing";
610
- default:
611
- return assertNever(harnessId);
612
- }
613
- }
614
- function liveSmokeState(record) {
615
- if (record === undefined)
616
- return "live smoke not requested";
617
- switch (record.result.status) {
618
- case "succeeded":
619
- return "live smoke passed";
620
- case "failed":
621
- case "canceled":
622
- case "requires_action":
623
- case "unsupported":
624
- case "pending":
625
- case "running":
626
- return "live smoke failed";
627
- case "skipped":
628
- return "live smoke skipped";
629
- default: {
630
- const exhausted = record.result.status;
631
- throw new Error(`unsupported live smoke status: ${String(exhausted)}`);
632
- }
633
- }
634
- }
635
- function createAdapterReadiness(input) {
636
- return input.matrix.rows.map((row) => {
637
- const liveRecord = input.records.find((record) => record.harnessId === row.harnessId && record.purpose === "live");
638
- const credentialRecord = input.records.find((record) => record.harnessId === row.harnessId && record.purpose === "credential-skip");
639
- const evidence = [
640
- ...(credentialRecord !== undefined && credentialStateFor(row.harnessId, input.env).includes("missing")
641
- ? [`credential skip: ${relativePath(credentialRecord.resultPath, input.outputRoot)}`]
642
- : []),
643
- ...(liveRecord !== undefined
644
- ? [
645
- `live result: ${relativePath(liveRecord.resultPath, input.outputRoot)}`,
646
- `status=${liveRecord.result.status}`
647
- ]
648
- : [])
649
- ];
650
- return {
651
- harnessId: row.harnessId,
652
- displayName: row.displayName,
653
- contractReadiness: contractReadinessFor(row.harnessId),
654
- credentialState: credentialStateFor(row.harnessId, input.env),
655
- liveSmoke: liveSmokeState(liveRecord),
656
- evidence,
657
- artifactRefs: liveRecord !== undefined ? safeArtifactRefs(liveRecord.result.artifacts) : []
658
- };
659
- });
660
- }
661
- function renderAdapterReadiness(readiness) {
662
- const lines = [
663
- "## Adapter Readiness",
664
- "",
665
- "| Adapter | Contract/Mock Readiness | Credentials | Live Smoke | Last Evidence | Safe Artifact Refs |",
666
- "| --- | --- | --- | --- | --- | --- |"
667
- ];
668
- for (const row of readiness) {
669
- const cells = [
670
- row.displayName,
671
- row.contractReadiness,
672
- row.credentialState,
673
- row.liveSmoke,
674
- row.evidence.length > 0 ? row.evidence.join("; ") : "-",
675
- row.artifactRefs.length > 0 ? row.artifactRefs.join("; ") : "-"
676
- ];
677
- lines.push(`| ${cells.map(escapeMarkdownCell).join(" | ")} |`);
678
- }
679
- return lines;
680
- }
681
- function renderSmokeRecords(records, outputRoot) {
682
- const lines = [
683
- "## Smoke Records",
684
- "",
685
- "| Task | Harness | Purpose | Expected | Result Status | Harness Kind | Result Record | Summary |",
686
- "| --- | --- | --- | --- | --- | --- | --- | --- |"
687
- ];
688
- for (const record of records) {
689
- const cells = [
690
- record.taskId,
691
- displayNameFor(record.harnessId),
692
- record.purpose,
693
- record.outcome,
694
- record.result.status,
695
- record.result.harness_kind,
696
- relativePath(record.resultPath, outputRoot),
697
- record.result.output_summary ?? ""
698
- ];
699
- lines.push(`| ${cells.map(escapeMarkdownCell).join(" | ")} |`);
700
- }
701
- return lines;
702
- }
703
- function renderDashboard(input) {
704
- const counts = new Map();
705
- for (const record of input.records) {
706
- counts.set(record.result.status, (counts.get(record.result.status) ?? 0) + 1);
707
- }
708
- const countText = [...counts.entries()]
709
- .sort(([left], [right]) => left.localeCompare(right))
710
- .map(([status, count]) => `${status}:${count}`)
711
- .join(", ");
712
- return [
713
- "# HandoffKit Harness Smoke Dashboard",
714
- "",
715
- `Generated: ${input.createdAt}`,
716
- `Output root: ${input.outputRoot}`,
717
- `Run-result status counts: ${countText}`,
718
- "",
719
- ...renderCapabilityMatrix(input.matrix),
720
- "",
721
- ...renderAdapterReadiness(input.readiness),
722
- "",
723
- ...renderSmokeRecords(input.records, input.outputRoot),
724
- ""
725
- ].join("\n");
726
- }
727
- export async function runHarnessSmokeDashboard(options = {}) {
728
- const repo = resolve(options.repo ?? process.cwd());
729
- const outputRoot = resolve(options.outputRoot ?? join(repo, DEFAULT_OUTPUT_DIR));
730
- const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
731
- const createdAt = options.createdAt ?? new Date().toISOString();
732
- const commandSuccess = options.commandSuccess ?? DEFAULT_COMMAND_SUCCESS;
733
- const commandFailure = options.commandFailure ?? DEFAULT_COMMAND_FAILURE;
734
- const env = options.env ?? process.env;
735
- mkdirSync(outputRoot, { recursive: true });
736
- const matrix = createHarnessCapabilityMatrix({
737
- ...options,
738
- repo,
739
- commandSuccess,
740
- commandFailure,
741
- env
742
- });
743
- const baseGitSha = currentGitSha(repo);
744
- const records = [];
745
- const runs = [
746
- ...smokeRuns({ commandSuccess, commandFailure }),
747
- ...liveSmokeRuns({
748
- env,
749
- targets: requestedLiveSmokeTargets({ env, liveSmoke: options.liveSmoke }),
750
- harnesses: options.liveSmokeHarnesses
751
- })
752
- ];
753
- for (const run of runs) {
754
- records.push(await runSmokeTask({
755
- run,
756
- repo,
757
- baseGitSha,
758
- outputRoot,
759
- timeoutMs,
760
- createdAt
761
- }));
762
- }
763
- const readiness = createAdapterReadiness({
764
- matrix,
765
- records,
766
- env,
767
- outputRoot
768
- });
769
- const dashboardPath = join(outputRoot, "dashboard.md");
770
- writeFileSync(dashboardPath, renderDashboard({
771
- matrix,
772
- readiness,
773
- records,
774
- createdAt,
775
- outputRoot
776
- }));
777
- return {
778
- outputRoot,
779
- dashboardPath,
780
- matrix,
781
- records,
782
- readiness
783
- };
784
- }
785
- export const harnessDashboard = {
786
- capabilities: createHarnessCapabilityMatrix,
787
- run: runHarnessSmokeDashboard
788
- };