@walwal-harness/cli 7.1.8 → 7.1.10
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/HR-Resource/ceo/SKILL.md +4 -4
- package/HR-Resource/cqo/SKILL.md +3 -3
- package/HR-Resource/cto/SKILL.md +4 -4
- package/README.md +58 -5
- package/apps/harness-dashboard/app/api/env/route.ts +62 -0
- package/apps/harness-dashboard/app/api/log/route.ts +19 -0
- package/apps/harness-dashboard/app/api/missions/route.ts +15 -0
- package/apps/harness-dashboard/app/api/snapshot/route.ts +15 -0
- package/apps/harness-dashboard/app/api/stream/route.ts +71 -0
- package/apps/harness-dashboard/app/globals.css +17 -0
- package/apps/harness-dashboard/app/layout.tsx +29 -0
- package/apps/harness-dashboard/app/page.tsx +35 -0
- package/apps/harness-dashboard/components/Drawer.tsx +97 -0
- package/apps/harness-dashboard/components/Header.tsx +40 -0
- package/apps/harness-dashboard/components/MissionTimeline.tsx +214 -0
- package/apps/harness-dashboard/components/OrgTree.tsx +330 -0
- package/apps/harness-dashboard/components/Scene.tsx +563 -0
- package/apps/harness-dashboard/components/Wordmark.tsx +14 -0
- package/apps/harness-dashboard/components/drawer/AgentLogTab.tsx +56 -0
- package/apps/harness-dashboard/components/drawer/GotchasTab.tsx +89 -0
- package/apps/harness-dashboard/components/drawer/HypothesisTab.tsx +42 -0
- package/apps/harness-dashboard/components/drawer/IncidentsTab.tsx +50 -0
- package/apps/harness-dashboard/components/drawer/MissionDocTab.tsx +92 -0
- package/apps/harness-dashboard/components/drawer/MissionFlowTab.tsx +520 -0
- package/apps/harness-dashboard/components/drawer/OwnerHistoryTab.tsx +160 -0
- package/apps/harness-dashboard/components/drawer/RoomMetricsTab.tsx +90 -0
- package/apps/harness-dashboard/components/drawer/TracksTab.tsx +47 -0
- package/apps/harness-dashboard/components/three/ArchiveBoxes.tsx +95 -0
- package/apps/harness-dashboard/components/three/DeptBoard.tsx +175 -0
- package/apps/harness-dashboard/components/three/Floor3D.tsx +404 -0
- package/apps/harness-dashboard/components/three/GoalCard3D.tsx +123 -0
- package/apps/harness-dashboard/components/three/HypothesisCards.tsx +71 -0
- package/apps/harness-dashboard/components/three/MeetingWhiteboard.tsx +98 -0
- package/apps/harness-dashboard/components/three/Minifig3D.tsx +391 -0
- package/apps/harness-dashboard/components/three/OpsSiren.tsx +99 -0
- package/apps/harness-dashboard/components/three/Stage3D.tsx +104 -0
- package/apps/harness-dashboard/components/three/TrackRibbons.tsx +81 -0
- package/apps/harness-dashboard/hooks/useHarnessStream.ts +53 -0
- package/apps/harness-dashboard/lib/__tests__/company-flow.test.ts +270 -0
- package/apps/harness-dashboard/lib/__tests__/harness-state.test.ts +194 -0
- package/apps/harness-dashboard/lib/__tests__/i18n.test.ts +16 -0
- package/apps/harness-dashboard/lib/__tests__/iso.test.ts +42 -0
- package/apps/harness-dashboard/lib/__tests__/safe-json.test.ts +46 -0
- package/apps/harness-dashboard/lib/__tests__/state-mapping.test.ts +159 -0
- package/apps/harness-dashboard/lib/__tests__/watcher.test.ts +48 -0
- package/apps/harness-dashboard/lib/agent-roster.ts +42 -0
- package/apps/harness-dashboard/lib/furniture.ts +165 -0
- package/apps/harness-dashboard/lib/harness-root.ts +20 -0
- package/apps/harness-dashboard/lib/harness-state.ts +1193 -0
- package/apps/harness-dashboard/lib/i18n.ts +26 -0
- package/apps/harness-dashboard/lib/iso.ts +150 -0
- package/apps/harness-dashboard/lib/log-filter.ts +23 -0
- package/apps/harness-dashboard/lib/markdown.tsx +306 -0
- package/apps/harness-dashboard/lib/path-planning.ts +72 -0
- package/apps/harness-dashboard/lib/safe-json.ts +24 -0
- package/apps/harness-dashboard/lib/state-mapping.ts +126 -0
- package/apps/harness-dashboard/lib/types.ts +329 -0
- package/apps/harness-dashboard/lib/watcher.ts +64 -0
- package/apps/harness-dashboard/next-env.d.ts +6 -0
- package/apps/harness-dashboard/next.config.mjs +9 -0
- package/apps/harness-dashboard/package-lock.json +4280 -0
- package/apps/harness-dashboard/package.json +38 -0
- package/apps/harness-dashboard/playwright.config.ts +23 -0
- package/apps/harness-dashboard/postcss.config.js +6 -0
- package/apps/harness-dashboard/tailwind.config.ts +49 -0
- package/apps/harness-dashboard/tsconfig.json +24 -0
- package/apps/harness-dashboard/vitest.config.ts +15 -0
- package/assets/templates/AGENTS-ko.md.template +19 -6
- package/assets/templates/AGENTS.md.template +20 -7
- package/assets/templates/HARNESS.md +75 -199
- package/assets/templates/config.json +2 -2
- package/bin/init.js +11 -11
- package/commands/goal.md +6 -4
- package/commands/hot-fix.md +16 -13
- package/commands/submission.md +28 -0
- package/conventions/ceo.md +3 -3
- package/conventions/shared.md +4 -3
- package/package.json +11 -1
- package/scripts/harness-dashboard-up.sh +24 -4
- package/scripts/harness-session-start.sh +1 -1
- package/scripts/harness-stop.sh +1 -1
- package/scripts/harness-user-prompt-submit.sh +12 -4
- package/scripts/harness-worker-evidence-validate.sh +8 -3
- package/scripts/import-agency-agents.js +1 -1
- package/scripts/init-agents-md.sh +17 -2
- package/assets/templates/AGENTS-ko /341/204/207/341/205/251/341/206/250/341/204/211/341/205/241/341/204/207/341/205/251/341/206/253.md" +0 -114
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import type { RoomState } from "@/lib/types";
|
|
3
|
+
|
|
4
|
+
interface RoomMetricsTabProps {
|
|
5
|
+
room: RoomState;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const METRIC_LABEL: Record<string, string> = {
|
|
9
|
+
sprint_number: "Cycle",
|
|
10
|
+
last_review: "Last review",
|
|
11
|
+
last_audit: "Last audit",
|
|
12
|
+
last_check: "Last check",
|
|
13
|
+
sprint_verdict: "Verdict",
|
|
14
|
+
open_alerts: "Open alerts",
|
|
15
|
+
open_arch_risks: "Open arch risks",
|
|
16
|
+
open_regressions: "Open regressions",
|
|
17
|
+
cadence: "Cadence",
|
|
18
|
+
next_scheduled: "Next meeting",
|
|
19
|
+
active_tracks: "Active tracks",
|
|
20
|
+
active_hypothesis: "Active hypothesis",
|
|
21
|
+
active_workers: "Active workers",
|
|
22
|
+
open_incidents: "Open incidents",
|
|
23
|
+
pass_rate: "Pass rate",
|
|
24
|
+
contract_signed: "Contract signed",
|
|
25
|
+
eval_scores: "Eval scores",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function fmt(key: string, value: unknown): string {
|
|
29
|
+
if (value === null || value === undefined || value === "") return "—";
|
|
30
|
+
if (key === "pass_rate" && typeof value === "number") {
|
|
31
|
+
return `${Math.round(value * 100)}%`;
|
|
32
|
+
}
|
|
33
|
+
if (key === "contract_signed" && typeof value === "object" && value) {
|
|
34
|
+
const v = value as { be?: boolean; fe?: boolean };
|
|
35
|
+
const parts = [v.be ? "BE" : null, v.fe ? "FE" : null].filter(Boolean);
|
|
36
|
+
return parts.length ? parts.join("·") : "—";
|
|
37
|
+
}
|
|
38
|
+
if (key === "eval_scores" && typeof value === "object" && value) {
|
|
39
|
+
const v = value as Record<string, number | null | undefined>;
|
|
40
|
+
const parts = Object.entries(v)
|
|
41
|
+
.filter(([, s]) => typeof s === "number")
|
|
42
|
+
.map(([k, s]) => `${k.slice(0, 3)} ${(s as number).toFixed(1)}`);
|
|
43
|
+
return parts.length ? parts.join(" · ") : "—";
|
|
44
|
+
}
|
|
45
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
46
|
+
return String(value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function RoomMetricsTab({ room }: RoomMetricsTabProps) {
|
|
50
|
+
const metrics = room.metrics ?? {};
|
|
51
|
+
const entries = Object.entries(metrics);
|
|
52
|
+
return (
|
|
53
|
+
<div className="space-y-3">
|
|
54
|
+
<div className="text-gray-400 text-[11px]">
|
|
55
|
+
{room.label_ko} <span className="text-gray-600">({room.label_en})</span>
|
|
56
|
+
</div>
|
|
57
|
+
{entries.length === 0 ? (
|
|
58
|
+
<div className="text-gray-500">No metrics available for this room.</div>
|
|
59
|
+
) : (
|
|
60
|
+
<div className="grid grid-cols-2 gap-2">
|
|
61
|
+
{entries.map(([k, v]) => (
|
|
62
|
+
<div
|
|
63
|
+
key={k}
|
|
64
|
+
data-testid={`metric-${k}`}
|
|
65
|
+
className="rounded border border-brick-wall bg-brick-wall/30 px-2 py-1.5"
|
|
66
|
+
>
|
|
67
|
+
<div className="text-[10px] uppercase tracking-wider text-gray-500">
|
|
68
|
+
{METRIC_LABEL[k] ?? k}
|
|
69
|
+
</div>
|
|
70
|
+
<div className="text-[12px] text-gray-100">{fmt(k, v)}</div>
|
|
71
|
+
</div>
|
|
72
|
+
))}
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
{room.seatLayout && (
|
|
76
|
+
<div className="rounded border border-brick-wall bg-brick-wall/30 px-2 py-1.5">
|
|
77
|
+
<div className="text-[10px] uppercase tracking-wider text-gray-500">
|
|
78
|
+
Seats
|
|
79
|
+
</div>
|
|
80
|
+
<div className="text-[12px] text-gray-100">
|
|
81
|
+
{room.seatLayout.occupants.length} / {room.seatLayout.seats}
|
|
82
|
+
{room.seatLayout.overflow > 0 && (
|
|
83
|
+
<span className="text-aura-alert"> +{room.seatLayout.overflow}</span>
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import type { ParallelTrack } from "@/lib/types";
|
|
3
|
+
|
|
4
|
+
const STATUS_TONE: Record<string, string> = {
|
|
5
|
+
dispatched: "border-aura-talking/40 bg-aura-talking/10 text-aura-talking",
|
|
6
|
+
in_progress: "border-aura-typing/40 bg-aura-typing/10 text-aura-typing",
|
|
7
|
+
joined: "border-aura-typing/60 bg-aura-typing/20 text-aura-typing",
|
|
8
|
+
blocked: "border-aura-alert/40 bg-aura-alert/10 text-aura-alert",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
interface TracksTabProps {
|
|
12
|
+
tracks: ParallelTrack[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function TracksTab({ tracks }: TracksTabProps) {
|
|
16
|
+
if (tracks.length === 0) {
|
|
17
|
+
return (
|
|
18
|
+
<div className="text-gray-500" data-testid="tracks-empty">
|
|
19
|
+
No parallel tracks active.
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
return (
|
|
24
|
+
<ul className="space-y-2" data-testid="tracks-list">
|
|
25
|
+
{tracks.map((t) => (
|
|
26
|
+
<li
|
|
27
|
+
key={t.id}
|
|
28
|
+
data-testid={`track-row-${t.id}`}
|
|
29
|
+
className={`rounded border px-2 py-1.5 ${
|
|
30
|
+
STATUS_TONE[t.status] ?? STATUS_TONE.dispatched
|
|
31
|
+
}`}
|
|
32
|
+
>
|
|
33
|
+
<div className="flex items-center justify-between text-[11px]">
|
|
34
|
+
<span className="font-semibold">{t.id}</span>
|
|
35
|
+
<span className="uppercase tracking-wider text-[9px]">{t.status}</span>
|
|
36
|
+
</div>
|
|
37
|
+
<div className="mt-1 text-[10px] text-gray-300">
|
|
38
|
+
{t.from_meeting} → {t.to_dept} ({t.to_room})
|
|
39
|
+
</div>
|
|
40
|
+
{t.label && (
|
|
41
|
+
<div className="mt-0.5 text-[11px] text-gray-100">{t.label}</div>
|
|
42
|
+
)}
|
|
43
|
+
</li>
|
|
44
|
+
))}
|
|
45
|
+
</ul>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Html } from "@react-three/drei";
|
|
3
|
+
import { ROOM_RECTS } from "@/lib/iso";
|
|
4
|
+
import type { ArchiveStat } from "@/lib/types";
|
|
5
|
+
|
|
6
|
+
interface ArchiveBoxesProps {
|
|
7
|
+
archive: ArchiveStat;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Visualises completed sprints as cardboard boxes stacked inside the archive
|
|
11
|
+
// room. The most-recent three are labelled (drei <Html>); the rest are
|
|
12
|
+
// anonymous boxes plus a "+N more" label.
|
|
13
|
+
export function ArchiveBoxes({ archive }: ArchiveBoxesProps) {
|
|
14
|
+
const room = ROOM_RECTS.find((r) => r.id === "archive")!;
|
|
15
|
+
const cols = 6;
|
|
16
|
+
const rowSpacingX = (room.ww - 1.0) / cols; // leave 0.5 inset on each side
|
|
17
|
+
const rowSpacingZ = 0.45;
|
|
18
|
+
const baseX = room.wx + 0.5;
|
|
19
|
+
const baseZ = room.wy + 0.5;
|
|
20
|
+
|
|
21
|
+
// Use the full sorted list so each rendered box can carry its own verdict
|
|
22
|
+
// colour, not just the recent three.
|
|
23
|
+
const ordered = archive.all.length > 0 ? archive.all : archive.recent;
|
|
24
|
+
const boxes = Array.from({ length: archive.sprintCount }, (_, i) => {
|
|
25
|
+
const col = i % cols;
|
|
26
|
+
const row = Math.floor(i / cols);
|
|
27
|
+
const entry = i < ordered.length ? ordered[i] : null;
|
|
28
|
+
return {
|
|
29
|
+
x: baseX + col * rowSpacingX + rowSpacingX / 2,
|
|
30
|
+
z: baseZ + row * rowSpacingZ,
|
|
31
|
+
isRecent: i < 3,
|
|
32
|
+
entry,
|
|
33
|
+
result: entry?.result ?? "unknown",
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const overflowAfterThree = Math.max(0, archive.sprintCount - 3);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<group data-testid="archive-boxes">
|
|
41
|
+
{boxes.map((b, i) => {
|
|
42
|
+
const baseColor = b.isRecent ? "#caa978" : "#8a7c5a";
|
|
43
|
+
const tapeColor =
|
|
44
|
+
b.result === "PASS"
|
|
45
|
+
? "#34d399"
|
|
46
|
+
: b.result === "FAIL"
|
|
47
|
+
? "#ef4444"
|
|
48
|
+
: "#6a5a3a";
|
|
49
|
+
return (
|
|
50
|
+
<group key={i} position={[b.x, 0.21, b.z]} data-testid={`archive-box-${b.result.toLowerCase()}`}>
|
|
51
|
+
<mesh castShadow position={[0, 0.16, 0]}>
|
|
52
|
+
<boxGeometry args={[0.34, 0.32, 0.28]} />
|
|
53
|
+
<meshStandardMaterial
|
|
54
|
+
color={baseColor}
|
|
55
|
+
roughness={0.85}
|
|
56
|
+
/>
|
|
57
|
+
</mesh>
|
|
58
|
+
{/* Tape line on top — verdict-coloured for at-a-glance archive scan. */}
|
|
59
|
+
<mesh position={[0, 0.33, 0]}>
|
|
60
|
+
<boxGeometry args={[0.36, 0.01, 0.06]} />
|
|
61
|
+
<meshStandardMaterial color={tapeColor} />
|
|
62
|
+
</mesh>
|
|
63
|
+
{b.isRecent && b.entry && (
|
|
64
|
+
<Html
|
|
65
|
+
position={[0, 0.45, 0]}
|
|
66
|
+
transform
|
|
67
|
+
occlude={false}
|
|
68
|
+
scale={0.08}
|
|
69
|
+
pointerEvents="none"
|
|
70
|
+
zIndexRange={[3, 0]}
|
|
71
|
+
>
|
|
72
|
+
<div className="rounded bg-black/70 px-1.5 py-0.5 text-[10px] font-mono text-gray-100 whitespace-nowrap">
|
|
73
|
+
{b.entry.label}
|
|
74
|
+
</div>
|
|
75
|
+
</Html>
|
|
76
|
+
)}
|
|
77
|
+
</group>
|
|
78
|
+
);
|
|
79
|
+
})}
|
|
80
|
+
{overflowAfterThree > 0 && (
|
|
81
|
+
<Html
|
|
82
|
+
position={[room.wx + room.ww / 2, 0.6, room.wy + room.wh - 0.3]}
|
|
83
|
+
transform
|
|
84
|
+
occlude={false}
|
|
85
|
+
scale={0.1}
|
|
86
|
+
pointerEvents="none"
|
|
87
|
+
>
|
|
88
|
+
<div className="rounded bg-aura-typing/40 px-2 py-0.5 text-[10px] font-mono text-aura-typing whitespace-nowrap">
|
|
89
|
+
+{overflowAfterThree} more
|
|
90
|
+
</div>
|
|
91
|
+
</Html>
|
|
92
|
+
)}
|
|
93
|
+
</group>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Html } from "@react-three/drei";
|
|
3
|
+
import { ROOM_RECTS } from "@/lib/iso";
|
|
4
|
+
import type {
|
|
5
|
+
ContractSnapshot,
|
|
6
|
+
EvalScores,
|
|
7
|
+
RoomId,
|
|
8
|
+
} from "@/lib/types";
|
|
9
|
+
|
|
10
|
+
interface DeptBoardProps {
|
|
11
|
+
roomId: Extract<RoomId, "cto-team" | "cqo-team">;
|
|
12
|
+
contract: ContractSnapshot;
|
|
13
|
+
evalScores: EvalScores | null;
|
|
14
|
+
passRate: number | null;
|
|
15
|
+
openArchRisks?: number;
|
|
16
|
+
openRegressions?: number;
|
|
17
|
+
sprintVerdict?: "pending" | "PASS" | "FAIL";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function fmtRate(rate: number | null): string {
|
|
21
|
+
if (rate === null || !Number.isFinite(rate)) return "—";
|
|
22
|
+
return `${Math.round(rate * 100)}%`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function fmtScore(s: number | null | undefined): string {
|
|
26
|
+
if (s === null || s === undefined) return "—";
|
|
27
|
+
return s.toFixed(2);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Wall board for CTO/CQO rooms. CTO: contract sign-off + pass-rate. CQO:
|
|
31
|
+
// 5-axis eval scores + verdict + regressions.
|
|
32
|
+
export function DeptBoard({
|
|
33
|
+
roomId,
|
|
34
|
+
contract,
|
|
35
|
+
evalScores,
|
|
36
|
+
passRate,
|
|
37
|
+
openArchRisks,
|
|
38
|
+
openRegressions,
|
|
39
|
+
sprintVerdict,
|
|
40
|
+
}: DeptBoardProps) {
|
|
41
|
+
const room = ROOM_RECTS.find((r) => r.id === roomId)!;
|
|
42
|
+
const cx = room.wx + room.ww / 2;
|
|
43
|
+
// Mount on the back wall (wy is the north edge).
|
|
44
|
+
const cz = room.wy + 0.18;
|
|
45
|
+
const isCto = roomId === "cto-team";
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Html
|
|
49
|
+
position={[cx, 0.85, cz]}
|
|
50
|
+
transform
|
|
51
|
+
occlude={false}
|
|
52
|
+
scale={0.14}
|
|
53
|
+
pointerEvents="none"
|
|
54
|
+
zIndexRange={[5, 0]}
|
|
55
|
+
>
|
|
56
|
+
<div
|
|
57
|
+
data-testid={`dept-board-${roomId}`}
|
|
58
|
+
className="rounded-md border border-brick-wall bg-brick-bg/95 px-3 py-2 shadow-lg text-gray-100 font-mono"
|
|
59
|
+
style={{ minWidth: 240, maxWidth: 320 }}
|
|
60
|
+
>
|
|
61
|
+
<div className="text-[9px] uppercase tracking-widest text-gray-400">
|
|
62
|
+
{isCto ? "CTO BOARD" : "CQO BOARD"}
|
|
63
|
+
</div>
|
|
64
|
+
<div className="mt-1 grid grid-cols-2 gap-1 text-[10px]">
|
|
65
|
+
<div className="rounded bg-brick-wall/50 px-1.5 py-0.5">
|
|
66
|
+
<span className="text-gray-500">sprint</span>{" "}
|
|
67
|
+
<span data-testid={`board-sprint-${roomId}`}>
|
|
68
|
+
{contract.sprint_number ?? "—"}
|
|
69
|
+
</span>
|
|
70
|
+
</div>
|
|
71
|
+
<div className="rounded bg-brick-wall/50 px-1.5 py-0.5">
|
|
72
|
+
<span className="text-gray-500">pass</span>{" "}
|
|
73
|
+
<span data-testid={`board-pass-${roomId}`}>{fmtRate(passRate)}</span>
|
|
74
|
+
</div>
|
|
75
|
+
{isCto ? (
|
|
76
|
+
<>
|
|
77
|
+
<div className="rounded bg-brick-wall/50 px-1.5 py-0.5">
|
|
78
|
+
<span className="text-gray-500">contract BE</span>{" "}
|
|
79
|
+
<span
|
|
80
|
+
className={
|
|
81
|
+
contract.contract_signed.be ? "text-aura-typing" : "text-gray-500"
|
|
82
|
+
}
|
|
83
|
+
>
|
|
84
|
+
{contract.contract_signed.be ? "signed" : "—"}
|
|
85
|
+
</span>
|
|
86
|
+
</div>
|
|
87
|
+
<div className="rounded bg-brick-wall/50 px-1.5 py-0.5">
|
|
88
|
+
<span className="text-gray-500">contract FE</span>{" "}
|
|
89
|
+
<span
|
|
90
|
+
className={
|
|
91
|
+
contract.contract_signed.fe ? "text-aura-typing" : "text-gray-500"
|
|
92
|
+
}
|
|
93
|
+
>
|
|
94
|
+
{contract.contract_signed.fe ? "signed" : "—"}
|
|
95
|
+
</span>
|
|
96
|
+
</div>
|
|
97
|
+
<div className="rounded bg-brick-wall/50 px-1.5 py-0.5 col-span-2">
|
|
98
|
+
<span className="text-gray-500">arch risks</span>{" "}
|
|
99
|
+
<span
|
|
100
|
+
className={
|
|
101
|
+
(openArchRisks ?? 0) > 0
|
|
102
|
+
? "text-aura-alert"
|
|
103
|
+
: "text-aura-typing"
|
|
104
|
+
}
|
|
105
|
+
>
|
|
106
|
+
{openArchRisks ?? 0}
|
|
107
|
+
</span>
|
|
108
|
+
</div>
|
|
109
|
+
</>
|
|
110
|
+
) : (
|
|
111
|
+
<>
|
|
112
|
+
<div className="rounded bg-brick-wall/50 px-1.5 py-0.5 col-span-2">
|
|
113
|
+
<span className="text-gray-500">verdict</span>{" "}
|
|
114
|
+
<span
|
|
115
|
+
className={
|
|
116
|
+
sprintVerdict === "PASS"
|
|
117
|
+
? "text-aura-typing"
|
|
118
|
+
: sprintVerdict === "FAIL"
|
|
119
|
+
? "text-aura-alert"
|
|
120
|
+
: "text-gray-300"
|
|
121
|
+
}
|
|
122
|
+
>
|
|
123
|
+
{sprintVerdict ?? "pending"}
|
|
124
|
+
</span>
|
|
125
|
+
</div>
|
|
126
|
+
<div className="rounded bg-brick-wall/50 px-1.5 py-0.5 col-span-2">
|
|
127
|
+
<span className="text-gray-500">regressions</span>{" "}
|
|
128
|
+
<span
|
|
129
|
+
className={
|
|
130
|
+
(openRegressions ?? 0) > 0
|
|
131
|
+
? "text-aura-alert"
|
|
132
|
+
: "text-aura-typing"
|
|
133
|
+
}
|
|
134
|
+
>
|
|
135
|
+
{openRegressions ?? 0}
|
|
136
|
+
</span>
|
|
137
|
+
</div>
|
|
138
|
+
</>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
{!isCto && evalScores && (
|
|
143
|
+
<div
|
|
144
|
+
data-testid="eval-scores"
|
|
145
|
+
className="mt-1.5 grid grid-cols-5 gap-1 text-[9px] uppercase"
|
|
146
|
+
>
|
|
147
|
+
{(
|
|
148
|
+
["functional", "visual", "code_quality", "architecture", "security"] as const
|
|
149
|
+
).map((axis) => {
|
|
150
|
+
const v = evalScores?.[axis] ?? null;
|
|
151
|
+
const fail = v !== null && typeof v === "number" && v < 2.8;
|
|
152
|
+
return (
|
|
153
|
+
<div
|
|
154
|
+
key={axis}
|
|
155
|
+
data-testid={`eval-axis-${axis}`}
|
|
156
|
+
className={`rounded px-1 py-0.5 text-center ${
|
|
157
|
+
fail
|
|
158
|
+
? "bg-aura-alert/30 text-aura-alert"
|
|
159
|
+
: "bg-aura-typing/20 text-aura-typing"
|
|
160
|
+
}`}
|
|
161
|
+
title={axis}
|
|
162
|
+
>
|
|
163
|
+
<div className="text-[8px] tracking-wider opacity-70">
|
|
164
|
+
{axis.slice(0, 3)}
|
|
165
|
+
</div>
|
|
166
|
+
<div>{fmtScore(v)}</div>
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
169
|
+
})}
|
|
170
|
+
</div>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
</Html>
|
|
174
|
+
);
|
|
175
|
+
}
|