Merge branch 'main' into delete-manual-commit-strat-references · Entire
Merge branch 'main' into delete-manual-commit-strat-references
45be6e6 → main · gtrrz-victor · 21h ago · 15 files · +193 added / -22 removed
Changes
15
.github/workflows
- Mci.yml +3/-3
- Me2e-checkpoint-store.yml +1/-1
- Me2e-checkpoints-v2.yml +2/-2
- Me2e-isolated.yml +1/-1
- Me2e-windows.yml +1/-1
- Me2e.yml +2/-2
- Mlint.yml +2/-2
- Mpublish-git-remote-entire.yml +1/-1
- Mrelease.yml +3/-3
cmd/entire/cli
- Mimport_cmd.go +4
- Aimport_sync_notice.go +68
- Aimport_sync_notice_test.go +92
- Msetup_import.go +7
Mgo.mod +2/-2
Mgo.sum +4/-4
16 unmodified lines
17
18
19
20
20
21
22
23
14 unmodified lines
38
39
40
41
41
42
43
44
16 unmodified lines
61
62
63
64
64
65
66
67
16 unmodified lines
sudo apt-get update
sudo apt-get install -y gnome-keyring
echo 'somecredstorepass' | gnome-keyring-daemon --unlock
- uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4
- uses: jdx/mise-action@dad1bfd3df957f44999b559dd69dc1671cb4e9ea # v4
- name: Core Tests
env:
GO_TEST_PARALLEL: "16"
14 unmodified lines
sudo apt-get update
sudo apt-get install -y gnome-keyring
echo 'somecredstorepass' | gnome-keyring-daemon --unlock
- uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4
- uses: jdx/mise-action@dad1bfd3df957f44999b559dd69dc1671cb4e9ea # v4
- name: Integration Tests
env:
GO_TEST_PARALLEL: "16"
16 unmodified lines
sudo apt-get update
sudo apt-get install -y gnome-keyring
echo 'somecredstorepass' | gnome-keyring-daemon --unlock
- uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4
- uses: jdx/mise-action@dad1bfd3df957f44999b559dd69dc1671cb4e9ea # v4
- name: E2E Canary
env:
E2E_CHECKPOINT_STORE: ${{ matrix.checkpoint_store }}
M.github/workflows/ci.yml +3/-3
83 unmodified lines
84
85
86
87
87
88
89
90
83 unmodified lines
echo 'somecredstorepass' | gnome-keyring-daemon --unlock
- name: Setup mise
uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4
uses: jdx/mise-action@dad1bfd3df957f44999b559dd69dc1671cb4e9ea # v4
- name: Build entire CLI
run: go build -o /usr/local/bin/entire ./cmd/entire
M.github/workflows/e2e-checkpoint-store.yml +1/-1
48 unmodified lines
49
50
51
52
52
53
54
55
80 unmodified lines
136
137
138
139
139
140
141
142
48 unmodified lines
echo 'somecredstorepass' | gnome-keyring-daemon --unlock
- name: Setup mise
uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4
uses: jdx/mise-action@dad1bfd3df957f44999b559dd69dc1671cb4e9ea # v4
- name: Build entire CLI
run: go build -o /usr/local/bin/entire ./cmd/entire
M.github/workflows/e2e-checkpoints-v2.yml +2/-2
23 unmodified lines
24
25
26
27
27
28
29
30
23 unmodified lines
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup mise
uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4
uses: jdx/mise-action@dad1bfd3df957f44999b559dd69dc1671cb4e9ea # v4
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y tmux
M.github/workflows/e2e-isolated.yml +1/-1
22 unmodified lines
23
24
25
26
26
27
28
29
22 unmodified lines
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup mise
uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4
uses: jdx/mise-action@dad1bfd3df957f44999b559dd69dc1671cb4e9ea # v4
- name: Install Claude Code
run: |
M.github/workflows/e2e-windows.yml +1/-1
69 unmodified lines
70
71
72
73
73
74
75
76
131 unmodified lines
208
209
210
211
211
212
213
214
69 unmodified lines
persist-credentials: false
- name: Setup mise
uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4
uses: jdx/mise-action@dad1bfd3df957f44999b559dd69dc1671cb4e9ea # v4
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y tmux
131 unmodified lines
} >> "$GITHUB_OUTPUT"
- name: Notify Slack of E2E failure
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
with:
webhook: ${{ secrets.E2E_SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook
M.github/workflows/e2e.yml +2/-2
16 unmodified lines
17
18
19
20
20
21
22
23
24
25
26
26
27
28
29
16 unmodified lines
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
# Track go.mod so future Go bumps don't need a parallel workflow
# edit; release.yml uses the same pattern.
go-version-file: go.mod
- uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4
- uses: jdx/mise-action@dad1bfd3df957f44999b559dd69dc1671cb4e9ea # v4
- name: Run linters
run: mise run lint
M.github/workflows/lint.yml +2/-2
26 unmodified lines
27
28
29
30
30
31
32
33
26 unmodified lines
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
M.github/workflows/publish-git-remote-entire.yml +1/-1
27 unmodified lines
28
29
30
31
31
32
33
34
34
35
36
37
157 unmodified lines
195
196
197
198
198
199
200
201
27 unmodified lines
fetch-depth: 0
ref: ${{ env.RELEASE_TAG }}
- uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4
- uses: jdx/mise-action@dad1bfd3df957f44999b559dd69dc1671cb4e9ea # v4
- name: Setup Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
cache: false
go-version-file: go.mod
157 unmodified lines
if: ${{ always() && (needs.release.result == 'failure' || needs.mirror-nightly.result == 'failure') }}
steps:
- name: Notify Slack of release failure
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
with:
webhook: ${{ secrets.E2E_SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook
M.github/workflows/release.yml +3/-3
78 unmodified lines
79
80
81
82
83
84
85
86
87
88
78 unmodified lines
}
fmt.Fprintf(c.OutOrStdout(), "%s %d turn(s) from %d session(s) (%d already imported).
",
verb, res.TurnsImported, res.SessionsScanned, res.TurnsSkipped)
// A dry run writes nothing locally, so there is nothing to sync.
if !dryRun {
warnIfImportNotSynced(c.OutOrStdout(), res.TurnsImported > 0 || res.TurnsSkipped > 0)
}
return nil
},
}
Mcmd/entire/cli/import_cmd.go +4
package cli
import (
"fmt"
"io"
"os"
"github.com/entireio/cli/cmd/entire/cli/auth"
)
// Local auth reads, as package vars so the login heuristic's branching is
// testable without a real keyring or config dir. Production wiring is the real
// auth functions.
var (
importListContexts = auth.Contexts
importTokenForContext = auth.LoginTokenForContext
)
// importLoggedIn reports whether there is an active login the imported history
// could sync under: an ENTIRE_TOKEN env token, or a current stored login context
// that still has a token in the token store. It is local-only (env, contexts.json,
// and a token-store read) and never makes a network call, so it is safe on the
// import path.
//
// This is a presence check, not a liveness check. LoginTokenForContext returns a
// present-but-expired token without error, so a dead-but-not-removed login can
// still read as "logged in": confirming a token is actually usable needs a
// network refresh, which we deliberately avoid here (same reason the pre-push
// hook avoids ls-remote — no surprise auth prompts mid-command). That narrow
// residual false-negative (expired token → notice suppressed) is accepted to
// keep the check local and prompt-free; the common broken case this guards
// against — no context, or a context whose token was removed — is handled.
//
// Package var so tests can force the whole outcome (see #1773 review thread).
var importLoggedIn = func() bool {
if os.Getenv(auth.EnvTokenVar) != "" {
return true
}
ctxs, current, err := importListContexts()
if err != nil || current == "" {
return false
}
for _, c := range ctxs {
if c.Name == current {
tok, terr := importTokenForContext(c)
return terr == nil && tok != ""
}
}
return false
}
// warnIfImportNotSynced prints a one-time notice, when the user is not logged
// in, that imported agent history is stored locally only and will not appear in
// the Entire dashboard. It is a no-op when logged in or when nothing local was
// imported.
//
// Import writes read-only checkpoints to the local entire/checkpoints/v1 store
// and never syncs on its own; sync happens later via the git pre-push hook once
// logged in. Importing while logged out therefore succeeds locally but silently
// never reaches the dashboard — this notice surfaces that instead of leaving the
// user to discover an empty dashboard (see issue #1773).
func warnIfImportNotSynced(w io.Writer, importedLocalHistory bool) {
if !importedLocalHistory || importLoggedIn() {
return
}
fmt.Fprintln(w, "Note: you're not logged in, so this history was imported locally only and won't appear in your Entire dashboard.")
fmt.Fprintln(w, "Log in with 'entire login' before importing to have your history synced.")
}
Acmd/entire/cli/import_sync_notice.go +68
package cli
import (
"bytes"
"errors"
"strings"
"testing"
"github.com/entireio/cli/cmd/entire/cli/auth"
"github.com/entireio/cli/internal/entireclient/contexts"
)
func TestWarnIfImportNotSynced(t *testing.T) {
// Mutates the package-level importLoggedIn seam, so it cannot run in
// parallel with other tests that read it.
orig := importLoggedIn
t.Cleanup(func() { importLoggedIn = orig })
cases := []struct {
name string
loggedIn bool
imported bool
wantNotice bool
}{
{name: "logged out with imported history warns", loggedIn: false, imported: true, wantNotice: true},
{name: "logged in does not warn", loggedIn: true, imported: true, wantNotice: false},
{name: "nothing imported does not warn", loggedIn: false, imported: false, wantNotice: false},
{name: "logged in and nothing imported does not warn", loggedIn: true, imported: false, wantNotice: false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
importLoggedIn = func() bool { return tc.loggedIn }
var buf bytes.Buffer
warnIfImportNotSynced(&buf, tc.imported)
got := buf.String()
hasNotice := strings.Contains(got, "not logged in") && strings.Contains(got, "entire login")
if hasNotice != tc.wantNotice {
t.Fatalf("warnIfImportNotSynced(logged_in=%v, imported=%v): notice=%v, want %v; output=%q",
tc.loggedIn, tc.imported, hasNotice, tc.wantNotice, got)
}
})
}
}
// TestImportLoggedIn exercises the default login heuristic's branching via the
// local-read seams. The key case (#1773 review): a current context that exists
// but has no stored token must NOT count as logged in, so the sync notice still
// fires. Mutates package-level seams, so no t.Parallel.
func TestImportLoggedIn(t *testing.T) {
origCtx, origTok := importListContexts, importTokenForContext
t.Cleanup(func() { importListContexts, importTokenForContext = origCtx, origTok })
// Ensure no env token leaks in from the environment for the context cases.
t.Setenv(auth.EnvTokenVar, "")
withCurrent := func() ([]*contexts.Context, string, error) {
return []*contexts.Context{{Name: "prod"}}, "prod", nil
}
t.Run("current context with a stored token is logged in", func(t *testing.T) {
importListContexts = withCurrent
importTokenForContext = func(*contexts.Context) (string, error) { return "stored-token", nil }
if !importLoggedIn() {
t.Fatal("context with a token should count as logged in")
}
})
t.Run("current context with a missing token is NOT logged in", func(t *testing.T) {
importListContexts = withCurrent
importTokenForContext = func(*contexts.Context) (string, error) {
return "", errors.New("no token stored")
}
if importLoggedIn() {
t.Fatal("context present but token missing must not count as logged in")
}
})
t.Run("no current context is not logged in", func(t *testing.T) {
importListContexts = func() ([]*contexts.Context, string, error) { return nil, "", nil }
importTokenForContext = func(*contexts.Context) (string, error) { return "stored-token", nil }
if importLoggedIn() {
t.Fatal("no current context should not count as logged in")
}
})
t.Run("env token counts as logged in even with no context", func(t *testing.T) {
t.Setenv(auth.EnvTokenVar, "env-token")
importListContexts = func() ([]*contexts.Context, string, error) { return nil, "", nil }
if !importLoggedIn() {
t.Fatal("ENTIRE_TOKEN should count as logged in")
}
})
}
Acmd/entire/cli/import_sync_notice_test.go +92
220 unmodified lines
221
222
223
224
225
226
227
4 unmodified lines
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
220 unmodified lines
// import_cmd.go; without it only always-on secret scanning would run.
strategy.EnsureRedactionConfigured()
var importedLocalHistory bool
for _, e := range selected {
res, err := agentimport.Run(ctx, repo, e.imp, agentimport.Options{
RepoRoot: repoRoot,
fmt.Fprintf(w, "Note: could not import %s history: %v\n", e.displayName, err)
continue
}
if res.TurnsImported > 0 || res.TurnsSkipped > 0 {
importedLocalHistory = true
}
fmt.Fprintf(w, "Imported %d turn(s) from %d session(s) (%d already imported).\n",
res.TurnsImported, res.SessionsScanned, res.TurnsSkipped)
}
// Enable often runs before the user has logged in; surface once that a
// logged-out import stays local and won't reach the dashboard (issue #1773).
warnIfImportNotSynced(w, importedLocalHistory)
}
// pluralSessions renders a session count with correct pluralization.
Mcmd/entire/cli/setup_import.go +7
20 unmodified lines
21
22
23
24
24
25
26
27
28
29
29
30
31
32
20 unmodified lines
github.com/go-git/x/plugin/objectsigner/program v0.0.0-20260624122410-382b2905c041
github.com/gofrs/flock v0.13.0
github.com/google/uuid v1.6.0
github.com/mattn/go-isatty v0.0.22
github.com/mattn/go-isatty v0.0.23
github.com/mattn/go-runewidth v0.0.24
github.com/muesli/termenv v0.16.0
github.com/ogen-go/ogen v1.23.0
github.com/oklog/ulid/v2 v2.1.1
github.com/posthog/posthog-go v1.18.0
github.com/posthog/posthog-go v1.19.0
github.com/sergi/go-diff v1.4.0
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
Mgo.mod +2/-2
203 unmodified lines
204
205
206
207
208
207
208
209
210
211
33 unmodified lines
245
246
247
248
249
248
249
250
251
252
203 unmodified lines
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
github.com/mattn/go-isatty v0.0.23 h1:cYwCQTQf3HB6xUC+BtyCLZNr7IzbOmoZbmssVNzSyiQ=
github.com/mattn/go-isatty v0.0.23/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mholt/archives v0.1.6-0.20260429171216-ef71b7a32fae h1:J5ek2lGxYgdh5SMMmlNTSKLmS1x2oJQla/V0NaAH7vo=
33 unmodified lines
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posthog/posthog-go v1.18.0 h1:gCkHzRjGR0WFype95mVvCXn4AioGHdy9fdrmxxFIRls=
github.com/posthog/posthog-go v1.18.0/go.mod h1://M430hNH3e8CDv4i8SJesb26816Mpa6GIZaiP4pNQU=
github.com/posthog/posthog-go v1.19.0 h1:GsPilbAfQo8dbIo2yheTZ8pbmE5yYW3G289kXrxZxFA=
github.com/posthog/posthog-go v1.19.0/go.mod h1://M430hNH3e8CDv4i8SJesb26816Mpa6GIZaiP4pNQU=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=