@coldtea/abide 0.0.1
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 +10 -0
- package/dist/abide-hook.d.ts +2 -0
- package/dist/abide-hook.d.ts.map +1 -0
- package/dist/abide-hook.js +23 -0
- package/dist/abide-hook.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +64 -0
- package/dist/bin.js.map +1 -0
- package/dist/commands/audit.d.ts +3 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +79 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/bench.d.ts +3 -0
- package/dist/commands/bench.d.ts.map +1 -0
- package/dist/commands/bench.js +206 -0
- package/dist/commands/bench.js.map +1 -0
- package/dist/commands/calibrate.d.ts +3 -0
- package/dist/commands/calibrate.d.ts.map +1 -0
- package/dist/commands/calibrate.js +159 -0
- package/dist/commands/calibrate.js.map +1 -0
- package/dist/commands/check.d.ts +3 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +106 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/compile.d.ts +3 -0
- package/dist/commands/compile.d.ts.map +1 -0
- package/dist/commands/compile.js +116 -0
- package/dist/commands/compile.js.map +1 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +93 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +58 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/replay.d.ts +4 -0
- package/dist/commands/replay.d.ts.map +1 -0
- package/dist/commands/replay.js +118 -0
- package/dist/commands/replay.js.map +1 -0
- package/dist/commands/report.d.ts +5 -0
- package/dist/commands/report.d.ts.map +1 -0
- package/dist/commands/report.js +78 -0
- package/dist/commands/report.js.map +1 -0
- package/dist/commands/rubric.d.ts +3 -0
- package/dist/commands/rubric.d.ts.map +1 -0
- package/dist/commands/rubric.js +40 -0
- package/dist/commands/rubric.js.map +1 -0
- package/dist/commands/uninstall.d.ts +2 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +31 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/hooks/postToolUse.d.ts +3 -0
- package/dist/hooks/postToolUse.d.ts.map +1 -0
- package/dist/hooks/postToolUse.js +145 -0
- package/dist/hooks/postToolUse.js.map +1 -0
- package/dist/hooks/sessionStart.d.ts +11 -0
- package/dist/hooks/sessionStart.d.ts.map +1 -0
- package/dist/hooks/sessionStart.js +83 -0
- package/dist/hooks/sessionStart.js.map +1 -0
- package/dist/hooks/stop.d.ts +26 -0
- package/dist/hooks/stop.d.ts.map +1 -0
- package/dist/hooks/stop.js +227 -0
- package/dist/hooks/stop.js.map +1 -0
- package/dist/hooks/turnStart.d.ts +8 -0
- package/dist/hooks/turnStart.d.ts.map +1 -0
- package/dist/hooks/turnStart.js +40 -0
- package/dist/hooks/turnStart.js.map +1 -0
- package/dist/lib/applyPatch.d.ts +22 -0
- package/dist/lib/applyPatch.d.ts.map +1 -0
- package/dist/lib/applyPatch.js +63 -0
- package/dist/lib/applyPatch.js.map +1 -0
- package/dist/lib/audit.d.ts +40 -0
- package/dist/lib/audit.d.ts.map +1 -0
- package/dist/lib/audit.js +215 -0
- package/dist/lib/audit.js.map +1 -0
- package/dist/lib/band.d.ts +20 -0
- package/dist/lib/band.d.ts.map +1 -0
- package/dist/lib/band.js +40 -0
- package/dist/lib/band.js.map +1 -0
- package/dist/lib/calibration.d.ts +13 -0
- package/dist/lib/calibration.d.ts.map +1 -0
- package/dist/lib/calibration.js +25 -0
- package/dist/lib/calibration.js.map +1 -0
- package/dist/lib/checkRunner.d.ts +34 -0
- package/dist/lib/checkRunner.d.ts.map +1 -0
- package/dist/lib/checkRunner.js +76 -0
- package/dist/lib/checkRunner.js.map +1 -0
- package/dist/lib/claude.d.ts +4 -0
- package/dist/lib/claude.d.ts.map +1 -0
- package/dist/lib/claude.js +28 -0
- package/dist/lib/claude.js.map +1 -0
- package/dist/lib/compilePrompt.d.ts +22 -0
- package/dist/lib/compilePrompt.d.ts.map +1 -0
- package/dist/lib/compilePrompt.js +33 -0
- package/dist/lib/compilePrompt.js.map +1 -0
- package/dist/lib/constants.d.ts +33 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +33 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/coverage.d.ts +7 -0
- package/dist/lib/coverage.d.ts.map +1 -0
- package/dist/lib/coverage.js +17 -0
- package/dist/lib/coverage.js.map +1 -0
- package/dist/lib/credentials.d.ts +27 -0
- package/dist/lib/credentials.d.ts.map +1 -0
- package/dist/lib/credentials.js +89 -0
- package/dist/lib/credentials.js.map +1 -0
- package/dist/lib/diff.d.ts +30 -0
- package/dist/lib/diff.d.ts.map +1 -0
- package/dist/lib/diff.js +139 -0
- package/dist/lib/diff.js.map +1 -0
- package/dist/lib/events.d.ts +5 -0
- package/dist/lib/events.d.ts.map +1 -0
- package/dist/lib/events.js +38 -0
- package/dist/lib/events.js.map +1 -0
- package/dist/lib/git.d.ts +37 -0
- package/dist/lib/git.d.ts.map +1 -0
- package/dist/lib/git.js +163 -0
- package/dist/lib/git.js.map +1 -0
- package/dist/lib/hookRunner.d.ts +10 -0
- package/dist/lib/hookRunner.d.ts.map +1 -0
- package/dist/lib/hookRunner.js +52 -0
- package/dist/lib/hookRunner.js.map +1 -0
- package/dist/lib/hosts.d.ts +20 -0
- package/dist/lib/hosts.d.ts.map +1 -0
- package/dist/lib/hosts.js +101 -0
- package/dist/lib/hosts.js.map +1 -0
- package/dist/lib/jev.d.ts +24 -0
- package/dist/lib/jev.d.ts.map +1 -0
- package/dist/lib/jev.js +169 -0
- package/dist/lib/jev.js.map +1 -0
- package/dist/lib/lintConfig.d.ts +3 -0
- package/dist/lib/lintConfig.d.ts.map +1 -0
- package/dist/lib/lintConfig.js +55 -0
- package/dist/lib/lintConfig.js.map +1 -0
- package/dist/lib/loadRules.d.ts +12 -0
- package/dist/lib/loadRules.d.ts.map +1 -0
- package/dist/lib/loadRules.js +31 -0
- package/dist/lib/loadRules.js.map +1 -0
- package/dist/lib/opencodePlugin.d.ts +10 -0
- package/dist/lib/opencodePlugin.d.ts.map +1 -0
- package/dist/lib/opencodePlugin.js +36 -0
- package/dist/lib/opencodePlugin.js.map +1 -0
- package/dist/lib/output.d.ts +6 -0
- package/dist/lib/output.d.ts.map +1 -0
- package/dist/lib/output.js +35 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/packageRoot.d.ts +12 -0
- package/dist/lib/packageRoot.d.ts.map +1 -0
- package/dist/lib/packageRoot.js +50 -0
- package/dist/lib/packageRoot.js.map +1 -0
- package/dist/lib/paths.d.ts +24 -0
- package/dist/lib/paths.d.ts.map +1 -0
- package/dist/lib/paths.js +75 -0
- package/dist/lib/paths.js.map +1 -0
- package/dist/lib/reason.d.ts +11 -0
- package/dist/lib/reason.d.ts.map +1 -0
- package/dist/lib/reason.js +31 -0
- package/dist/lib/reason.js.map +1 -0
- package/dist/lib/regularFile.d.ts +26 -0
- package/dist/lib/regularFile.d.ts.map +1 -0
- package/dist/lib/regularFile.js +77 -0
- package/dist/lib/regularFile.js.map +1 -0
- package/dist/lib/replay.d.ts +74 -0
- package/dist/lib/replay.d.ts.map +1 -0
- package/dist/lib/replay.js +250 -0
- package/dist/lib/replay.js.map +1 -0
- package/dist/lib/replayCodex.d.ts +6 -0
- package/dist/lib/replayCodex.d.ts.map +1 -0
- package/dist/lib/replayCodex.js +132 -0
- package/dist/lib/replayCodex.js.map +1 -0
- package/dist/lib/replayOpencode.d.ts +15 -0
- package/dist/lib/replayOpencode.d.ts.map +1 -0
- package/dist/lib/replayOpencode.js +154 -0
- package/dist/lib/replayOpencode.js.map +1 -0
- package/dist/lib/rubricFile.d.ts +31 -0
- package/dist/lib/rubricFile.d.ts.map +1 -0
- package/dist/lib/rubricFile.js +75 -0
- package/dist/lib/rubricFile.js.map +1 -0
- package/dist/lib/scope.d.ts +4 -0
- package/dist/lib/scope.d.ts.map +1 -0
- package/dist/lib/scope.js +14 -0
- package/dist/lib/scope.js.map +1 -0
- package/dist/lib/session.d.ts +48 -0
- package/dist/lib/session.d.ts.map +1 -0
- package/dist/lib/session.js +182 -0
- package/dist/lib/session.js.map +1 -0
- package/dist/lib/settings.d.ts +26 -0
- package/dist/lib/settings.d.ts.map +1 -0
- package/dist/lib/settings.js +104 -0
- package/dist/lib/settings.js.map +1 -0
- package/dist/lib/sources.d.ts +28 -0
- package/dist/lib/sources.d.ts.map +1 -0
- package/dist/lib/sources.js +137 -0
- package/dist/lib/sources.js.map +1 -0
- package/dist/lib/stdin.d.ts +3 -0
- package/dist/lib/stdin.d.ts.map +1 -0
- package/dist/lib/stdin.js +24 -0
- package/dist/lib/stdin.js.map +1 -0
- package/dist/lib/transcript.d.ts +3 -0
- package/dist/lib/transcript.d.ts.map +1 -0
- package/dist/lib/transcript.js +57 -0
- package/dist/lib/transcript.js.map +1 -0
- package/dist/lib/ui.d.ts +8 -0
- package/dist/lib/ui.d.ts.map +1 -0
- package/dist/lib/ui.js +34 -0
- package/dist/lib/ui.js.map +1 -0
- package/dist/ui/components/Buckets.d.ts +6 -0
- package/dist/ui/components/Buckets.d.ts.map +1 -0
- package/dist/ui/components/Buckets.js +11 -0
- package/dist/ui/components/Buckets.js.map +1 -0
- package/dist/ui/components/Callout.d.ts +11 -0
- package/dist/ui/components/Callout.d.ts.map +1 -0
- package/dist/ui/components/Callout.js +10 -0
- package/dist/ui/components/Callout.js.map +1 -0
- package/dist/ui/components/Checklist.d.ts +10 -0
- package/dist/ui/components/Checklist.d.ts.map +1 -0
- package/dist/ui/components/Checklist.js +8 -0
- package/dist/ui/components/Checklist.js.map +1 -0
- package/dist/ui/components/Columns.d.ts +28 -0
- package/dist/ui/components/Columns.d.ts.map +1 -0
- package/dist/ui/components/Columns.js +20 -0
- package/dist/ui/components/Columns.js.map +1 -0
- package/dist/ui/components/Header.d.ts +9 -0
- package/dist/ui/components/Header.d.ts.map +1 -0
- package/dist/ui/components/Header.js +7 -0
- package/dist/ui/components/Header.js.map +1 -0
- package/dist/ui/components/Live.d.ts +16 -0
- package/dist/ui/components/Live.d.ts.map +1 -0
- package/dist/ui/components/Live.js +40 -0
- package/dist/ui/components/Live.js.map +1 -0
- package/dist/ui/components/RuleTable.d.ts +15 -0
- package/dist/ui/components/RuleTable.d.ts.map +1 -0
- package/dist/ui/components/RuleTable.js +81 -0
- package/dist/ui/components/RuleTable.js.map +1 -0
- package/dist/ui/components/Section.d.ts +10 -0
- package/dist/ui/components/Section.d.ts.map +1 -0
- package/dist/ui/components/Section.js +8 -0
- package/dist/ui/components/Section.js.map +1 -0
- package/dist/ui/components/Verdicts.d.ts +9 -0
- package/dist/ui/components/Verdicts.d.ts.map +1 -0
- package/dist/ui/components/Verdicts.js +12 -0
- package/dist/ui/components/Verdicts.js.map +1 -0
- package/dist/ui/render.d.ts +20 -0
- package/dist/ui/render.d.ts.map +1 -0
- package/dist/ui/render.js +32 -0
- package/dist/ui/render.js.map +1 -0
- package/dist/ui/theme.d.ts +37 -0
- package/dist/ui/theme.d.ts.map +1 -0
- package/dist/ui/theme.js +63 -0
- package/dist/ui/theme.js.map +1 -0
- package/dist/ui/views/AuditView.d.ts +18 -0
- package/dist/ui/views/AuditView.d.ts.map +1 -0
- package/dist/ui/views/AuditView.js +48 -0
- package/dist/ui/views/AuditView.js.map +1 -0
- package/dist/ui/views/BenchView.d.ts +33 -0
- package/dist/ui/views/BenchView.d.ts.map +1 -0
- package/dist/ui/views/BenchView.js +19 -0
- package/dist/ui/views/BenchView.js.map +1 -0
- package/dist/ui/views/CalibrateView.d.ts +26 -0
- package/dist/ui/views/CalibrateView.d.ts.map +1 -0
- package/dist/ui/views/CalibrateView.js +42 -0
- package/dist/ui/views/CalibrateView.js.map +1 -0
- package/dist/ui/views/CheckView.d.ts +19 -0
- package/dist/ui/views/CheckView.d.ts.map +1 -0
- package/dist/ui/views/CheckView.js +15 -0
- package/dist/ui/views/CheckView.js.map +1 -0
- package/dist/ui/views/ErrorView.d.ts +4 -0
- package/dist/ui/views/ErrorView.d.ts.map +1 -0
- package/dist/ui/views/ErrorView.js +26 -0
- package/dist/ui/views/ErrorView.js.map +1 -0
- package/dist/ui/views/HelpView.d.ts +2 -0
- package/dist/ui/views/HelpView.d.ts.map +1 -0
- package/dist/ui/views/HelpView.js +25 -0
- package/dist/ui/views/HelpView.js.map +1 -0
- package/dist/ui/views/InitView.d.ts +27 -0
- package/dist/ui/views/InitView.d.ts.map +1 -0
- package/dist/ui/views/InitView.js +24 -0
- package/dist/ui/views/InitView.js.map +1 -0
- package/dist/ui/views/ReplayView.d.ts +16 -0
- package/dist/ui/views/ReplayView.d.ts.map +1 -0
- package/dist/ui/views/ReplayView.js +44 -0
- package/dist/ui/views/ReplayView.js.map +1 -0
- package/dist/ui/views/ReportView.d.ts +14 -0
- package/dist/ui/views/ReportView.d.ts.map +1 -0
- package/dist/ui/views/ReportView.js +86 -0
- package/dist/ui/views/ReportView.js.map +1 -0
- package/dist/ui/views/RubricView.d.ts +18 -0
- package/dist/ui/views/RubricView.d.ts.map +1 -0
- package/dist/ui/views/RubricView.js +18 -0
- package/dist/ui/views/RubricView.js.map +1 -0
- package/opencode/abide.mjs +246 -0
- package/package.json +50 -0
- package/skills/abide-compile/SKILL.md +173 -0
- package/src/abide-hook.ts +23 -0
- package/src/bin.ts +64 -0
- package/src/commands/audit.ts +97 -0
- package/src/commands/bench.ts +226 -0
- package/src/commands/calibrate.ts +165 -0
- package/src/commands/check.ts +114 -0
- package/src/commands/compile.ts +127 -0
- package/src/commands/init.ts +109 -0
- package/src/commands/login.ts +61 -0
- package/src/commands/replay.ts +148 -0
- package/src/commands/report.ts +83 -0
- package/src/commands/rubric.ts +45 -0
- package/src/commands/uninstall.ts +33 -0
- package/src/hooks/postToolUse.ts +171 -0
- package/src/hooks/sessionStart.ts +100 -0
- package/src/hooks/stop.ts +275 -0
- package/src/hooks/turnStart.ts +36 -0
- package/src/lib/applyPatch.ts +75 -0
- package/src/lib/audit.ts +265 -0
- package/src/lib/band.ts +49 -0
- package/src/lib/calibration.ts +35 -0
- package/src/lib/checkRunner.ts +124 -0
- package/src/lib/claude.ts +35 -0
- package/src/lib/compilePrompt.ts +67 -0
- package/src/lib/constants.ts +39 -0
- package/src/lib/coverage.ts +21 -0
- package/src/lib/credentials.ts +104 -0
- package/src/lib/diff.ts +209 -0
- package/src/lib/events.ts +34 -0
- package/src/lib/git.ts +206 -0
- package/src/lib/hookRunner.ts +55 -0
- package/src/lib/hosts.ts +112 -0
- package/src/lib/jev.ts +223 -0
- package/src/lib/lintConfig.ts +53 -0
- package/src/lib/loadRules.ts +39 -0
- package/src/lib/opencodePlugin.ts +38 -0
- package/src/lib/output.ts +44 -0
- package/src/lib/packageRoot.ts +48 -0
- package/src/lib/paths.ts +96 -0
- package/src/lib/reason.ts +46 -0
- package/src/lib/regularFile.ts +96 -0
- package/src/lib/replay.ts +314 -0
- package/src/lib/replayCodex.ts +130 -0
- package/src/lib/replayOpencode.ts +186 -0
- package/src/lib/rubricFile.ts +99 -0
- package/src/lib/scope.ts +18 -0
- package/src/lib/session.ts +223 -0
- package/src/lib/settings.ts +112 -0
- package/src/lib/sources.ts +154 -0
- package/src/lib/stdin.ts +23 -0
- package/src/lib/transcript.ts +49 -0
- package/src/lib/ui.ts +38 -0
- package/src/ui/components/Buckets.tsx +30 -0
- package/src/ui/components/Callout.tsx +26 -0
- package/src/ui/components/Checklist.tsx +29 -0
- package/src/ui/components/Columns.tsx +62 -0
- package/src/ui/components/Header.tsx +35 -0
- package/src/ui/components/Live.tsx +73 -0
- package/src/ui/components/RuleTable.tsx +94 -0
- package/src/ui/components/Section.tsx +24 -0
- package/src/ui/components/Verdicts.tsx +40 -0
- package/src/ui/render.tsx +51 -0
- package/src/ui/theme.ts +65 -0
- package/src/ui/views/AuditView.tsx +140 -0
- package/src/ui/views/BenchView.tsx +87 -0
- package/src/ui/views/CalibrateView.tsx +108 -0
- package/src/ui/views/CheckView.tsx +56 -0
- package/src/ui/views/ErrorView.tsx +32 -0
- package/src/ui/views/HelpView.tsx +51 -0
- package/src/ui/views/InitView.tsx +103 -0
- package/src/ui/views/ReplayView.tsx +100 -0
- package/src/ui/views/ReportView.tsx +225 -0
- package/src/ui/views/RubricView.tsx +82 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Rule, RuleStatus } from "@coldtea/abide-schema";
|
|
2
|
+
import { assertNever } from "@coldtea/abide-schema";
|
|
3
|
+
import { Columns, type Cell, type Column, type Row } from "./Columns.js";
|
|
4
|
+
import { glyph, meter, palette, scopeLabel } from "../theme.js";
|
|
5
|
+
|
|
6
|
+
export type RuleStats = { checks: number; fired: number; flagged: number };
|
|
7
|
+
|
|
8
|
+
const statusCell = (status: RuleStatus): Cell => {
|
|
9
|
+
switch (status) {
|
|
10
|
+
case "active":
|
|
11
|
+
return { text: `${glyph.dot} active`, color: palette.sage };
|
|
12
|
+
case "weak":
|
|
13
|
+
return { text: `${glyph.dot} weak`, color: palette.amber, bold: true };
|
|
14
|
+
case "noisy":
|
|
15
|
+
return { text: `${glyph.dot} noisy`, color: palette.rose, bold: true };
|
|
16
|
+
case "disabled":
|
|
17
|
+
return { text: `${glyph.ring} off`, color: palette.ash };
|
|
18
|
+
default:
|
|
19
|
+
return assertNever(status);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const checkCell = (rule: Rule): Cell => {
|
|
24
|
+
switch (rule.check.type) {
|
|
25
|
+
case "lint":
|
|
26
|
+
return { text: "linter", color: palette.sage };
|
|
27
|
+
case "model":
|
|
28
|
+
return { text: `jev ${rule.check.question.type}`, color: palette.indigo };
|
|
29
|
+
case "deferred":
|
|
30
|
+
return { text: "deferred", color: palette.ceramicWarm };
|
|
31
|
+
case "unenforceable":
|
|
32
|
+
return { text: "unenforceable", color: palette.ash };
|
|
33
|
+
default:
|
|
34
|
+
return assertNever(rule.check);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const whenCell = (rule: Rule): Cell => {
|
|
39
|
+
if (rule.check.type === "model")
|
|
40
|
+
return { text: rule.when ?? "", color: rule.when === "turn" ? palette.plum : palette.mist };
|
|
41
|
+
return { text: "", color: palette.ash };
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const calibrationCell = (rule: Rule): Cell => {
|
|
45
|
+
const c = rule.calibration;
|
|
46
|
+
if (!c || c.verdict === "skipped") return { text: c ? "skipped" : "", color: palette.ash };
|
|
47
|
+
const color =
|
|
48
|
+
c.verdict === "decisive" ? palette.mist : c.verdict === "weak" ? palette.amber : palette.rose;
|
|
49
|
+
return { text: `${meter(c.median, 6)} ${c.median.toFixed(2)}`, color };
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
type RuleTableProps = {
|
|
53
|
+
rules: readonly Rule[];
|
|
54
|
+
stats?: ReadonlyMap<string, RuleStats>;
|
|
55
|
+
/** Only rules abide checks: the model ones. */
|
|
56
|
+
runnableOnly?: boolean;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export function RuleTable({ rules, stats, runnableOnly = false }: RuleTableProps) {
|
|
60
|
+
const shown = runnableOnly ? rules.filter((r) => r.check.type === "model") : rules;
|
|
61
|
+
const columns: Column[] = [
|
|
62
|
+
{ key: "id", label: "rule", width: 30 },
|
|
63
|
+
{ key: "check", label: "check", width: 12 },
|
|
64
|
+
{ key: "when", label: "when", width: 4 },
|
|
65
|
+
{ key: "status", label: "status", width: 9 },
|
|
66
|
+
...(stats
|
|
67
|
+
? [{ key: "fired", label: "fired / checks", width: 14, align: "right" as const }]
|
|
68
|
+
: []),
|
|
69
|
+
{ key: "cal", label: "calibration", width: 12 },
|
|
70
|
+
{ key: "scope", label: "scope" },
|
|
71
|
+
];
|
|
72
|
+
const rows: Row[] = shown.map((rule) => {
|
|
73
|
+
const s = stats?.get(rule.id);
|
|
74
|
+
return {
|
|
75
|
+
id: {
|
|
76
|
+
text: rule.id,
|
|
77
|
+
color: rule.status === "active" ? palette.cloud : palette.mist,
|
|
78
|
+
bold: rule.status === "active",
|
|
79
|
+
},
|
|
80
|
+
check: checkCell(rule),
|
|
81
|
+
when: whenCell(rule),
|
|
82
|
+
status: statusCell(rule.status),
|
|
83
|
+
fired: s
|
|
84
|
+
? {
|
|
85
|
+
text: `${s.fired} / ${s.checks}`,
|
|
86
|
+
color: s.fired > 0 ? palette.rose : s.checks > 0 ? palette.mist : palette.ash,
|
|
87
|
+
}
|
|
88
|
+
: { text: "", color: palette.ash },
|
|
89
|
+
cal: calibrationCell(rule),
|
|
90
|
+
scope: { text: scopeLabel(rule.scope), color: rule.scope ? palette.mist : palette.ash },
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
return <Columns columns={columns} rows={rows} indent={2} />;
|
|
94
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import { glyph, palette } from "../theme.js";
|
|
4
|
+
|
|
5
|
+
type SectionProps = { title: string; children: ReactNode; aside?: string };
|
|
6
|
+
|
|
7
|
+
/** A titled block with a thin teal rule down its left, so a long report scans in pieces. */
|
|
8
|
+
export function Section({ title, aside, children }: SectionProps) {
|
|
9
|
+
return (
|
|
10
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
11
|
+
<Box>
|
|
12
|
+
<Text color={palette.teal}>{glyph.bar}</Text>
|
|
13
|
+
<Text color={palette.cloud} bold>
|
|
14
|
+
{" "}
|
|
15
|
+
{title}
|
|
16
|
+
</Text>
|
|
17
|
+
{aside ? <Text color={palette.mist}> {aside}</Text> : null}
|
|
18
|
+
</Box>
|
|
19
|
+
<Box flexDirection="column" paddingLeft={2}>
|
|
20
|
+
{children}
|
|
21
|
+
</Box>
|
|
22
|
+
</Box>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import type { Verdict } from "@coldtea/abide-schema";
|
|
3
|
+
import { glyph, meter, palette } from "../theme.js";
|
|
4
|
+
|
|
5
|
+
const bandColor = (band: Verdict["band"]): string =>
|
|
6
|
+
band === "act" ? palette.rose : band === "flag" ? palette.amber : palette.mist;
|
|
7
|
+
|
|
8
|
+
type VerdictsProps = { verdicts: readonly Verdict[]; all?: boolean };
|
|
9
|
+
|
|
10
|
+
/** Verdicts as meters, loudest first. Clear ones fold into one quiet line unless asked for. */
|
|
11
|
+
export function Verdicts({ verdicts, all = false }: VerdictsProps) {
|
|
12
|
+
const sorted = [...verdicts].sort((a, b) => b.probability - a.probability);
|
|
13
|
+
const shown = all ? sorted : sorted.filter((v) => v.band !== "clear");
|
|
14
|
+
const hidden = sorted.length - shown.length;
|
|
15
|
+
return (
|
|
16
|
+
<Box flexDirection="column">
|
|
17
|
+
{shown.map((v) => (
|
|
18
|
+
<Box key={v.ruleId}>
|
|
19
|
+
<Text color={bandColor(v.band)}>{meter(v.probability)}</Text>
|
|
20
|
+
<Text color={bandColor(v.band)} bold={v.band === "act"}>
|
|
21
|
+
{" "}
|
|
22
|
+
{v.probability.toFixed(2)}
|
|
23
|
+
</Text>
|
|
24
|
+
<Text color={v.band === "clear" ? palette.mist : palette.cloud} bold={v.band === "act"}>
|
|
25
|
+
{" "}
|
|
26
|
+
{v.ruleId}
|
|
27
|
+
</Text>
|
|
28
|
+
{v.answer ? <Text color={palette.ash}> {v.answer}</Text> : null}
|
|
29
|
+
</Box>
|
|
30
|
+
))}
|
|
31
|
+
{hidden > 0 ? (
|
|
32
|
+
<Text color={palette.ash}>
|
|
33
|
+
{glyph.check} {hidden} {hidden === 1 ? "rule" : "rules"} clear
|
|
34
|
+
</Text>
|
|
35
|
+
) : shown.length === 0 ? (
|
|
36
|
+
<Text color={palette.sage}>{glyph.check} nothing to report</Text>
|
|
37
|
+
) : null}
|
|
38
|
+
</Box>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { render } from "ink";
|
|
2
|
+
import type { ReactElement, ReactNode } from "react";
|
|
3
|
+
import { Live, type Progress } from "./components/Live.js";
|
|
4
|
+
import { ErrorView } from "./views/ErrorView.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Draws a finished view once and leaves. Ink prints the final frame on a pipe
|
|
8
|
+
* too, so `abide report | less` still reads well. Nothing in the hooks goes
|
|
9
|
+
* through here: their stdout belongs to the host.
|
|
10
|
+
*/
|
|
11
|
+
export const showStatic = async (element: ReactElement): Promise<void> => {
|
|
12
|
+
const instance = render(element, { patchConsole: false, exitOnCtrlC: true });
|
|
13
|
+
instance.unmount();
|
|
14
|
+
await instance.waitUntilExit();
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type LiveJob<T> = {
|
|
18
|
+
header: ReactNode;
|
|
19
|
+
run: (progress: Progress) => Promise<T>;
|
|
20
|
+
done: (result: T) => ReactNode;
|
|
21
|
+
/** Exit code once the result is drawn. */
|
|
22
|
+
code?: (result: T) => number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Runs a job under a spinner, draws its result, and resolves to the exit code. */
|
|
26
|
+
export const showLive = async <T,>(job: LiveJob<T>): Promise<number> => {
|
|
27
|
+
const outcome: { code: number; result?: { value: T } } = { code: 0 };
|
|
28
|
+
const instance = render(
|
|
29
|
+
<Live<T>
|
|
30
|
+
header={job.header}
|
|
31
|
+
run={job.run}
|
|
32
|
+
done={(result) => {
|
|
33
|
+
outcome.result = { value: result };
|
|
34
|
+
return job.done(result);
|
|
35
|
+
}}
|
|
36
|
+
failed={(error) => <ErrorView error={error} />}
|
|
37
|
+
onExit={(exitCode) => {
|
|
38
|
+
outcome.code = exitCode;
|
|
39
|
+
}}
|
|
40
|
+
animate={Boolean(process.stdout.isTTY)}
|
|
41
|
+
/>,
|
|
42
|
+
{ patchConsole: false, exitOnCtrlC: true },
|
|
43
|
+
);
|
|
44
|
+
await instance.waitUntilExit();
|
|
45
|
+
if (outcome.result !== undefined && job.code) return job.code(outcome.result.value);
|
|
46
|
+
return outcome.code;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const showError = async (error: unknown): Promise<void> => {
|
|
50
|
+
await showStatic(<ErrorView error={error} />);
|
|
51
|
+
};
|
package/src/ui/theme.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** Coldtea's palette, as a terminal can show it. Truecolor where available; chalk degrades the rest. */
|
|
2
|
+
export const palette = {
|
|
3
|
+
teal: "#3da5a8",
|
|
4
|
+
tealLight: "#58c4c8",
|
|
5
|
+
sage: "#5cbd68",
|
|
6
|
+
ceramic: "#d8c4a0",
|
|
7
|
+
ceramicWarm: "#c4a46e",
|
|
8
|
+
amber: "#d49a50",
|
|
9
|
+
amberBright: "#e0ac62",
|
|
10
|
+
rose: "#c9627d",
|
|
11
|
+
plum: "#a468c2",
|
|
12
|
+
indigo: "#6876d8",
|
|
13
|
+
copper: "#b97b47",
|
|
14
|
+
mist: "#8d96a3",
|
|
15
|
+
ash: "#5a6370",
|
|
16
|
+
cloud: "#cdd3dc",
|
|
17
|
+
paper: "#f0ece6",
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
export type Tone = "accent" | "ok" | "warn" | "bad" | "info" | "muted" | "text" | "highlight";
|
|
21
|
+
|
|
22
|
+
export const toneColor: Record<Tone, string> = {
|
|
23
|
+
accent: palette.teal,
|
|
24
|
+
ok: palette.sage,
|
|
25
|
+
warn: palette.amber,
|
|
26
|
+
bad: palette.rose,
|
|
27
|
+
info: palette.indigo,
|
|
28
|
+
muted: palette.mist,
|
|
29
|
+
text: palette.cloud,
|
|
30
|
+
highlight: palette.ceramic,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const glyph = {
|
|
34
|
+
dot: "●",
|
|
35
|
+
ring: "○",
|
|
36
|
+
check: "✓",
|
|
37
|
+
cross: "✗",
|
|
38
|
+
arrow: "→",
|
|
39
|
+
bar: "▎",
|
|
40
|
+
dotSep: "·",
|
|
41
|
+
ellipsis: "…",
|
|
42
|
+
} as const;
|
|
43
|
+
|
|
44
|
+
/** Cuts a string to fit, keeping the tail readable when the head is the boring part. */
|
|
45
|
+
export const truncate = (value: string, max: number, keep: "head" | "tail" = "head"): string => {
|
|
46
|
+
if (max <= 1) return value.slice(0, Math.max(0, max));
|
|
47
|
+
if (value.length <= max) return value;
|
|
48
|
+
return keep === "head"
|
|
49
|
+
? `${value.slice(0, max - 1)}${glyph.ellipsis}`
|
|
50
|
+
: `${glyph.ellipsis}${value.slice(value.length - max + 1)}`;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** A probability as a small bar: filled tenths, in the band's colour. */
|
|
54
|
+
export const meter = (probability: number, width = 10): string => {
|
|
55
|
+
const filled = Math.round(Math.min(1, Math.max(0, probability)) * width);
|
|
56
|
+
return `${"█".repeat(filled)}${"░".repeat(width - filled)}`;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Globs as a person reads them: a list of whole-tree extension globs becomes `ts, tsx`; anything else stays a glob. */
|
|
60
|
+
export const scopeLabel = (scope: readonly string[] | undefined): string => {
|
|
61
|
+
if (scope === undefined) return "everywhere";
|
|
62
|
+
const extensions = scope.map((g) => /^\*\*\/\*\.([A-Za-z0-9]+)$/.exec(g)?.[1]);
|
|
63
|
+
if (extensions.every((e): e is string => e !== undefined)) return extensions.join(", ");
|
|
64
|
+
return scope.join(", ");
|
|
65
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import type { Verdict } from "@coldtea/abide-schema";
|
|
3
|
+
import type { AuditFileResult, RuleTally } from "../../lib/audit.js";
|
|
4
|
+
import { ms, usd } from "../../lib/ui.js";
|
|
5
|
+
import { Callout } from "../components/Callout.js";
|
|
6
|
+
import { Columns } from "../components/Columns.js";
|
|
7
|
+
import { Header } from "../components/Header.js";
|
|
8
|
+
import { Section } from "../components/Section.js";
|
|
9
|
+
import { glyph, meter, palette } from "../theme.js";
|
|
10
|
+
|
|
11
|
+
export type AuditData = {
|
|
12
|
+
root: string;
|
|
13
|
+
files: number;
|
|
14
|
+
skipped: { tooBig: string[]; outOfScope: number };
|
|
15
|
+
results: AuditFileResult[];
|
|
16
|
+
tallies: RuleTally[];
|
|
17
|
+
spendUsd: number;
|
|
18
|
+
elapsedMs: number;
|
|
19
|
+
all: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const worst = (r: AuditFileResult): Verdict | undefined =>
|
|
23
|
+
[...r.verdicts].sort((a, b) => b.probability - a.probability)[0];
|
|
24
|
+
|
|
25
|
+
export function AuditView({ data }: { data: AuditData }) {
|
|
26
|
+
const broken = data.results.filter((r) => r.verdicts.some((v) => v.band === "act"));
|
|
27
|
+
const flaggedOnly = data.results.filter(
|
|
28
|
+
(r) => !r.verdicts.some((v) => v.band === "act") && r.verdicts.some((v) => v.band === "flag"),
|
|
29
|
+
);
|
|
30
|
+
const failed = data.results.filter((r) => r.error !== undefined);
|
|
31
|
+
const rows = data.tallies.filter((t) => data.all || t.broken.length + t.flagged.length > 0);
|
|
32
|
+
const fileRows = [...broken, ...(data.all ? flaggedOnly : [])].sort(
|
|
33
|
+
(a, b) => (worst(b)?.probability ?? 0) - (worst(a)?.probability ?? 0),
|
|
34
|
+
);
|
|
35
|
+
return (
|
|
36
|
+
<Box flexDirection="column">
|
|
37
|
+
<Header
|
|
38
|
+
command="audit"
|
|
39
|
+
where={data.root}
|
|
40
|
+
note={`${data.files} files judged ${glyph.dotSep} ${ms(data.elapsedMs)} ${glyph.dotSep} about ${usd(data.spendUsd)}`}
|
|
41
|
+
/>
|
|
42
|
+
<Section
|
|
43
|
+
title="By rule"
|
|
44
|
+
aside={
|
|
45
|
+
rows.length === 0
|
|
46
|
+
? "nothing broken"
|
|
47
|
+
: `${rows.length} ${rows.length === 1 ? "rule" : "rules"} with something to look at`
|
|
48
|
+
}
|
|
49
|
+
>
|
|
50
|
+
{rows.length === 0 ? (
|
|
51
|
+
<Text color={palette.sage}>{glyph.check} No file breaks a rule above the act line.</Text>
|
|
52
|
+
) : (
|
|
53
|
+
<Columns
|
|
54
|
+
indent={2}
|
|
55
|
+
columns={[
|
|
56
|
+
{ key: "rule", label: "rule", width: 34 },
|
|
57
|
+
{ key: "broken", label: "broken", width: 7, align: "right" },
|
|
58
|
+
{ key: "flagged", label: "uncertain", width: 9, align: "right" },
|
|
59
|
+
{ key: "checked", label: "of", width: 5, align: "right" },
|
|
60
|
+
{ key: "example", label: "for example", keep: "tail" },
|
|
61
|
+
]}
|
|
62
|
+
rows={rows.map((t) => ({
|
|
63
|
+
rule: {
|
|
64
|
+
text: t.ruleId,
|
|
65
|
+
color: t.broken.length > 0 ? palette.cloud : palette.mist,
|
|
66
|
+
bold: t.broken.length > 0,
|
|
67
|
+
},
|
|
68
|
+
broken: {
|
|
69
|
+
text: String(t.broken.length),
|
|
70
|
+
color: t.broken.length > 0 ? palette.rose : palette.ash,
|
|
71
|
+
bold: t.broken.length > 0,
|
|
72
|
+
},
|
|
73
|
+
flagged: {
|
|
74
|
+
text: String(t.flagged.length),
|
|
75
|
+
color: t.flagged.length > 0 ? palette.amber : palette.ash,
|
|
76
|
+
},
|
|
77
|
+
checked: { text: String(t.checked), color: palette.ash },
|
|
78
|
+
example: { text: t.broken[0] ?? t.flagged[0] ?? "", color: palette.mist },
|
|
79
|
+
}))}
|
|
80
|
+
/>
|
|
81
|
+
)}
|
|
82
|
+
</Section>
|
|
83
|
+
{fileRows.length > 0 ? (
|
|
84
|
+
<Section
|
|
85
|
+
title="By file"
|
|
86
|
+
aside={`${broken.length} ${broken.length === 1 ? "file breaks" : "files break"} a rule ${glyph.dotSep} ${flaggedOnly.length} uncertain only`}
|
|
87
|
+
>
|
|
88
|
+
{fileRows.slice(0, data.all ? fileRows.length : 40).map((r) => {
|
|
89
|
+
const top = worst(r);
|
|
90
|
+
const acts = r.verdicts.filter((v) => v.band === "act").map((v) => v.ruleId);
|
|
91
|
+
const flags = r.verdicts.filter((v) => v.band === "flag").map((v) => v.ruleId);
|
|
92
|
+
return (
|
|
93
|
+
<Box key={r.file} flexDirection="column">
|
|
94
|
+
<Box>
|
|
95
|
+
<Text color={acts.length ? palette.rose : palette.amber}>
|
|
96
|
+
{meter(top?.probability ?? 0, 6)}
|
|
97
|
+
</Text>
|
|
98
|
+
<Text color={palette.cloud} bold={acts.length > 0}>
|
|
99
|
+
{" "}
|
|
100
|
+
{r.file}
|
|
101
|
+
</Text>
|
|
102
|
+
</Box>
|
|
103
|
+
<Box paddingLeft={8}>
|
|
104
|
+
<Text color={palette.ash} wrap="truncate-end">
|
|
105
|
+
{acts.length ? acts.join(", ") : ""}
|
|
106
|
+
{acts.length && flags.length ? " " : ""}
|
|
107
|
+
{flags.length ? `uncertain: ${flags.join(", ")}` : ""}
|
|
108
|
+
</Text>
|
|
109
|
+
</Box>
|
|
110
|
+
</Box>
|
|
111
|
+
);
|
|
112
|
+
})}
|
|
113
|
+
{!data.all && fileRows.length > 40 ? (
|
|
114
|
+
<Text color={palette.ash}>{fileRows.length - 40} more. Pass --all to list them.</Text>
|
|
115
|
+
) : null}
|
|
116
|
+
</Section>
|
|
117
|
+
) : null}
|
|
118
|
+
{failed.length > 0 ? (
|
|
119
|
+
<Callout
|
|
120
|
+
tone="warn"
|
|
121
|
+
title={`${failed.length} ${failed.length === 1 ? "file was" : "files were"} judged only in part, or not at all`}
|
|
122
|
+
>
|
|
123
|
+
{failed.slice(0, 5).map((r) => (
|
|
124
|
+
<Text key={r.file} color={palette.mist}>
|
|
125
|
+
{r.file} <Text color={palette.ash}>{r.error}</Text>
|
|
126
|
+
</Text>
|
|
127
|
+
))}
|
|
128
|
+
</Callout>
|
|
129
|
+
) : null}
|
|
130
|
+
{data.skipped.tooBig.length + data.skipped.outOfScope > 0 ? (
|
|
131
|
+
<Text color={palette.ash}>
|
|
132
|
+
Skipped {data.skipped.outOfScope} {data.skipped.outOfScope === 1 ? "file" : "files"} no
|
|
133
|
+
rule applies to
|
|
134
|
+
{data.skipped.tooBig.length ? ` and ${data.skipped.tooBig.length} too big to send` : ""}.
|
|
135
|
+
Turn-phase rules do not apply to an audit.
|
|
136
|
+
</Text>
|
|
137
|
+
) : null}
|
|
138
|
+
</Box>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import { Columns } from "../components/Columns.js";
|
|
3
|
+
import { Header } from "../components/Header.js";
|
|
4
|
+
import { Section } from "../components/Section.js";
|
|
5
|
+
import { ms, usd } from "../../lib/ui.js";
|
|
6
|
+
import { glyph, palette } from "../theme.js";
|
|
7
|
+
|
|
8
|
+
export type BenchRow = { what: string; median: number; p90: number };
|
|
9
|
+
export type BenchData = {
|
|
10
|
+
root: string;
|
|
11
|
+
runs: number;
|
|
12
|
+
activeModelRules: number;
|
|
13
|
+
rows: BenchRow[];
|
|
14
|
+
tokens: { small: number; large: number };
|
|
15
|
+
cost: { small: number; large: number };
|
|
16
|
+
perTurn: { latencyMs: number; costUsd: number };
|
|
17
|
+
sessions: {
|
|
18
|
+
count: number;
|
|
19
|
+
medianChecks: number;
|
|
20
|
+
medianLatencyMs: number;
|
|
21
|
+
medianCostUsd: number;
|
|
22
|
+
} | null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function BenchView({ data }: { data: BenchData }) {
|
|
26
|
+
return (
|
|
27
|
+
<Box flexDirection="column">
|
|
28
|
+
<Header
|
|
29
|
+
command="bench"
|
|
30
|
+
where={data.root}
|
|
31
|
+
note={`${data.activeModelRules} active model rules ${glyph.dotSep} ${data.runs} runs each`}
|
|
32
|
+
/>
|
|
33
|
+
<Section title="Latency">
|
|
34
|
+
<Columns
|
|
35
|
+
indent={2}
|
|
36
|
+
columns={[
|
|
37
|
+
{ key: "what", label: "" },
|
|
38
|
+
{ key: "median", label: "median", width: 8, align: "right" },
|
|
39
|
+
{ key: "p90", label: "p90", width: 8, align: "right" },
|
|
40
|
+
]}
|
|
41
|
+
rows={data.rows.map((r) => ({
|
|
42
|
+
what: { text: r.what, color: palette.cloud },
|
|
43
|
+
median: { text: ms(r.median), color: palette.ceramic, bold: true },
|
|
44
|
+
p90: { text: ms(r.p90), color: palette.mist },
|
|
45
|
+
}))}
|
|
46
|
+
/>
|
|
47
|
+
</Section>
|
|
48
|
+
<Section title="Spend">
|
|
49
|
+
<Text color={palette.cloud}>
|
|
50
|
+
input tokens per edit <Text color={palette.ceramic}>{data.tokens.small}</Text> small{" "}
|
|
51
|
+
<Text color={palette.ash}>{glyph.dotSep}</Text>{" "}
|
|
52
|
+
<Text color={palette.ceramic}>{data.tokens.large}</Text> large
|
|
53
|
+
</Text>
|
|
54
|
+
<Text color={palette.cloud}>
|
|
55
|
+
per edit <Text color={palette.ceramic}>{usd(data.cost.small)}</Text> small{" "}
|
|
56
|
+
<Text color={palette.ash}>{glyph.dotSep}</Text>{" "}
|
|
57
|
+
<Text color={palette.ceramic}>{usd(data.cost.large)}</Text> large
|
|
58
|
+
</Text>
|
|
59
|
+
<Box marginTop={1}>
|
|
60
|
+
<Text color={palette.cloud}>
|
|
61
|
+
A 15 edit turn here: about{" "}
|
|
62
|
+
<Text color={palette.ceramic}>{ms(data.perTurn.latencyMs)}</Text> of waiting and{" "}
|
|
63
|
+
<Text color={palette.ceramic}>{usd(data.perTurn.costUsd)}</Text>, before repairs.
|
|
64
|
+
</Text>
|
|
65
|
+
</Box>
|
|
66
|
+
</Section>
|
|
67
|
+
<Section
|
|
68
|
+
title="Real sessions"
|
|
69
|
+
aside={data.sessions ? `${data.sessions.count} recorded` : "none yet"}
|
|
70
|
+
>
|
|
71
|
+
{data.sessions ? (
|
|
72
|
+
<Text color={palette.cloud}>
|
|
73
|
+
median per session <Text color={palette.ceramic}>{data.sessions.medianChecks}</Text>{" "}
|
|
74
|
+
checks <Text color={palette.ash}>{glyph.dotSep}</Text>{" "}
|
|
75
|
+
<Text color={palette.ceramic}>{ms(data.sessions.medianLatencyMs)}</Text> of hook time{" "}
|
|
76
|
+
<Text color={palette.ash}>{glyph.dotSep}</Text>{" "}
|
|
77
|
+
<Text color={palette.ceramic}>{usd(data.sessions.medianCostUsd)}</Text>
|
|
78
|
+
</Text>
|
|
79
|
+
) : (
|
|
80
|
+
<Text color={palette.ash}>
|
|
81
|
+
The figures above are measured; per-session numbers appear once the hooks have run here.
|
|
82
|
+
</Text>
|
|
83
|
+
)}
|
|
84
|
+
</Section>
|
|
85
|
+
</Box>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import type { CalibrationVerdict } from "@coldtea/abide-schema";
|
|
3
|
+
import { Callout } from "../components/Callout.js";
|
|
4
|
+
import { Columns } from "../components/Columns.js";
|
|
5
|
+
import { Header } from "../components/Header.js";
|
|
6
|
+
import { Section } from "../components/Section.js";
|
|
7
|
+
import { usd } from "../../lib/ui.js";
|
|
8
|
+
import { glyph, meter, palette } from "../theme.js";
|
|
9
|
+
|
|
10
|
+
export type CalibrateRow = {
|
|
11
|
+
id: string;
|
|
12
|
+
when: string;
|
|
13
|
+
states: number;
|
|
14
|
+
median: number;
|
|
15
|
+
min: number;
|
|
16
|
+
max: number;
|
|
17
|
+
fired: number;
|
|
18
|
+
verdict: CalibrationVerdict;
|
|
19
|
+
};
|
|
20
|
+
export type CalibrateData = {
|
|
21
|
+
root: string;
|
|
22
|
+
file: string;
|
|
23
|
+
hunks: number;
|
|
24
|
+
commits: number;
|
|
25
|
+
calls: number;
|
|
26
|
+
spendUsd: number;
|
|
27
|
+
rows: CalibrateRow[];
|
|
28
|
+
weak: string[];
|
|
29
|
+
noisy: string[];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const verdictColor = (v: CalibrationVerdict): string =>
|
|
33
|
+
v === "decisive"
|
|
34
|
+
? palette.sage
|
|
35
|
+
: v === "weak"
|
|
36
|
+
? palette.amber
|
|
37
|
+
: v === "noisy"
|
|
38
|
+
? palette.rose
|
|
39
|
+
: palette.ash;
|
|
40
|
+
|
|
41
|
+
export function CalibrateView({ data }: { data: CalibrateData }) {
|
|
42
|
+
return (
|
|
43
|
+
<Box flexDirection="column">
|
|
44
|
+
<Header
|
|
45
|
+
command="calibrate"
|
|
46
|
+
where={data.root}
|
|
47
|
+
note={`${data.rows.length} rules ${glyph.dotSep} ${data.hunks} hunks and ${data.commits} commits from git history`}
|
|
48
|
+
/>
|
|
49
|
+
<Section
|
|
50
|
+
title="Rules"
|
|
51
|
+
aside={`${data.calls} calls ${glyph.dotSep} about ${usd(data.spendUsd)}`}
|
|
52
|
+
>
|
|
53
|
+
<Columns
|
|
54
|
+
indent={2}
|
|
55
|
+
columns={[
|
|
56
|
+
{ key: "id", label: "rule", width: 34 },
|
|
57
|
+
{ key: "when", label: "when", width: 4 },
|
|
58
|
+
{ key: "states", label: "states", width: 6, align: "right" },
|
|
59
|
+
{ key: "median", label: "median", width: 12 },
|
|
60
|
+
{ key: "range", label: "min max", width: 11 },
|
|
61
|
+
{ key: "fired", label: "fired", width: 5, align: "right" },
|
|
62
|
+
{ key: "verdict", label: "verdict" },
|
|
63
|
+
]}
|
|
64
|
+
rows={data.rows.map((r) => ({
|
|
65
|
+
id: { text: r.id, color: palette.cloud },
|
|
66
|
+
when: { text: r.when, color: r.when === "turn" ? palette.plum : palette.mist },
|
|
67
|
+
states: { text: String(r.states), color: palette.mist },
|
|
68
|
+
median: {
|
|
69
|
+
text: `${meter(r.median, 6)} ${r.median.toFixed(2)}`,
|
|
70
|
+
color: verdictColor(r.verdict),
|
|
71
|
+
},
|
|
72
|
+
range: { text: `${r.min.toFixed(2)} ${r.max.toFixed(2)}`, color: palette.ash },
|
|
73
|
+
fired: { text: String(r.fired), color: r.fired > 0 ? palette.rose : palette.ash },
|
|
74
|
+
verdict: {
|
|
75
|
+
text: r.verdict,
|
|
76
|
+
color: verdictColor(r.verdict),
|
|
77
|
+
bold: r.verdict !== "decisive" && r.verdict !== "skipped",
|
|
78
|
+
},
|
|
79
|
+
}))}
|
|
80
|
+
/>
|
|
81
|
+
</Section>
|
|
82
|
+
{data.weak.length > 0 ? (
|
|
83
|
+
<Callout
|
|
84
|
+
tone="warn"
|
|
85
|
+
title={`${data.weak.length} ${data.weak.length === 1 ? "rule" : "rules"} never answered near 0 or near 1 and will not run`}
|
|
86
|
+
>
|
|
87
|
+
<Text color={palette.cloud}>{data.weak.join(", ")}</Text>
|
|
88
|
+
<Text color={palette.mist}>Likely badly worded. Run abide tune to rewrite them.</Text>
|
|
89
|
+
</Callout>
|
|
90
|
+
) : null}
|
|
91
|
+
{data.noisy.length > 0 ? (
|
|
92
|
+
<Callout
|
|
93
|
+
tone="bad"
|
|
94
|
+
title={`${data.noisy.length} ${data.noisy.length === 1 ? "rule fired" : "rules fired"} on most historic hunks and will not run`}
|
|
95
|
+
>
|
|
96
|
+
<Text color={palette.cloud}>{data.noisy.join(", ")}</Text>
|
|
97
|
+
<Text color={palette.mist}>Too broad. Narrow the scope or the wording.</Text>
|
|
98
|
+
</Callout>
|
|
99
|
+
) : null}
|
|
100
|
+
{data.weak.length + data.noisy.length === 0 ? (
|
|
101
|
+
<Text color={palette.sage}>
|
|
102
|
+
{glyph.check} Every rule is decisive on this repository's history.
|
|
103
|
+
</Text>
|
|
104
|
+
) : null}
|
|
105
|
+
<Text color={palette.ash}>wrote {data.file}</Text>
|
|
106
|
+
</Box>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import type { Verdict } from "@coldtea/abide-schema";
|
|
3
|
+
import { Header } from "../components/Header.js";
|
|
4
|
+
import { Section } from "../components/Section.js";
|
|
5
|
+
import { Verdicts } from "../components/Verdicts.js";
|
|
6
|
+
import { ms, usd } from "../../lib/ui.js";
|
|
7
|
+
import { glyph, palette } from "../theme.js";
|
|
8
|
+
|
|
9
|
+
export type CheckSection = {
|
|
10
|
+
phase: "edit" | "turn";
|
|
11
|
+
files: string[];
|
|
12
|
+
modelRules: number;
|
|
13
|
+
calls: number;
|
|
14
|
+
latencyMs: number;
|
|
15
|
+
verdicts: Verdict[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type CheckData = { root: string; sections: CheckSection[]; spendUsd: number; all: boolean };
|
|
19
|
+
|
|
20
|
+
export function CheckView({ data }: { data: CheckData }) {
|
|
21
|
+
const acts = data.sections.flatMap((s) => s.verdicts).filter((v) => v.band === "act").length;
|
|
22
|
+
const flags = data.sections.flatMap((s) => s.verdicts).filter((v) => v.band === "flag").length;
|
|
23
|
+
return (
|
|
24
|
+
<Box flexDirection="column">
|
|
25
|
+
<Header command="check" where={data.root} />
|
|
26
|
+
{data.sections.map((s, i) => (
|
|
27
|
+
<Section
|
|
28
|
+
key={i}
|
|
29
|
+
title={
|
|
30
|
+
s.phase === "edit"
|
|
31
|
+
? (s.files[0] ?? "")
|
|
32
|
+
: `turn ${glyph.dotSep} ${s.files.length} ${s.files.length === 1 ? "file" : "files"}`
|
|
33
|
+
}
|
|
34
|
+
aside={`${s.modelRules} rules ${glyph.dotSep} ${s.calls} ${s.calls === 1 ? "call" : "calls"} ${glyph.dotSep} ${ms(s.latencyMs)}`}
|
|
35
|
+
>
|
|
36
|
+
<Verdicts verdicts={s.verdicts} all={data.all} />
|
|
37
|
+
</Section>
|
|
38
|
+
))}
|
|
39
|
+
<Box>
|
|
40
|
+
<Text color={acts > 0 ? palette.rose : palette.sage} bold>
|
|
41
|
+
{acts > 0 ? `${glyph.cross} ${acts} to repair` : `${glyph.check} nothing to repair`}
|
|
42
|
+
</Text>
|
|
43
|
+
{flags > 0 ? (
|
|
44
|
+
<Text color={palette.amber}>
|
|
45
|
+
{" "}
|
|
46
|
+
{glyph.dot} {flags} uncertain
|
|
47
|
+
</Text>
|
|
48
|
+
) : null}
|
|
49
|
+
<Text color={palette.ash}>
|
|
50
|
+
{" "}
|
|
51
|
+
{glyph.dotSep} about {usd(data.spendUsd)}
|
|
52
|
+
</Text>
|
|
53
|
+
</Box>
|
|
54
|
+
</Box>
|
|
55
|
+
);
|
|
56
|
+
}
|