From 5430ebd1cc96302884b5293ea67207af71370a18 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Wed, 17 Jun 2026 14:28:55 -0700 Subject: [PATCH] feat(agent): autoMaintain config block + dashboard (#774) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds on #773: the per-repo auto-maintain policy + a dashboard surface. - AutoMaintainPolicy { requireApprovals, mergeMethod } + normalizeAuto- MaintainPolicy (conservative defaults squash/1; requireApprovals clamped to [0,10]; invalid merge method -> squash). Pure + 100% covered. - Persisted on repository_settings as JSON (migration 0043, default '{}' -> defaults), mirroring autonomy/commandAuthorization. - Config-as-code: .gittensory.yml settings.autoMaintain (+ settings. autonomy from #773) resolved through the existing yml > DB > defaults resolver; a non-mapping block is ignored, never blanking the DB policy. - Dashboard: a new 'Auto-maintain (agent layer)' section in the maintainer settings editor (#130) — per-action autonomy selectors for all six action classes + approvals-before-auto-merge + merge method. - Maintainer PUT /settings now accepts autonomy + autoMaintain (bounded: requireApprovals 0..10; unknown autonomy action classes dropped by the DB normalizer; out-of-range approvals rejected at the boundary). NOTE: migration 0043 follows #773's 0042 — both ahead of the open #833; whichever merges into a 0042/0043 collision rebases + renumbers. The action layer that consults this (resolveAutonomy + the policy) is #778. --- apps/gittensory-ui/public/openapi.json | 20 ++++ .../site/app-panels/maintainer-settings.tsx | 108 +++++++++++++++++- migrations/0043_agent_auto_maintain.sql | 4 + src/api/routes.ts | 4 + src/db/repositories.ts | 12 +- src/db/schema.ts | 1 + src/openapi/schemas.ts | 1 + src/settings/autonomy.ts | 29 ++++- src/signals/focus-manifest.ts | 8 +- src/types.ts | 14 +++ test/integration/api.test.ts | 22 +++- test/unit/autonomy.test.ts | 27 +++++ test/unit/data-spine.test.ts | 6 + test/unit/focus-manifest.test.ts | 10 ++ 14 files changed, 260 insertions(+), 6 deletions(-) create mode 100644 migrations/0043_agent_auto_maintain.sql diff --git a/apps/gittensory-ui/public/openapi.json b/apps/gittensory-ui/public/openapi.json index 5723f6d736..537eddf78a 100644 --- a/apps/gittensory-ui/public/openapi.json +++ b/apps/gittensory-ui/public/openapi.json @@ -8090,6 +8090,26 @@ ] } } + }, + "autoMaintain": { + "type": "object", + "properties": { + "requireApprovals": { + "type": "integer" + }, + "mergeMethod": { + "type": "string", + "enum": [ + "merge", + "squash", + "rebase" + ] + } + }, + "required": [ + "requireApprovals", + "mergeMethod" + ] } }, "required": [ diff --git a/apps/gittensory-ui/src/components/site/app-panels/maintainer-settings.tsx b/apps/gittensory-ui/src/components/site/app-panels/maintainer-settings.tsx index 05edfeaa5b..19e45bc238 100644 --- a/apps/gittensory-ui/src/components/site/app-panels/maintainer-settings.tsx +++ b/apps/gittensory-ui/src/components/site/app-panels/maintainer-settings.tsx @@ -40,8 +40,30 @@ type MaintainerSettings = { requireLinkedIssue: boolean; badgeEnabled: boolean; commandAuthorization: CommandAuthorization; + autonomy: Partial>; + autoMaintain: { requireApprovals: number; mergeMethod: AutoMergeMethod }; }; +type AutonomyLevel = "observe" | "suggest" | "propose" | "auto_with_approval" | "auto"; +type AgentActionClass = "review" | "request_changes" | "approve" | "merge" | "close" | "label"; +type AutoMergeMethod = "merge" | "squash" | "rebase"; + +const AUTONOMY_LEVELS: AutonomyLevel[] = [ + "observe", + "suggest", + "propose", + "auto_with_approval", + "auto", +]; +const AGENT_ACTION_CLASSES: AgentActionClass[] = [ + "review", + "request_changes", + "approve", + "merge", + "close", + "label", +]; + type Message = { kind: "ok" | "err"; text: string }; const GATE_MODE_OPTIONS: Array<[GateMode, string]> = [ @@ -84,6 +106,8 @@ const EDITABLE_KEYS: Array = [ "requireLinkedIssue", "badgeEnabled", "commandAuthorization", + "autonomy", + "autoMaintain", ]; type SelectFieldDef = { @@ -277,7 +301,19 @@ export function MaintainerSettings({ reviewability }: { reviewability: Array<{ p credentials: "include", silentStatus: true, }); - setSettings(result.ok ? result.data : null); + // Default the agent-layer fields defensively so the editor renders even against an older response shape. + setSettings( + result.ok + ? { + ...result.data, + autonomy: result.data.autonomy ?? {}, + autoMaintain: result.data.autoMaintain ?? { + requireApprovals: 1, + mergeMethod: "squash", + }, + } + : null, + ); setLoading(false); }, [repoFullName]); @@ -443,6 +479,76 @@ export function MaintainerSettings({ reviewability }: { reviewability: Array<{ p ) : null} +
+

Auto-maintain (agent layer)

+

+ Per-action autonomy: observe (watch only) →{" "} + suggest →{" "} + propose →{" "} + auto_with_approval →{" "} + auto. Deny-by-default — anything left at{" "} + observe never acts. +

+
+ {AGENT_ACTION_CLASSES.map((actionClass) => ( + + ))} +
+
+ + +
+
+