fix(onboarding): address trail 742 review findings · Entire

fix(onboarding): address trail 742 review findings

ace2530·

peyton-alt·15h ago·5 files·+148 added/-25 removed

Trail-Finding: 019f6d3c-1848-7aa7-918d-c6516a50fc23 Trail-Finding: 019f6d07-41fa-71d5-91d9-26ef8755761d Trail-Finding: 019f6cd3-b47c Co-Authored-By: Claude Fable 5 noreply@anthropic.com

Sessions

01KXR7HPN0FAFCHCPKB4MHM2SWView transcript

Changes

5

445 unmodified lines

445 unmodified lines

healMirrorProbeCache(owner, repo)
        return outcome, nil
    }
    healMirrorProbeCache(owner, repo)
    if noWait {
        // Placement registered, clone continuing server-side — nothing more
        // to learn now, so record the serving placement.
        healMirrorProbeCache(owner, repo)
        return outcome, nil
    }
    status, werr := awaitMirrorReady(ctx, c, created.MirrorId, timeout, onStatus)
    outcome.status = status
    outcome.polled = true
    // Write through only on a confirmed-ready clone: caching at create time
    // would keep the checklist green for the cache TTL even when the clone
    // failed moments later. A timeout leaves the cache alone — the next live
    // probe reads the truth.
    if werr == nil && status == coreapi.MirrorStatusReady {
        healMirrorProbeCache(owner, repo)
    }
    return outcome, werr
}

// healMirrorProbeCache records a serving placement in the onboarding probe
// cache — the setup checklist's own hint is `entire repo mirror create
// <slug>`, and a cached "not mirrored" must not survive the prescribed
// remediation. A suspended placement never serves, so callers write through
// only after every suspension signal is checked (CreateMirror's Suspended
// flag, plus the GetMirror read for existing empty-upstream placements).
// remediation. Callers write through only once the placement's outcome is
// settled enough to trust for a TTL: after every suspension signal is
// checked (CreateMirror's Suspended flag, plus the GetMirror read for
// existing empty-upstream placements), and — when a clone is awaited — only
// on a confirmed-ready status, never before a clone that may still fail.
func healMirrorProbeCache(owner, repo string) {
    slugOwner, slugRepo := githubSlug(owner, repo)
    defaultMirrorProbeCache().put(mirrorProbeKey(slugOwner+"/"+slugRepo), mirrorProbeResult{Mirrored: true}, time.Now())
}

Mcmd/entire/cli/repo_mirror.go+15/-4

290 unmodified lines

290 unmodified lines

}) }

// A clone that fails after registration must not leave a green probe-cache // entry — the checklist would show ✓ for the cache TTL while the mirror never // became usable. A confirmed-ready clone and a --no-wait registration (where // there is genuinely nothing more to know yet) still write through. // Not parallel: redirects the probe cache and shortens the poll interval. func TestCreateAndAwaitMirror_CacheWaitsForCloneOutcome(t *testing.T) { t.Setenv("XDG_CACHE_HOME", t.TempDir()) prev := mirrorPollInterval mirrorPollInterval = time.Millisecond t.Cleanup(func() { mirrorPollInterval = prev }) ctx := t.Context()

serve := func(t *testing.T, status coreapi.MirrorStatus) *coreapi.Client { t.Helper() created := &coreapi.CreatedMirror{Created: true, MirrorId: "m1"} srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") switch { case r.Method == http.MethodPost && r.URL.Path == mirrorsAPIPath: w.WriteHeader(http.StatusCreated) if err := printJSON(w, created); err != nil { t.Errorf("encode created response: %v", err) } case r.Method == http.MethodGet && strings.HasPrefix(r.URL.Path, "/api/v1/mirrors/"): m := &coreapi.Mirror{} m.Status = coreapi.NewOptMirrorStatus(status) if err := printJSON(w, m); err != nil { t.Errorf("encode mirror response: %v", err) } default: t.Errorf("unexpected request %s %s", r.Method, r.URL.Path) w.WriteHeader(http.StatusNotFound) } })) t.Cleanup(srv.Close) c, err := coreapi.NewWithBearer(srv.URL, "tok") require.NoError(t, err) return c }

t.Run("failed clone is not cached as mirrored", func(t *testing.T) { c := serve(t, coreapi.MirrorStatusFailed) _, err := createAndAwaitMirror(ctx, c, "fail", "r", "c", false, time.Second, nil, nil) require.ErrorIs(t, err, errMirrorCloneFailed) _, _, ok := defaultMirrorProbeCache().get(mirrorProbeKey("fail/r"), time.Now()) require.False(t, ok, "a failed clone must not write through to the probe cache") })

t.Run("ready clone is cached", func(t *testing.T) { c := serve(t, coreapi.MirrorStatusReady) _, err := createAndAwaitMirror(ctx, c, "ok2", "r", "c", false, time.Second, nil, nil) require.NoError(t, err) probe, _, ok := defaultMirrorProbeCache().get(mirrorProbeKey("ok2/r"), time.Now()) require.True(t, ok, "a ready clone should be cached") require.True(t, probe.Mirrored) })

t.Run("no-wait registration is cached", func(t *testing.T) { c := serve(t, coreapi.MirrorStatusReady) _, err := createAndAwaitMirror(ctx, c, "nw", "r", "c", true, time.Second, nil, nil) require.NoError(t, err) _, _, ok := defaultMirrorProbeCache().get(mirrorProbeKey("nw/r"), time.Now()) require.True(t, ok, "a no-wait registration should be cached") }) }

func countEq(xs []string, want string) int { n := 0 for _, x := range xs {


Mcmd/entire/cli/repo_mirror_test.go+67

844 unmodified lines

844 unmodified lines

// the initial commit captures the .entire/, .claude/, hooks, and // settings files that setup writes. var bootstrap *bootstrapState loggingReady := false if _, err := paths.WorktreeRoot(ctx); err != nil { bootstrapOpts.Yes = opts.Yes state, bootstrapErr := runGitHubBootstrapInit(ctx, cmd.OutOrStdout(), cmd.ErrOrStderr(), bootstrapOpts)

// Visual separator between bootstrap init and agent setup.
printBootstrapSection(cmd.OutOrStdout(), "Enabling Entire")
// Logging must initialize BEFORE the finalize defer below is
// registered: defers run LIFO, so Close (registered here,
// first) runs after the deferred ladder — whose probe/offer
// warnings would otherwise be written to a closed log file
// and silently dropped. Safe at this point: phase 1 just
// created the repo, so Init can't leave a stray .entire/ in
// a folder whose bootstrap was declined.
defer initEnableLogging(ctx)()
loggingReady = true
// On the way out (if setup succeeded), create the initial
// commit and push to the GitHub repo. If setup returned an
// error, skip the finalize — the user can fix the issue and
// retry.

// Operational logging (ladder probes, offer failures) belongs in
// .entire/logs — without Init the logging package falls back to
// slog's stderr default and WARN lines leak verbatim into
// enable's output. After the bootstrap block, so a declined
// bootstrap doesn't leave a stray .entire/ in a non-repo folder.
logging.SetLogLevelGetter(GetLogLevel)
if lerr := logging.Init(ctx, ""); lerr == nil {
    defer logging.Close()
}

// After the bootstrap block, so a declined bootstrap doesn't
// leave a stray .entire/ in a non-repo folder; the bootstrap
// path initialized logging inside the block instead (before its
// finalize defer — see the LIFO note there).
if !loggingReady {
    defer initEnableLogging(ctx)()
}

if err := validateSetupFlags(opts.UseLocalSettings, opts.UseProjectSettings); err != nil {

...

}

Mcmd/entire/cli/setup.go+31/-8

602 unmodified lines

602 unmodified lines

// success path (mirrors writeAgentHelpHint, which only renders when set up).
    AgentHelp string `json:"agent_help,omitempty"`
    // Setup maps each onboarding rung (hooks, auth, mirror, import) to its
    // state: done, missing, blocked, unknown, or not_applicable. Present only
    // when enabled — same gate as the human checklist.
    Setup map[string]string `json:"setup,omitempty"`
    Error string            `json:"error,omitempty"`
    // state plus the same detail and remediation command the human checklist
    // shows — the state name alone isn't actionable for a non-interactive
    // agent (Agent-Safe CLI Fallbacks). Present only when enabled — same gate
    // as the human checklist.
    Setup map[string]setupRungJSON `json:"setup,omitempty"`
    Error string                   `json:"error,omitempty"`
}

// setupRungJSON is one onboarding rung in `entire status --json`.
type setupRungJSON struct {
    // State: done, missing, blocked, unknown, or not_applicable.
    State string `json:"state"`
    // Detail mirrors the human checklist row's annotation, e.g.
    // "2 claude-code sessions found, not imported".
    Detail string `json:"detail,omitempty"`
    // Hint is the remediation command for this rung, e.g.
    // "entire repo mirror create github.com/owner/repo".
    Hint string `json:"hint,omitempty"`
}

type sessionBriefJSON struct {
...
}

Mcmd/entire/cli/status.go+24/-6

2110 unmodified lines

2110 unmodified lines

writeSettings(t, testSettingsEnabled) stubOnboardingResults(t, []onboarding.Result{ {Rung: onboarding.Rung{Key: onboarding.KeyHooks, Title: "Agent hooks"}, Check: onboarding.Check{State: onboarding.StateDone}}, {Rung: onboarding.Rung{Key: onboarding.KeyAuth, Title: "Logged in"}, Check: onboarding.Check{State: onboarding.StateMissing}}, {Rung: onboarding.Rung{Key: onboarding.KeyMirror, Title: "Repo mirrored"}, Check: onboarding.Check{State: onboarding.StateBlocked}}, {Rung: onboarding.Rung{Key: onboarding.KeyAuth, Title: "Logged in"}, Check: onboarding.Check{State: onboarding.StateMissing, Hint: "entire auth login"}}, {Rung: onboarding.Rung{Key: onboarding.KeyMirror, Title: "Repo mirrored"}, Check: onboarding.Check{State: onboarding.StateBlocked, Detail: "needs login", Hint: "entire auth login"}}, })

var stdout bytes.Buffer

var parsed struct { Setup map[string]string json:"setup" Setup map[string]setupRungJSON json:"setup" } if err := json.Unmarshal(stdout.Bytes(), &parsed); err != nil { t.Fatalf("unmarshal status JSON: %v\noutput: %s", err, stdout.String()) } want := map[string]string{"hooks": "done", "auth": "missing", "mirror": "blocked"}; for key, state := range want { if parsed.Setup[key] != state { t.Errorf("setup[%q] = %q, want %q", key, parsed.Setup[key], state) } } want := map[string]setupRungJSON{ "hooks": {State: "done"}, "auth": {State: "missing", Hint: "entire auth login"}, "mirror": {State: "blocked", Detail: "needs login", Hint: "entire auth login"}, }; for key, rung := range want { if parsed.Setup[key] != rung { t.Errorf("setup[%q] = %+v, want %+v", key, parsed.Setup[key], rung) } } }


Mcmd/entire/cli/status_test.go+11/-7