Slice 6: right-click command menu + git write actions

git boundary: Pull/Push/Commit/DiscardAll (discard is 1.4-destructive). Scanner.RefreshRepo re-scans one repo after a mutation. Service GitFetch/GitPull/GitPush/GitCommit/GitDiscard record a git-* activity event and refresh on success; service.New takes a refresh hook. HTTP POST /api/repo/git. New <repo-menu> overlay with plain-language commands (Get latest/Publish/Check for updates/Save my work/Set active/Ask Claude to switch/Copy path/Discard all changes), summoned by repo-list's repo:contextmenu. Added service test for commit/discard on a temp repo. Verified the menu live for safe commands.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-20 09:12:18 -04:00
parent 9d1519222c
commit 6ef1c195e3
13 changed files with 521 additions and 9 deletions
+36 -1
View File
@@ -68,7 +68,8 @@ func main() {
}
// The one service layer both the HTTP API and the MCP server call (§1.7).
svc := service.New(g, scanner.Index, feed, fg)
// scanner.RefreshRepo lets a mutating action re-scan just that repo.
svc := service.New(g, scanner.Index, feed, fg, scanner.RefreshRepo)
tmpl, err := render.New("web/templates")
if err != nil {
@@ -161,6 +162,40 @@ func main() {
return c.JSON(http.StatusOK, map[string]any{"pending": false})
})
// Plain-language git commands (the right-click menu, §6). One endpoint,
// op-switched. Destructive ops (discard) are confirmed UI-side per §1.4.
e.POST("/api/repo/git", func(c echo.Context) error {
var body struct {
Path string `json:"path"`
Op string `json:"op"`
Message string `json:"message"`
}
if err := c.Bind(&body); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid body"})
}
ctx := c.Request().Context()
var out string
var err error
switch body.Op {
case "fetch":
out, err = svc.GitFetch(ctx, activity.ActorUser, body.Path)
case "pull":
out, err = svc.GitPull(ctx, activity.ActorUser, body.Path)
case "push":
out, err = svc.GitPush(ctx, activity.ActorUser, body.Path)
case "commit":
out, err = svc.GitCommit(ctx, activity.ActorUser, body.Path, body.Message)
case "discard":
out, err = svc.GitDiscard(ctx, activity.ActorUser, body.Path)
default:
return c.JSON(http.StatusBadRequest, map[string]string{"error": "unknown op: " + body.Op})
}
if err != nil {
return c.JSON(http.StatusBadGateway, map[string]string{"error": err.Error()})
}
return c.JSON(http.StatusOK, map[string]any{"ok": true, "output": out})
})
// Forge PRs + "Merge & clean up" (§8.4).
e.GET("/api/repo/prs", func(c echo.Context) error {
prs, err := svc.ForgePRs(c.Request().Context(), c.QueryParam("path"))