feat: add prek pre-commit hooks and dependabot (#1720)

* feat: add prek pre-commit hooks and dependabot

Pre-commit hooks (via prek):
- Basic hygiene: trailing-whitespace, end-of-file-fixer, check-yaml, check-added-large-files, check-merge-conflict
- Security: detect-secrets, zizmor (GitHub Actions audit)
- Linting: shellcheck, actionlint, oxlint, swiftlint
- Formatting: oxfmt, swiftformat

Dependabot:
- npm and GitHub Actions ecosystems
- Grouped updates (production/development/actions)
- 7-day cooldown for supply chain protection

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs: add prek install instruction to AGENTS.md

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Dan Guido
2026-01-25 05:53:23 -05:00
committed by GitHub
parent 612a27f3dd
commit 48aea87028
69 changed files with 2143 additions and 214 deletions

View File

@@ -1,3 +1,2 @@
import "./styles.css";
import "./ui/app.ts";

View File

@@ -279,4 +279,3 @@
min-width: 120px;
}
}

View File

@@ -122,4 +122,3 @@
border-top: 1px solid var(--border);
margin: 1em 0;
}

View File

@@ -196,4 +196,3 @@
transform: scale(1);
}
}

View File

@@ -3,4 +3,3 @@ export type EventLogEntry = {
event: string;
payload?: unknown;
};

View File

@@ -154,13 +154,13 @@ const COMPACTION_TOAST_DURATION_MS = 5000;
export function handleCompactionEvent(host: CompactionHost, payload: AgentEventPayload) {
const data = payload.data ?? {};
const phase = typeof data.phase === "string" ? data.phase : "";
// Clear any existing timer
if (host.compactionClearTimer != null) {
window.clearTimeout(host.compactionClearTimer);
host.compactionClearTimer = null;
}
if (phase === "start") {
host.compactionStatus = {
active: true,
@@ -183,13 +183,13 @@ export function handleCompactionEvent(host: CompactionHost, payload: AgentEventP
export function handleAgentEvent(host: ToolStreamHost, payload?: AgentEventPayload) {
if (!payload) return;
// Handle compaction events
if (payload.stream === "compaction") {
handleCompactionEvent(host as CompactionHost, payload);
return;
}
if (payload.stream !== "tool") return;
const sessionKey =
typeof payload.sessionKey === "string" ? payload.sessionKey : undefined;

View File

@@ -74,4 +74,3 @@ export function removePathValue(
delete (current as Record<string, unknown>)[lastKey];
}
}

View File

@@ -54,4 +54,3 @@ export async function callDebugMethod(state: DebugState) {
state.debugCallError = String(err);
}
}

View File

@@ -33,4 +33,3 @@ export async function loadPresence(state: PresenceState) {
state.presenceLoading = false;
}
}

View File

@@ -39,4 +39,3 @@ describe("stripThinkingTags", () => {
expect(stripThinkingTags("Hello</final>")).toBe("Hello");
});
});

View File

@@ -30,4 +30,3 @@ describe("toSanitizedMarkdownHtml", () => {
expect(html).toContain("console.log(1)");
});
});

View File

@@ -55,4 +55,3 @@ export function formatCronPayload(job: CronJob) {
if (p.kind === "systemEvent") return `System: ${p.text}`;
return `Agent: ${p.message}`;
}

View File

@@ -30,4 +30,3 @@ describe("generateUUID", () => {
expect(id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
});
});

View File

@@ -40,4 +40,3 @@ export function generateUUID(cryptoLike: CryptoLike | null = globalThis.crypto):
return uuidFromBytes(weakRandomBytes());
}

View File

@@ -43,4 +43,3 @@ export function renderChannelAccountCount(
if (count < 2) return nothing;
return html`<div class="account-count">Accounts (${count})</div>`;
}

View File

@@ -116,4 +116,3 @@ export function renderWhatsAppCard(params: {
</div>
`;
}

View File

@@ -70,7 +70,7 @@ const COMPACTION_TOAST_DURATION_MS = 5000;
function renderCompactionIndicator(status: CompactionIndicatorStatus | null | undefined) {
if (!status) return nothing;
// Show "compacting..." while active
if (status.active) {
return html`
@@ -91,7 +91,7 @@ function renderCompactionIndicator(status: CompactionIndicatorStatus | null | un
`;
}
}
return nothing;
}

View File

@@ -120,7 +120,7 @@ export function renderNode(params: {
const hasString = normalizedTypes.has("string");
const hasNumber = normalizedTypes.has("number");
const hasBoolean = normalizedTypes.has("boolean");
if (hasBoolean && normalizedTypes.size === 1) {
return renderNode({
...params,
@@ -383,14 +383,14 @@ function renderObject(params: {
const hint = hintForPath(path, hints);
const label = hint?.label ?? schema.title ?? humanize(String(path.at(-1)));
const help = hint?.help ?? schema.description;
const fallback = value ?? schema.default;
const obj = fallback && typeof fallback === "object" && !Array.isArray(fallback)
? (fallback as Record<string, unknown>)
: {};
const props = schema.properties ?? {};
const entries = Object.entries(props);
// Sort by hint order
const sorted = entries.sort((a, b) => {
const orderA = hintForPath([...path, a[0]], hints)?.order ?? 0;
@@ -514,7 +514,7 @@ function renderArray(params: {
</button>
</div>
${help ? html`<div class="cfg-array__help">${help}</div>` : nothing}
${arr.length === 0 ? html`
<div class="cfg-array__empty">
No items yet. Click "Add" to create one.
@@ -597,7 +597,7 @@ function renderMapField(params: {
Add Entry
</button>
</div>
${entries.length === 0 ? html`
<div class="cfg-map__empty">No custom entries.</div>
` : html`

View File

@@ -94,16 +94,16 @@ function matchesSearch(key: string, schema: JsonSchema, query: string): boolean
if (!query) return true;
const q = query.toLowerCase();
const meta = SECTION_META[key];
// Check key name
if (key.toLowerCase().includes(q)) return true;
// Check label and description
if (meta) {
if (meta.label.toLowerCase().includes(q)) return true;
if (meta.description.toLowerCase().includes(q)) return true;
}
return schemaMatches(schema, q);
}
@@ -192,8 +192,8 @@ export function renderConfigForm(props: ConfigFormProps) {
<div class="config-empty">
<div class="config-empty__icon">${icons.search}</div>
<div class="config-empty__text">
${searchQuery
? `No settings match "${searchQuery}"`
${searchQuery
? `No settings match "${searchQuery}"`
: "No settings in this section"}
</div>
</div>

View File

@@ -89,4 +89,3 @@ export function isSensitivePath(path: Array<string | number>): boolean {
key.endsWith("key")
);
}

View File

@@ -5,4 +5,3 @@ export {
} from "./config-form.analyze";
export { renderNode } from "./config-form.node";
export { schemaType, type JsonSchema } from "./config-form.shared";

View File

@@ -138,7 +138,7 @@ function computeDiff(
): Array<{ path: string; from: unknown; to: unknown }> {
if (!original || !current) return [];
const changes: Array<{ path: string; from: unknown; to: unknown }> = [];
function compare(orig: unknown, curr: unknown, path: string) {
if (orig === curr) return;
if (typeof orig !== typeof curr) {
@@ -164,7 +164,7 @@ function computeDiff(
compare(origObj[key], currObj[key], path ? `${path}.${key}` : key);
}
}
compare(original, current, "");
return changes;
}
@@ -258,7 +258,7 @@ export function renderConfig(props: ConfigProps) {
<div class="config-sidebar__title">Settings</div>
<span class="pill pill--sm ${validity === "valid" ? "pill--ok" : validity === "invalid" ? "pill--danger" : ""}">${validity}</span>
</div>
<!-- Search -->
<div class="config-search">
<svg class="config-search__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -273,13 +273,13 @@ export function renderConfig(props: ConfigProps) {
@input=${(e: Event) => props.onSearchChange((e.target as HTMLInputElement).value)}
/>
${props.searchQuery ? html`
<button
<button
class="config-search__clear"
@click=${() => props.onSearchChange("")}
>×</button>
` : nothing}
</div>
<!-- Section nav -->
<nav class="config-nav">
<button
@@ -299,7 +299,7 @@ export function renderConfig(props: ConfigProps) {
</button>
`)}
</nav>
<!-- Mode toggle at bottom -->
<div class="config-sidebar__footer">
<div class="config-mode-toggle">
@@ -319,7 +319,7 @@ export function renderConfig(props: ConfigProps) {
</div>
</div>
</aside>
<!-- Main content -->
<main class="config-main">
<!-- Action bar -->
@@ -358,7 +358,7 @@ export function renderConfig(props: ConfigProps) {
</button>
</div>
</div>
<!-- Diff panel (form mode only - raw mode doesn't have granular diff) -->
${hasChanges && props.formMode === "form" ? html`
<details class="config-diff">