@chrysb/alphaclaw 0.4.5 → 0.4.6-beta.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/README.md +21 -18
- package/lib/public/css/theme.css +29 -0
- package/lib/public/js/app.js +41 -2
- package/lib/public/js/components/badge.js +4 -0
- package/lib/public/js/components/doctor/findings-list.js +195 -0
- package/lib/public/js/components/doctor/fix-card-modal.js +186 -0
- package/lib/public/js/components/doctor/general-warning.js +37 -0
- package/lib/public/js/components/doctor/helpers.js +144 -0
- package/lib/public/js/components/doctor/index.js +538 -0
- package/lib/public/js/components/doctor/summary-cards.js +24 -0
- package/lib/public/js/lib/api.js +81 -0
- package/lib/server/commands.js +8 -4
- package/lib/server/db/doctor/index.js +529 -0
- package/lib/server/db/doctor/schema.js +69 -0
- package/lib/server/doctor/constants.js +43 -0
- package/lib/server/doctor/normalize.js +214 -0
- package/lib/server/doctor/prompt.js +89 -0
- package/lib/server/doctor/service.js +393 -0
- package/lib/server/doctor/workspace-fingerprint.js +126 -0
- package/lib/server/routes/doctor.js +124 -0
- package/lib/server/routes/system.js +21 -1
- package/lib/server/utils/json.js +56 -10
- package/lib/server.js +44 -0
- package/package.json +1 -1
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { h } from "https://esm.sh/preact";
|
|
2
|
+
import htm from "https://esm.sh/htm";
|
|
3
|
+
import { ActionButton } from "../action-button.js";
|
|
4
|
+
import { getDoctorWarningMessage, shouldShowDoctorWarning } from "./helpers.js";
|
|
5
|
+
|
|
6
|
+
const html = htm.bind(h);
|
|
7
|
+
|
|
8
|
+
export const GeneralDoctorWarning = ({
|
|
9
|
+
doctorStatus = null,
|
|
10
|
+
dismissedUntilMs = 0,
|
|
11
|
+
onOpenDoctor = () => {},
|
|
12
|
+
onDismiss = () => {},
|
|
13
|
+
}) => {
|
|
14
|
+
if (!shouldShowDoctorWarning(doctorStatus, dismissedUntilMs)) return null;
|
|
15
|
+
return html`
|
|
16
|
+
<div class="bg-yellow-500/10 border border-yellow-500/35 rounded-xl p-4">
|
|
17
|
+
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
|
18
|
+
<div class="space-y-1">
|
|
19
|
+
<h2 class="font-semibold text-sm text-yellow-300">Drift Doctor</h2>
|
|
20
|
+
<p class="text-xs text-yellow-100/80">${getDoctorWarningMessage(doctorStatus)}</p>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="flex flex-wrap gap-2">
|
|
23
|
+
<${ActionButton}
|
|
24
|
+
onClick=${onDismiss}
|
|
25
|
+
tone="secondary"
|
|
26
|
+
idleLabel="Dismiss for 1 week"
|
|
27
|
+
/>
|
|
28
|
+
<${ActionButton}
|
|
29
|
+
onClick=${onOpenDoctor}
|
|
30
|
+
tone="warning"
|
|
31
|
+
idleLabel="Open Drift Doctor"
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
`;
|
|
37
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
export const getDoctorPriorityTone = (priority = "") => {
|
|
2
|
+
const normalized = String(priority || "").trim().toUpperCase();
|
|
3
|
+
if (normalized === "P0") return "danger";
|
|
4
|
+
if (normalized === "P1") return "warning";
|
|
5
|
+
return "neutral";
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const getDoctorStatusTone = (status = "") => {
|
|
9
|
+
const normalized = String(status || "").trim().toLowerCase();
|
|
10
|
+
if (normalized === "fixed") return "success";
|
|
11
|
+
if (normalized === "dismissed") return "neutral";
|
|
12
|
+
return "warning";
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const getDoctorCategoryTone = (category = "") => {
|
|
16
|
+
const normalized = String(category || "")
|
|
17
|
+
.trim()
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.replace(/[_-]+/g, " ");
|
|
20
|
+
if (normalized === "token efficiency") return "info";
|
|
21
|
+
if (normalized === "redundancy") return "accent";
|
|
22
|
+
if (normalized === "mixed concerns") return "cyan";
|
|
23
|
+
if (normalized === "workspace state") return "secondary";
|
|
24
|
+
return "info";
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const formatDoctorCategory = (category = "") => {
|
|
28
|
+
const normalized = String(category || "")
|
|
29
|
+
.trim()
|
|
30
|
+
.replace(/[_-]+/g, " ");
|
|
31
|
+
if (!normalized) return "Workspace";
|
|
32
|
+
return normalized.replace(/\b\w/g, (character) => character.toUpperCase());
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const buildDoctorPriorityCounts = (cards = []) =>
|
|
36
|
+
cards.reduce(
|
|
37
|
+
(totals, card) => {
|
|
38
|
+
const priority = String(card?.priority || "").trim().toUpperCase();
|
|
39
|
+
if (priority === "P0" || priority === "P1" || priority === "P2") {
|
|
40
|
+
totals[priority] += 1;
|
|
41
|
+
}
|
|
42
|
+
return totals;
|
|
43
|
+
},
|
|
44
|
+
{ P0: 0, P1: 0, P2: 0 },
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
export const groupDoctorCardsByStatus = (cards = []) =>
|
|
48
|
+
cards.reduce(
|
|
49
|
+
(groups, card) => {
|
|
50
|
+
const status = String(card?.status || "open").trim().toLowerCase();
|
|
51
|
+
if (status === "fixed") {
|
|
52
|
+
groups.fixed.push(card);
|
|
53
|
+
return groups;
|
|
54
|
+
}
|
|
55
|
+
if (status === "dismissed") {
|
|
56
|
+
groups.dismissed.push(card);
|
|
57
|
+
return groups;
|
|
58
|
+
}
|
|
59
|
+
groups.open.push(card);
|
|
60
|
+
return groups;
|
|
61
|
+
},
|
|
62
|
+
{ open: [], dismissed: [], fixed: [] },
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
export const shouldShowDoctorWarning = (
|
|
66
|
+
doctorStatus = null,
|
|
67
|
+
dismissedUntilMs = 0,
|
|
68
|
+
) => {
|
|
69
|
+
if (!doctorStatus || doctorStatus.runInProgress) return false;
|
|
70
|
+
if (doctorStatus.needsInitialRun || !doctorStatus.stale) return false;
|
|
71
|
+
if (!doctorStatus.changeSummary?.hasMeaningfulChanges) return false;
|
|
72
|
+
return Number(dismissedUntilMs || 0) <= Date.now();
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const getDoctorWarningMessage = (doctorStatus = null) => {
|
|
76
|
+
if (!doctorStatus) return "";
|
|
77
|
+
const changedFilesCount = Number(doctorStatus.changeSummary?.changedFilesCount || 0);
|
|
78
|
+
if (changedFilesCount > 0) {
|
|
79
|
+
return `Drift Doctor has not been run in the last week and ${changedFilesCount} file${changedFilesCount === 1 ? "" : "s"} changed since the last review.`;
|
|
80
|
+
}
|
|
81
|
+
return "Doctor has not been run in the last week.";
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const getDoctorChangeLabel = (changeSummary = null) => {
|
|
85
|
+
const changedFilesCount = Number(changeSummary?.changedFilesCount || 0);
|
|
86
|
+
const hasMeaningfulChanges = !!changeSummary?.hasMeaningfulChanges;
|
|
87
|
+
if (changedFilesCount === 0) {
|
|
88
|
+
return { text: "No changes since last run", meaningful: false };
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
text: `${changedFilesCount} change${changedFilesCount === 1 ? "" : "s"} since last run`,
|
|
92
|
+
meaningful: hasMeaningfulChanges,
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const getDoctorRunPillDetail = (run = null) => {
|
|
97
|
+
if (!run || typeof run !== "object") return "";
|
|
98
|
+
if (run.status === "running") return "Running";
|
|
99
|
+
if (run.status === "failed") return "Failed";
|
|
100
|
+
if ((run.cardCount || 0) === 0) return "No findings";
|
|
101
|
+
return `${run.cardCount || 0} finding${run.cardCount === 1 ? "" : "s"}`;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const buildDoctorRunMarkers = (run = null) => {
|
|
105
|
+
if (!run || typeof run !== "object") return [];
|
|
106
|
+
if (run.status === "running") {
|
|
107
|
+
return [{ tone: "cyan", count: 0, label: "Running" }];
|
|
108
|
+
}
|
|
109
|
+
if (run.status === "failed") {
|
|
110
|
+
return [{ tone: "neutral", count: 0, label: "Failed" }];
|
|
111
|
+
}
|
|
112
|
+
if ((run.cardCount || 0) === 0) {
|
|
113
|
+
return [{ tone: "success", count: 0, label: "No findings" }];
|
|
114
|
+
}
|
|
115
|
+
const markers = [];
|
|
116
|
+
if (Number(run?.priorityCounts?.P0 || 0) > 0) {
|
|
117
|
+
markers.push({
|
|
118
|
+
tone: "danger",
|
|
119
|
+
count: Number(run.priorityCounts.P0 || 0),
|
|
120
|
+
label: "P0",
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (Number(run?.priorityCounts?.P1 || 0) > 0) {
|
|
124
|
+
markers.push({
|
|
125
|
+
tone: "warning",
|
|
126
|
+
count: Number(run.priorityCounts.P1 || 0),
|
|
127
|
+
label: "P1",
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (Number(run?.priorityCounts?.P2 || 0) > 0) {
|
|
131
|
+
markers.push({
|
|
132
|
+
tone: "neutral",
|
|
133
|
+
count: Number(run.priorityCounts.P2 || 0),
|
|
134
|
+
label: "P2",
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return markers;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export const buildDoctorStatusFilterOptions = () => [
|
|
141
|
+
{ value: "open", label: "Open" },
|
|
142
|
+
{ value: "dismissed", label: "Dismissed" },
|
|
143
|
+
{ value: "fixed", label: "Fixed" },
|
|
144
|
+
];
|