diff --git a/.pnpm-store/store/v11/index.db b/.pnpm-store/store/v11/index.db new file mode 100644 index 00000000..7f9770bd Binary files /dev/null and b/.pnpm-store/store/v11/index.db differ diff --git a/core/codegen.go b/core/codegen.go index 7ceec826..c566f03f 100644 --- a/core/codegen.go +++ b/core/codegen.go @@ -168,7 +168,7 @@ func emitInterface(t reflect.Type, name string) string { optional = true } } - nullable := f.Type.Kind() == reflect.Ptr + nullable := f.Type.Kind() == reflect.Pointer tsTypeStr := tsType(f.Type) marker := ": " @@ -199,7 +199,7 @@ func emitInterface(t reflect.Type, name string) string { // - interface{} -> unknown func tsType(t reflect.Type) string { switch t.Kind() { - case reflect.Ptr: + case reflect.Pointer: return tsType(t.Elem()) case reflect.Bool: return "boolean" diff --git a/internal/adapter/dolt/supervisor/supervisor.go b/internal/adapter/dolt/supervisor/supervisor.go index d4727be3..ccbf4672 100644 --- a/internal/adapter/dolt/supervisor/supervisor.go +++ b/internal/adapter/dolt/supervisor/supervisor.go @@ -129,7 +129,7 @@ type Supervisor struct { // "ready then went sideways → restarting" lane. starting atomic.Bool - done chan struct{} // closed when the supervisor permanently fails + done chan struct{} // closed when the supervisor permanently fails doneErr atomic.Pointer[error] cancel context.CancelFunc // cancels the supervisor goroutines @@ -563,7 +563,7 @@ func pickFreePort(host string) (int, error) { if err != nil { return 0, err } - defer l.Close() + defer func() { _ = l.Close() }() addr, ok := l.Addr().(*net.TCPAddr) if !ok { return 0, fmt.Errorf("unexpected listener addr type %T", l.Addr()) diff --git a/internal/adapter/dolt/supervisor/unix.go b/internal/adapter/dolt/supervisor/unix.go index 7784c81e..a29d24d4 100644 --- a/internal/adapter/dolt/supervisor/unix.go +++ b/internal/adapter/dolt/supervisor/unix.go @@ -4,6 +4,7 @@ package supervisor import ( "bufio" + "context" "io" "log/slog" "os/exec" @@ -45,7 +46,7 @@ func pipeToSlog(r io.ReadCloser, log *slog.Logger, level slog.Level, source stri buf := make([]byte, 0, 64*1024) scanner.Buffer(buf, 1024*1024) for scanner.Scan() { - log.Log(nil, level, "dolt subprocess output", + log.Log(context.Background(), level, "dolt subprocess output", "source", source, "line", scanner.Text()) } } diff --git a/internal/adapter/firecracker/firecracker.go b/internal/adapter/firecracker/firecracker.go index c089dacc..61014eee 100644 --- a/internal/adapter/firecracker/firecracker.go +++ b/internal/adapter/firecracker/firecracker.go @@ -135,7 +135,7 @@ type VM struct { // applier during Start. Empty when egress enforcement was not // configured (no provider attached) or when the fallback path // ran. Stop reads it to drive teardownEgressRules. - egressTable string + egressTable string //nolint:unused // referenced only by firecracker_linux.go } // validate enforces the minimal Spec contract shared by every backend. @@ -228,7 +228,7 @@ func mergeSecrets(vaultSecrets, explicit map[string]string) map[string]string { // errAlreadyStopped is returned by the internal stop path when a VM // has already been torn down. Promoted to a no-op nil return at the // public Supervisor.Stop boundary so callers can stop idempotently. -var errAlreadyStopped = errors.New("firecracker: vm already stopped") +var errAlreadyStopped = errors.New("firecracker: vm already stopped") //nolint:unused // referenced only by firecracker_linux.go // newVMID generates a per-VM identifier of the shape // "ws--<8hex>". Uses crypto/rand so concurrent VMs from diff --git a/internal/adapter/firecracker/firecracker_linux.go b/internal/adapter/firecracker/firecracker_linux.go index b21fe83e..52c36c07 100644 --- a/internal/adapter/firecracker/firecracker_linux.go +++ b/internal/adapter/firecracker/firecracker_linux.go @@ -404,4 +404,3 @@ func generateMAC(id string) string { return fmt.Sprintf("02:%02x:%02x:%02x:%02x:%02x", h[0], h[1], h[2], h[3], h[4]) } - diff --git a/internal/auth/oauth/oauth.go b/internal/auth/oauth/oauth.go index ba959dff..c939cfbd 100644 --- a/internal/auth/oauth/oauth.go +++ b/internal/auth/oauth/oauth.go @@ -228,8 +228,8 @@ func (o *OAuth) CallbackHandler() http.HandlerFunc { return func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]any{ - "status": "ok", - "flow": "device", + "status": "ok", + "flow": "device", "client_id": o.ClientID(), }) } diff --git a/internal/auth/oauth/oauth_github.go b/internal/auth/oauth/oauth_github.go index 2582563f..f54b590c 100644 --- a/internal/auth/oauth/oauth_github.go +++ b/internal/auth/oauth/oauth_github.go @@ -262,8 +262,8 @@ func matchesTeam(teams []ghTeam, allowed []string) bool { // broader tenant-config refactor; this in-memory store mirrors the // SQLStore + MemStore split tenant.Store uses today. type OrgGateStore struct { - mu sync.RWMutex - gates map[string]OrgGate + mu sync.RWMutex + gates map[string]OrgGate } // NewOrgGateStore returns an empty in-memory store. diff --git a/internal/cli/admin_backup.go b/internal/cli/admin_backup.go index aa229048..cc622537 100644 --- a/internal/cli/admin_backup.go +++ b/internal/cli/admin_backup.go @@ -68,10 +68,10 @@ func newAdminCmd() *cobra.Command { } type backupFlags struct { - out string - dataDir string - auditLog string - version string + out string + dataDir string + auditLog string + version string skipDoltDump bool } @@ -105,10 +105,10 @@ with 'gemba admin restore --from '.`, } type restoreFlags struct { - from string - dataDir string + from string + dataDir string auditLog string - force bool + force bool } func newAdminRestoreCmd() *cobra.Command { @@ -234,7 +234,7 @@ func runAdminRestore(out io.Writer, flags restoreFlags) error { if err != nil { return fmt.Errorf("gzip reader: %w", err) } - defer gz.Close() + defer func() { _ = gz.Close() }() tr := tar.NewReader(gz) auditOut := flags.auditLog diff --git a/internal/cli/admin_backup_test.go b/internal/cli/admin_backup_test.go index 9daecda3..7a413f9f 100644 --- a/internal/cli/admin_backup_test.go +++ b/internal/cli/admin_backup_test.go @@ -147,11 +147,10 @@ func TestAdminBackup_RequiresFlags(t *testing.T) { if err := runAdminBackup(&buf, backupFlags{dataDir: t.TempDir()}); err == nil { t.Fatalf("expected error for missing --out") } - if err := runAdminBackup(&buf, backupFlags{out: filepath.Join(t.TempDir(), "x.tgz")}); err == nil { - // This may not surface here because the cobra layer validates; - // runAdminBackup itself attempts to read data-dir which is "". - // Either way, the absence of a tar.gz is the failure mode. - } + // Missing data-dir: runAdminBackup itself may not surface this (cobra + // validates at a higher layer); the absence of a tar.gz is the actual + // failure mode. Call it for coverage; ignore the result. + _ = runAdminBackup(&buf, backupFlags{out: filepath.Join(t.TempDir(), "x.tgz")}) } // TestAdminRestore_RejectsTampered verifies that a backup whose diff --git a/internal/cli/bead_crud.go b/internal/cli/bead_crud.go index 5db53ec1..61764c17 100644 --- a/internal/cli/bead_crud.go +++ b/internal/cli/bead_crud.go @@ -32,8 +32,8 @@ import ( "github.com/spf13/cobra" "github.com/GembaCore/gemba-core/core" - gembaclient "github.com/GembaCore/gemba-core/internal/client" "github.com/GembaCore/gemba-core/internal/cli/serverconfig" + gembaclient "github.com/GembaCore/gemba-core/internal/client" ) // clientFactory is the seam tests use to inject a pre-baked typed @@ -477,4 +477,3 @@ Server-side this targets POST /api/work-items/{id}/notes, which is a cmd.Flags().StringVar(¬e, "note", "", "note text (alternative to positional)") return cmd } - diff --git a/internal/cli/login.go b/internal/cli/login.go index 7aff2448..a8636ee3 100644 --- a/internal/cli/login.go +++ b/internal/cli/login.go @@ -30,8 +30,8 @@ import ( "golang.org/x/term" "github.com/GembaCore/gemba-core/internal/auth/credstore" - gembaclient "github.com/GembaCore/gemba-core/internal/client" "github.com/GembaCore/gemba-core/internal/cli/serverconfig" + gembaclient "github.com/GembaCore/gemba-core/internal/client" ) // whoamiResponse mirrors internal/server/whoami.go's wire shape. @@ -253,7 +253,7 @@ func promptForTokenMasked(out io.Writer) (string, error) { return "", fmt.Errorf("open /dev/tty: %w (use --token or --token-stdin)", err) } defer tty.Close() - fmt.Fprint(out, "Enter personal access token: ") + _, _ = fmt.Fprint(out, "Enter personal access token: ") b, err := term.ReadPassword(int(tty.Fd())) fmt.Fprintln(out) if err != nil { @@ -399,4 +399,3 @@ func printNotLoggedIn(out io.Writer, asJSON bool, server string) error { fmt.Fprintln(out, "not logged in") return nil } - diff --git a/internal/cli/logs.go b/internal/cli/logs.go index fdd95254..813dc2e3 100644 --- a/internal/cli/logs.go +++ b/internal/cli/logs.go @@ -50,8 +50,8 @@ going, up to --max-retries times.`, if !follow { return errors.New( "transcript replay not yet implemented (server " + - "lacks GET /api/runs/{id}/transcript). Use -f " + - "to attach to the live stream instead.") + "lacks GET /api/runs/{id}/transcript); use -f " + + "to attach to the live stream instead") } return runLogsFollow(cmd, args[0]) }, diff --git a/internal/cli/run.go b/internal/cli/run.go index f56e795f..1e48f45d 100644 --- a/internal/cli/run.go +++ b/internal/cli/run.go @@ -119,7 +119,8 @@ func runRunCmd(cmd *cobra.Command, beadID string) error { "detached. resume with: gemba logs -f %s\n", runID) // Cobra eats os.Exit-equivalent codes; the parent process gets // 130 by exiting directly. This is the SIGINT convention. - os.Exit(exitCodeDetached) + stop() + os.Exit(exitCodeDetached) //nolint:gocritic // intentional: SIGINT exit convention after manual stop() } return nil } diff --git a/internal/cli/run_client.go b/internal/cli/run_client.go index bf1bf353..a99ea388 100644 --- a/internal/cli/run_client.go +++ b/internal/cli/run_client.go @@ -19,8 +19,8 @@ import ( "github.com/spf13/cobra" - gembaclient "github.com/GembaCore/gemba-core/internal/client" "github.com/GembaCore/gemba-core/internal/cli/serverconfig" + gembaclient "github.com/GembaCore/gemba-core/internal/client" ) // runClientFactory builds a typed client bound to server. Tests diff --git a/internal/cli/serve.go b/internal/cli/serve.go index b02827fa..c8490d49 100644 --- a/internal/cli/serve.go +++ b/internal/cli/serve.go @@ -41,13 +41,13 @@ import ( "github.com/GembaCore/gemba-core/internal/persona" "github.com/GembaCore/gemba-core/internal/personas/onboarder" "github.com/GembaCore/gemba-core/internal/server" - "github.com/GembaCore/gemba-core/internal/tenant" "github.com/GembaCore/gemba-core/internal/server/metrics" "github.com/GembaCore/gemba-core/internal/shader" "github.com/GembaCore/gemba-core/internal/shader/gastown" "github.com/GembaCore/gemba-core/internal/skills/bootstrap_review" "github.com/GembaCore/gemba-core/internal/skills/epic_order" "github.com/GembaCore/gemba-core/internal/skills/escalation_handoff" + "github.com/GembaCore/gemba-core/internal/tenant" "github.com/GembaCore/gemba-core/internal/transport/api" "github.com/GembaCore/gemba-core/internal/walk" walksources "github.com/GembaCore/gemba-core/internal/walk/sources" diff --git a/internal/cli/serve_embedded_dolt.go b/internal/cli/serve_embedded_dolt.go index d3c0c86a..1a36031e 100644 --- a/internal/cli/serve_embedded_dolt.go +++ b/internal/cli/serve_embedded_dolt.go @@ -162,7 +162,7 @@ func bootstrapEmbeddedDoltSchema(ctx context.Context, sup *supervisor.Supervisor if err != nil { return fmt.Errorf("embedded dolt: mkdtemp for bd init: %w", err) } - defer os.RemoveAll(tmpDir) + defer func() { _ = os.RemoveAll(tmpDir) }() bdCtx, cancel := context.WithTimeout(ctx, 60*time.Second) defer cancel() diff --git a/internal/cli/spec.go b/internal/cli/spec.go index a2e7c35e..2750b6db 100644 --- a/internal/cli/spec.go +++ b/internal/cli/spec.go @@ -1,11 +1,12 @@ // gemba spec — ASDD interception layer command tree. // // Subcommands: -// spec new Scaffold a new spec directory (and optionally Kit files). -// spec init Seed ASDD project artifacts (constitution, hooks, CLAUDE.md). -// spec hook PreToolUse hook handlers (Write/Edit/NotebookEdit, Bash). -// spec preflight Refuse /implement when tasks.md still exists. -// spec reconcile Stub — to be implemented by the analyzer (gm-v0sp.21). +// +// spec new Scaffold a new spec directory (and optionally Kit files). +// spec init Seed ASDD project artifacts (constitution, hooks, CLAUDE.md). +// spec hook PreToolUse hook handlers (Write/Edit/NotebookEdit, Bash). +// spec preflight Refuse /implement when tasks.md still exists. +// spec reconcile Stub — to be implemented by the analyzer (gm-v0sp.21). package cli import ( @@ -127,9 +128,9 @@ func newSpecInitCmd() *cobra.Command { return err } opts := scaffold.Options{ - WriteHooks: asdd, - WriteConstitution: asdd, - UpdateClaudeMD: asdd || updateClaudeMD, + WriteHooks: asdd, + WriteConstitution: asdd, + UpdateClaudeMD: asdd || updateClaudeMD, WriteSlashCommands: asdd, } r, err := scaffold.Apply(root, opts) diff --git a/internal/cli/spec_analyze.go b/internal/cli/spec_analyze.go index 184fcd86..8550daf5 100644 --- a/internal/cli/spec_analyze.go +++ b/internal/cli/spec_analyze.go @@ -164,7 +164,7 @@ func renderProposal(w io.Writer, p *analyzer.Proposal) { fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\n", b.LocalRef, b.Kind, dashIfEmpty(b.Parent), dashIfEmpty(b.Priority), b.Title) } - tw.Flush() + _ = tw.Flush() } func dashIfEmpty(s string) string { diff --git a/internal/cli/spec_diff.go b/internal/cli/spec_diff.go index aa4253df..218bc471 100644 --- a/internal/cli/spec_diff.go +++ b/internal/cli/spec_diff.go @@ -20,11 +20,11 @@ import ( // newSpecDiffCmd wires `gemba spec diff `. // // It is a pure planner — no bd mutations, no disk writes. The CLI: -// 1. parses spec.md, -// 2. loads the sibling .spec.lock.json (if any), -// 3. calls reconcile.Diff with either the real bd lister or a test stub -// (--bd-stub ), -// 4. emits either a human table or JSON. +// 1. parses spec.md, +// 2. loads the sibling .spec.lock.json (if any), +// 3. calls reconcile.Diff with either the real bd lister or a test stub +// (--bd-stub ), +// 4. emits either a human table or JSON. func newSpecDiffCmd() *cobra.Command { var jsonOut bool var label string @@ -146,7 +146,7 @@ func renderPlanTable(cmd *cobra.Command, plan *reconcile.Plan) error { emit(plan.Creates) emit(plan.Updates) emit(plan.Orphans) - tw.Flush() + _ = tw.Flush() fmt.Fprintf(out, "summary: %d create, %d update, %d orphan\n", len(plan.Creates), len(plan.Updates), len(plan.Orphans)) return nil diff --git a/internal/cli/spec_lint.go b/internal/cli/spec_lint.go index 3735483c..22013b6e 100644 --- a/internal/cli/spec_lint.go +++ b/internal/cli/spec_lint.go @@ -59,7 +59,7 @@ func newSpecLintCmd() *cobra.Command { } fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", sev, f.Rule, f.Anchor, f.Message) } - tw.Flush() + _ = tw.Flush() } } if strict { diff --git a/internal/cli/spec_promote.go b/internal/cli/spec_promote.go index 45b7d06f..f8f36d77 100644 --- a/internal/cli/spec_promote.go +++ b/internal/cli/spec_promote.go @@ -115,7 +115,7 @@ func newSpecPromoteCmd() *cobra.Command { return nil } - fmt.Fprint(cmd.OutOrStdout(), diff) + _, _ = fmt.Fprint(cmd.OutOrStdout(), diff) return nil }, } diff --git a/internal/cli/status.go b/internal/cli/status.go index 4e76aa36..6ddef0f9 100644 --- a/internal/cli/status.go +++ b/internal/cli/status.go @@ -211,7 +211,9 @@ func fetchStatus(ctx context.Context, c *gembaclient.Client, srv string) (status if err != nil { // Some adaptors won't recognize "open" as a status; that's // fine — fall back to ready+in_progress as the floor. - openItems = append(ready, inflight...) + openItems = make([]core.WorkItem, 0, len(ready)+len(inflight)) + openItems = append(openItems, ready...) + openItems = append(openItems, inflight...) } closedToday := 0 if closed, err := c.ListWorkItemsByStatus(ctx, "closed"); err == nil { @@ -352,7 +354,7 @@ func renderAdHoc(w io.Writer, p statusPayload) { fmt.Fprintf(tw, " ○\t%s\t%s\t%s\n", string(wi.ID), priorityBadge(wi.Priority), wi.Title) } - tw.Flush() + _ = tw.Flush() } fmt.Fprintln(w) @@ -382,7 +384,7 @@ func renderAdHoc(w io.Writer, p statusPayload) { fmt.Fprintf(tw, " ◐\t%s\t%s\t%s%s\n", string(wi.ID), priorityBadge(wi.Priority), wi.Title, suffix) } - tw.Flush() + _ = tw.Flush() } fmt.Fprintln(w) @@ -438,4 +440,3 @@ func humanDuration(d time.Duration) string { return fmt.Sprintf("%dd", int(d.Hours()/24)) } } - diff --git a/internal/client/client_test.go b/internal/client/client_test.go index d4f6a49f..0b026fea 100644 --- a/internal/client/client_test.go +++ b/internal/client/client_test.go @@ -283,4 +283,3 @@ func indexOf(s, sub string) int { } return -1 } - diff --git a/internal/client/status.go b/internal/client/status.go index d8cd72f0..4a117704 100644 --- a/internal/client/status.go +++ b/internal/client/status.go @@ -90,6 +90,7 @@ func (c *Client) WorkspaceStatus(ctx context.Context, wsid string) (*WorkspaceSt if err != nil { return nil, err } + defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, mapErr(resp, raw) } diff --git a/internal/client/work_items.go b/internal/client/work_items.go index 1f0889d6..e9c8e587 100644 --- a/internal/client/work_items.go +++ b/internal/client/work_items.go @@ -92,6 +92,7 @@ func (c *Client) CreateWorkItem(ctx context.Context, in CreateWorkItemInput) (*c if err != nil { return nil, err } + defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, mapErr(resp, raw) } @@ -126,6 +127,7 @@ func (c *Client) ListWorkItems(ctx context.Context, f ListWorkItemsFilter) ([]co if err != nil { return nil, err } + defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, mapErr(resp, raw) } @@ -154,6 +156,7 @@ func (c *Client) GetWorkItem(ctx context.Context, id string) (*core.WorkItem, er if err != nil { return nil, err } + defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, mapErr(resp, raw) } @@ -178,6 +181,7 @@ func (c *Client) PatchWorkItem(ctx context.Context, id string, patch core.WorkIt if err != nil { return nil, err } + defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, mapErr(resp, raw) } @@ -193,11 +197,11 @@ func (c *Client) PatchWorkItem(ctx context.Context, id string, patch core.WorkIt // (gm-o9t8.1.5.5). Decoupled from the server-side type so the wire // client stays independent of internal/server. type CascadeDispatchResponse struct { - WrapperID string `json:"wrapper_id"` - Dispatched []CascadeDispatchedItem `json:"dispatched"` - Blocked []string `json:"blocked,omitempty"` - Skipped []string `json:"skipped,omitempty"` - Errors []CascadeDispatchError `json:"errors,omitempty"` + WrapperID string `json:"wrapper_id"` + Dispatched []CascadeDispatchedItem `json:"dispatched"` + Blocked []string `json:"blocked,omitempty"` + Skipped []string `json:"skipped,omitempty"` + Errors []CascadeDispatchError `json:"errors,omitempty"` } // CascadeDispatchedItem is one entry in the Dispatched slice. @@ -244,6 +248,7 @@ func (c *Client) CascadeDispatchWorkItem(ctx context.Context, id string, in Casc if err != nil { return nil, err } + defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, mapErr(resp, raw) } @@ -276,6 +281,7 @@ func (c *Client) NoteWorkItem(ctx context.Context, id, note string) error { if err != nil { return err } + defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return mapErr(resp, raw) } diff --git a/internal/config/bind.go b/internal/config/bind.go index bc93bb30..58de6e72 100644 --- a/internal/config/bind.go +++ b/internal/config/bind.go @@ -2,6 +2,7 @@ package config import ( + "errors" "fmt" "net" "net/url" @@ -414,8 +415,8 @@ func (c ServeConfig) ValidateWorkPlaneFlags() error { // unused. Reject the combination unless the operator explicitly // opted out via --embedded-dolt=false. if projectDir != "" && c.EmbeddedDoltSet && c.EmbeddedDolt { - return fmt.Errorf( - "--project-dir owns its own Dolt config; --embedded-dolt is mutually exclusive. Pick one.") + return errors.New( + "--project-dir owns its own Dolt config; --embedded-dolt is mutually exclusive — pick one") } if projectDir == "" && c.DoltURL == "" { return fmt.Errorf( diff --git a/internal/config/bind_test.go b/internal/config/bind_test.go index e51f38dc..f051fa54 100644 --- a/internal/config/bind_test.go +++ b/internal/config/bind_test.go @@ -518,10 +518,10 @@ func TestBeadsSource(t *testing.T) { // gm-o9t8.3.5.1 — config-validation matrix for OAuth + MultiTenant. func TestOAuthConfigMatrix(t *testing.T) { cases := []struct { - name string - cfg ServeConfig - wantEnabled bool - wantMTErr bool + name string + cfg ServeConfig + wantEnabled bool + wantMTErr bool }{ {"single_user_unset", ServeConfig{}, false, false}, {"single_user_oauth_set", ServeConfig{ diff --git a/internal/egress/match.go b/internal/egress/match.go index 1947500a..480386ec 100644 --- a/internal/egress/match.go +++ b/internal/egress/match.go @@ -14,13 +14,13 @@ import "strings" // // Examples (matrix lives in match_test.go): // -// pattern host match? -// "api.anthropic.com" "api.anthropic.com" yes -// "*.anthropic.com" "api.anthropic.com" yes -// "*.anthropic.com" "foo.bar.anthropic.com" no (* is one label) -// "**.anthropic.com" "foo.bar.anthropic.com" yes -// "**.anthropic.com" "anthropic.com" no (** = one or more) -// "github.com" "GitHub.COM" yes (case-insensitive) +// pattern host match? +// "api.anthropic.com" "api.anthropic.com" yes +// "*.anthropic.com" "api.anthropic.com" yes +// "*.anthropic.com" "foo.bar.anthropic.com" no (* is one label) +// "**.anthropic.com" "foo.bar.anthropic.com" yes +// "**.anthropic.com" "anthropic.com" no (** = one or more) +// "github.com" "GitHub.COM" yes (case-insensitive) // // Matching is anchored on both ends — the pattern must consume the // whole host. The matcher is purely syntactic; CIDR / IP literals are diff --git a/internal/egress/store.go b/internal/egress/store.go index 1de6886f..82e7288c 100644 --- a/internal/egress/store.go +++ b/internal/egress/store.go @@ -51,8 +51,8 @@ func effectiveFromList(workspace []Rule) []Rule { // MemStore is a process-local Store used by tests and by the // single-user fallback when a Dolt pool isn't wired. type MemStore struct { - mu sync.RWMutex - byWS map[string]map[string]Rule // wsid -> id -> rule + mu sync.RWMutex + byWS map[string]map[string]Rule // wsid -> id -> rule } // NewMemStore returns an empty in-memory store. diff --git a/internal/server/admin_console.go b/internal/server/admin_console.go index 5bfdc028..6b09b65e 100644 --- a/internal/server/admin_console.go +++ b/internal/server/admin_console.go @@ -234,10 +234,10 @@ func (r *Router) adminAuditVerify(w http.ResponseWriter, req *http.Request) { // also try to recover the int when present. broken := firstBrokenSeq(records, ed25519.PublicKey(r.adminAuditPubKey)) writeJSON(w, http.StatusOK, map[string]any{ - "ok": false, - "count": len(records), - "error": err.Error(), - "broken_seq": broken, + "ok": false, + "count": len(records), + "error": err.Error(), + "broken_seq": broken, }) return } diff --git a/internal/server/billing_usage.go b/internal/server/billing_usage.go index 5e92e4f4..61e66eff 100644 --- a/internal/server/billing_usage.go +++ b/internal/server/billing_usage.go @@ -68,9 +68,9 @@ func (r *Router) tenantUsage(w http.ResponseWriter, req *http.Request) { } snap := r.usageAggregator.Snapshot(string(requested)) writeJSON(w, http.StatusOK, map[string]any{ - "tenant_id": string(requested), - "vm_minutes": snap.VMMinutes, - "egress_bytes": snap.EgressBytes, - "audit_log_gb": snap.AuditLogGB, + "tenant_id": string(requested), + "vm_minutes": snap.VMMinutes, + "egress_bytes": snap.EgressBytes, + "audit_log_gb": snap.AuditLogGB, }) } diff --git a/internal/server/diff_test.go b/internal/server/diff_test.go index 97d7f379..678cf43a 100644 --- a/internal/server/diff_test.go +++ b/internal/server/diff_test.go @@ -176,8 +176,8 @@ func TestDiff_PathTraversalRejected(t *testing.T) { "path=/etc/passwd", "path=foo/../../bar", "path=-flag", - "path=foo%7Cbar", // pipe — shell metachar - "path=foo%60bar", // backtick + "path=foo%7Cbar", // pipe — shell metachar + "path=foo%60bar", // backtick } for _, q := range cases { t.Run(q, func(t *testing.T) { diff --git a/internal/server/dolthub/dolthub.go b/internal/server/dolthub/dolthub.go index 711580c9..a92ac4a8 100644 --- a/internal/server/dolthub/dolthub.go +++ b/internal/server/dolthub/dolthub.go @@ -23,8 +23,8 @@ var _ Vault = (*secrets.Vault)(nil) // Standard secret names used by gemba-remote DoltHub integration. const ( - SecretRemoteURL = "DOLTHUB_REMOTE_URL" - SecretAuthToken = "DOLTHUB_AUTH_TOKEN" + SecretRemoteURL = "DOLTHUB_REMOTE_URL" + SecretAuthToken = "DOLTHUB_AUTH_TOKEN" ) // Remote is a per-workspace DoltHub remote handle. RepoPath is the diff --git a/internal/server/dolthub/dolthub_test.go b/internal/server/dolthub/dolthub_test.go index 41e130bb..a59d4588 100644 --- a/internal/server/dolthub/dolthub_test.go +++ b/internal/server/dolthub/dolthub_test.go @@ -75,7 +75,7 @@ func TestVaultInterfaceCompat(t *testing.T) { } r := Open(t.TempDir(), v) // Confirm the type satisfies Vault. - var _ Vault = r.Vault + var _ = r.Vault // Without a real dolt repo, Configure may fail when it tries to add // the remote; we only verify the read-side here. _, err = v.Inject(SecretRemoteURL) diff --git a/internal/server/health.go b/internal/server/health.go index 5b27b2b9..4f48b302 100644 --- a/internal/server/health.go +++ b/internal/server/health.go @@ -98,22 +98,22 @@ type healthState struct { // per the contract above. Calling AttachHealthChecks more than once // replaces the prior wiring atomically. func (r *Router) AttachHealthChecks(vault VaultProber, dolt DoltProber, auditDir string) { - r.healthState.mu.Lock() - defer r.healthState.mu.Unlock() + r.mu.Lock() + defer r.mu.Unlock() r.healthState.vault = vault - r.healthState.dolt = dolt - r.healthState.auditDir = auditDir - if r.healthState.kvmPath == "" { - r.healthState.kvmPath = "/dev/kvm" + r.dolt = dolt + r.auditDir = auditDir + if r.kvmPath == "" { + r.kvmPath = "/dev/kvm" } } // setHealthKVMPath is a test seam — production callers never override // the default /dev/kvm path. func (r *Router) setHealthKVMPath(p string) { - r.healthState.mu.Lock() - defer r.healthState.mu.Unlock() - r.healthState.kvmPath = p + r.mu.Lock() + defer r.mu.Unlock() + r.kvmPath = p } // healthzPlain is the /healthz handler. It returns 200 OK with @@ -130,12 +130,12 @@ func (r *Router) readyzPlain(w http.ResponseWriter, req *http.Request) { ctx, cancel := context.WithTimeout(req.Context(), 5*time.Second) defer cancel() - r.healthState.mu.RLock() + r.mu.RLock() v := r.healthState.vault - d := r.healthState.dolt - auditDir := r.healthState.auditDir - kvmPath := r.healthState.kvmPath - r.healthState.mu.RUnlock() + d := r.dolt + auditDir := r.auditDir + kvmPath := r.kvmPath + r.mu.RUnlock() if kvmPath == "" { kvmPath = "/dev/kvm" diff --git a/internal/server/router.go b/internal/server/router.go index 24185615..44bbe917 100644 --- a/internal/server/router.go +++ b/internal/server/router.go @@ -274,7 +274,7 @@ type Router struct { // orgGates is the per-tenant GitHub OrgGate registry (wsB slice b). // Nil ⇒ every tenant is open (no policy applied). - orgGates *oauth.OrgGateStore + orgGates *oauth.OrgGateStore //nolint:unused // consumer lands with PR #9 (oauthLogin gate check) // adminEmails is the comma-separated allow-list for admin-only // routes mounted at /api/v1/admin/* (wsB slice b). Empty disables diff --git a/internal/server/secrets/vault.go b/internal/server/secrets/vault.go index 39ddb6ba..c4765a94 100644 --- a/internal/server/secrets/vault.go +++ b/internal/server/secrets/vault.go @@ -45,8 +45,8 @@ var ErrTampered = errors.New("secrets: vault blob failed integrity check") // Vault is a per-workspace secret store. All methods are safe for // concurrent use. The zero value is unusable; obtain a Vault via Open. type Vault struct { - path string - workspaceID string + path string + workspaceID string workspaceKey [32]byte mu sync.Mutex @@ -240,7 +240,9 @@ func (v *Vault) write(m map[string][]byte) error { return fmt.Errorf("secrets: nonce: %w", err) } ct := aead.Seal(nil, nonce, pt, []byte(v.workspaceID)) - blob := append(nonce, ct...) + blob := make([]byte, 0, len(nonce)+len(ct)) + blob = append(blob, nonce...) + blob = append(blob, ct...) tmp := v.path + ".tmp" if err := os.WriteFile(tmp, blob, 0o600); err != nil { return fmt.Errorf("secrets: write tmp: %w", err) diff --git a/internal/server/status.go b/internal/server/status.go index adaf4f47..43b8cbee 100644 --- a/internal/server/status.go +++ b/internal/server/status.go @@ -50,12 +50,12 @@ import ( // semantics so an unresolvable subsystem renders as null rather than as // a misleading zero. type WorkspaceStatusResponse struct { - WorkspaceID string `json:"workspace_id"` - Mode string `json:"mode"` - Repo *WorkspaceRepoStatus `json:"repo"` - Beads WorkspaceBeadCounts `json:"beads"` - Agents WorkspaceAgentSummary `json:"agents"` - EmbeddedDolt *EmbeddedDoltStatus `json:"embedded_dolt"` + WorkspaceID string `json:"workspace_id"` + Mode string `json:"mode"` + Repo *WorkspaceRepoStatus `json:"repo"` + Beads WorkspaceBeadCounts `json:"beads"` + Agents WorkspaceAgentSummary `json:"agents"` + EmbeddedDolt *EmbeddedDoltStatus `json:"embedded_dolt"` } // EmbeddedDoltStatus surfaces the embedded-dolt supervisor's posture so @@ -68,7 +68,7 @@ type WorkspaceStatusResponse struct { // - "starting" — Start() in flight; first SELECT 1 hasn't landed yet // - "ready" — last probe succeeded; subprocess healthy // - "restarting" — a recent probe failed; supervisor is in backoff / -// respawn between probes +// respawn between probes // - "failed" — maxConsecutiveFailures exhausted; supervisor gave up type EmbeddedDoltStatus struct { Enabled bool `json:"enabled"` @@ -569,9 +569,9 @@ func transcriptPathForSession(transcriptsDir string, s *core.Session) string { // // - terminalType: the event_type of the last terminal event, or "" // - note: the latest bead_note_appended note text (preferred -// summary signal), or "" +// summary signal), or "" // - reason: the "summary" / "reason" field of the terminal -// event when present, or "" +// event when present, or "" // - hasTerminal: true iff we observed any terminal event // // Parse errors per line are ignored — a corrupt line shouldn't poison diff --git a/internal/server/workspaces_lifecycle.go b/internal/server/workspaces_lifecycle.go index bc450280..a6bbf92c 100644 --- a/internal/server/workspaces_lifecycle.go +++ b/internal/server/workspaces_lifecycle.go @@ -196,14 +196,14 @@ func (f *FakeS3Uploader) Upload(_ context.Context, key string, payload []byte) ( // egress-template wiring together. Built via NewWorkspaceLifecycle and // attached to the Router via AttachWorkspaceLifecycle. type workspaceLifecycleHandler struct { - store WorkspaceLifecycleStore - vault VaultDeleter - s3 S3Uploader - egress egress.Store - dataDir string - clock func() time.Time - idGen func() string - retention time.Duration + store WorkspaceLifecycleStore + vault VaultDeleter + s3 S3Uploader + egress egress.Store + dataDir string + clock func() time.Time + idGen func() string + retention time.Duration } // WorkspaceLifecycleConfig bundles the optional collaborators for the diff --git a/internal/spec/analyzer/analyzer.go b/internal/spec/analyzer/analyzer.go index c4e50964..f9e3ed74 100644 --- a/internal/spec/analyzer/analyzer.go +++ b/internal/spec/analyzer/analyzer.go @@ -12,9 +12,9 @@ // // 1. heuristic.go — deterministic, offline classifier. This is the // baseline and always runs. Rules in short: -// * H1 line OR a line starting with "Milestone:" → milestone -// * H2 sections (## …) → epics -// * markdown checkbox lines (- [ ] / - [x] / * [ ] …) → stories +// * H1 line OR a line starting with "Milestone:" → milestone +// * H2 sections (## …) → epics +// * markdown checkbox lines (- [ ] / - [x] / * [ ] …) → stories // // 2. llm.go — opt-in refinement. If GEMBA_ANALYZER_LLM is set, the // heuristic proposal + raw payload are POSTed to a configurable diff --git a/internal/spec/apply/apply.go b/internal/spec/apply/apply.go index 16146268..7a70cb0b 100644 --- a/internal/spec/apply/apply.go +++ b/internal/spec/apply/apply.go @@ -111,13 +111,13 @@ func Apply(ctx context.Context, plan *reconcile.Plan, lock *lockfile.Lock, opts } emit(ctx, opts.Auditor, "spec.apply.start", map[string]any{ - "spec_id": plan.SpecID, - "nonce": plan.Nonce, - "creates": len(plan.Creates), - "updates": len(plan.Updates), - "orphans": len(plan.Orphans), - "dry_run": opts.DryRun, - "prune": opts.Prune, + "spec_id": plan.SpecID, + "nonce": plan.Nonce, + "creates": len(plan.Creates), + "updates": len(plan.Updates), + "orphans": len(plan.Orphans), + "dry_run": opts.DryRun, + "prune": opts.Prune, }) // Cache lockfile mapping by anchor for in-place mutation on success. diff --git a/internal/spec/apply/apply_test.go b/internal/spec/apply/apply_test.go index 764cd47f..0eb666fa 100644 --- a/internal/spec/apply/apply_test.go +++ b/internal/spec/apply/apply_test.go @@ -15,7 +15,7 @@ import ( type stubMutator struct { mu sync.Mutex - failCreateAt int // 1-indexed; 0 = never fail + failCreateAt int // 1-indexed; 0 = never fail failUpdateAt int failCloseAt int created int diff --git a/internal/spec/lockfile/lockfile.go b/internal/spec/lockfile/lockfile.go index d487adfa..e2495eb2 100644 --- a/internal/spec/lockfile/lockfile.go +++ b/internal/spec/lockfile/lockfile.go @@ -4,14 +4,14 @@ // // File layout: /.spec.lock.json // -// { -// "spec_id": "SP-001", -// "nonce": "uuid v4", -// "applied_at":"2026-05-13T00:00:00Z", -// "mappings": [ -// {"anchor":"M-01","bead_id":"gm-v0sp.7","content_hash":"sha256:..."} -// ] -// } +// { +// "spec_id": "SP-001", +// "nonce": "uuid v4", +// "applied_at":"2026-05-13T00:00:00Z", +// "mappings": [ +// {"anchor":"M-01","bead_id":"gm-v0sp.7","content_hash":"sha256:..."} +// ] +// } // // Writes are atomic (temp file + rename) so the file is never observed in // a half-written state. @@ -125,9 +125,9 @@ func Save(path string, lock *Lock) error { // Diff compares a parsed spec against an existing lock and partitions // mappings into three sets: // -// creates: anchors present in spec but absent from lock (need new beads) -// updates: anchors present in both but whose content hash changed -// orphans: anchors in lock but absent from spec (candidates for close) +// creates: anchors present in spec but absent from lock (need new beads) +// updates: anchors present in both but whose content hash changed +// orphans: anchors in lock but absent from spec (candidates for close) // // `creates` and `updates` use the spec's current content hash; `orphans` // echo back the stored lock mapping unchanged. diff --git a/internal/spec/parser/parser.go b/internal/spec/parser/parser.go index b785f796..ffdd9579 100644 --- a/internal/spec/parser/parser.go +++ b/internal/spec/parser/parser.go @@ -20,9 +20,10 @@ var ( ) // anchorRe matches an H3 heading whose title begins with a stable handle: -// ### M-01 Title -// ### E-02 Title -// ### S-13 Title +// +// ### M-01 Title +// ### E-02 Title +// ### S-13 Title var anchorRe = regexp.MustCompile(`^###\s+([MES])-(\d{2,})\s+(.+?)\s*$`) // inlineFieldRe captures `Status:`, `Parent:`, `Priority:` lines. diff --git a/internal/spec/parser/schema.go b/internal/spec/parser/schema.go index 1d8d8c06..e8955f18 100644 --- a/internal/spec/parser/schema.go +++ b/internal/spec/parser/schema.go @@ -23,7 +23,7 @@ type Frontmatter struct { // Story is a single deliverable under an Epic. type Story struct { - Anchor string // e.g. "S-01" + Anchor string // e.g. "S-01" Title string Status string Parent string diff --git a/internal/spec/promote/promote.go b/internal/spec/promote/promote.go index 99242676..ef721c6d 100644 --- a/internal/spec/promote/promote.go +++ b/internal/spec/promote/promote.go @@ -46,16 +46,16 @@ type BDMailLister struct { // rawBDMail is a permissive shape for the bd mail json output. We only // care about a handful of fields; everything else is ignored. type rawBDMail struct { - ID string `json:"id"` - MailID string `json:"mail_id"` - Subject string `json:"subject"` - Title string `json:"title"` - Body string `json:"body"` - Author string `json:"author"` - From string `json:"from"` - Timestamp string `json:"timestamp"` - CreatedAt string `json:"created_at"` - Status string `json:"status"` + ID string `json:"id"` + MailID string `json:"mail_id"` + Subject string `json:"subject"` + Title string `json:"title"` + Body string `json:"body"` + Author string `json:"author"` + From string `json:"from"` + Timestamp string `json:"timestamp"` + CreatedAt string `json:"created_at"` + Status string `json:"status"` Labels []string `json:"labels"` } diff --git a/internal/spec/scaffold/scaffold.go b/internal/spec/scaffold/scaffold.go index 576f7e21..32839cd1 100644 --- a/internal/spec/scaffold/scaffold.go +++ b/internal/spec/scaffold/scaffold.go @@ -14,11 +14,11 @@ import ( // Options controls what to scaffold. type Options struct { - Slug string // for {{slug}} replacement in slash-command templates - WriteSlashCommands bool - WriteHooks bool - WriteConstitution bool - UpdateClaudeMD bool + Slug string // for {{slug}} replacement in slash-command templates + WriteSlashCommands bool + WriteHooks bool + WriteConstitution bool + UpdateClaudeMD bool } // Result records the paths created or updated. diff --git a/internal/sse/sse.go b/internal/sse/sse.go index 249e3b79..6eb17fe8 100644 --- a/internal/sse/sse.go +++ b/internal/sse/sse.go @@ -84,7 +84,7 @@ func Consume(ctx context.Context, body io.ReadCloser, ch chan<- Event) error { case line == "": // Frame terminator. flush() - case strings.HasPrefix(line, ":"): + case strings.HasPrefix(line, ":"): //nolint:staticcheck // empty case is intentional — SSE spec ignores comment lines // Comment line — heartbeats / keepalive. Ignore. case strings.HasPrefix(line, "data:"): v := stripField(line, "data:") @@ -96,7 +96,7 @@ func Consume(ctx context.Context, body io.ReadCloser, ch chan<- Event) error { eventTyp = stripField(line, "event:") case strings.HasPrefix(line, "id:"): eventID = stripField(line, "id:") - case strings.HasPrefix(line, "retry:"): + case strings.HasPrefix(line, "retry:"): //nolint:staticcheck // empty case is intentional — server retry hints ignored, callers own reconnect cadence // Spec'd, ignored — we don't act on server-suggested retry // timing; callers own reconnect cadence. default: @@ -117,10 +117,7 @@ func Consume(ctx context.Context, body io.ReadCloser, ch chan<- Event) error { // per the SSE spec. func stripField(line, prefix string) string { v := strings.TrimPrefix(line, prefix) - if strings.HasPrefix(v, " ") { - v = v[1:] - } - return v + return strings.TrimPrefix(v, " ") } // deliver tries a non-blocking send. If ch is full we drop the oldest diff --git a/testing/e2e/package-lock.json b/testing/e2e/package-lock.json new file mode 100644 index 00000000..1d30f043 --- /dev/null +++ b/testing/e2e/package-lock.json @@ -0,0 +1,136 @@ +{ + "name": "gemba-e2e", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "gemba-e2e", + "version": "0.0.0", + "devDependencies": { + "@axe-core/playwright": "^4.10.0", + "@playwright/test": "^1.49.0", + "@types/node": "^22.9.0", + "axe-core": "^4.10.0", + "typescript": "^5.6.3" + } + }, + "node_modules/@axe-core/playwright": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.11.3.tgz", + "integrity": "sha512-h/kfksv4F0cVIDlKpT4700OehdRgpvuVskuQ2nb7/JmtWUXpe9ftHAPtwyXGvVSsa6SJ64A9ER7Zrzc/sIvC4w==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "axe-core": "~4.11.4" + }, + "peerDependencies": { + "playwright-core": ">= 1.0.0" + } + }, + "node_modules/@playwright/test": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz", + "integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/axe-core": { + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.4.tgz", + "integrity": "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", + "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", + "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + } + } +}