Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions cmd/logos/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,13 +231,24 @@ func runCheckpoint(args []string) error {
// Before the commit, so the mark is in the file and not only on stdout.
claimed, claimErr := claims.Check(ix.Vault, c.Project, c.Verified)
c.Unseen = claimed.Unseen
// Before the commit: once this checkpoint is in the new folder, nothing
// finds the old one any more. Split covers a folder an auto record already
// took over, which nobody has been told about yet. The name is kept as
// given because Commit rewrites c.Project to its folder.
named, stranded := c.Project, session.Stranded(ix.Vault, c.Project)
if stranded == "" {
stranded = session.Split(ix.Vault, c.Project)
}
if err := session.Commit(ix.DB, ix.Vault, c); err != nil {
return err
}
fmt.Printf("checkpoint written: %s.md\n", c.Slug)
if said := secret.Summary(c.Redactions); said != "" {
fmt.Println(said + ".")
}
if stranded != "" {
fmt.Println(session.SplitNotice(named, stranded))
}
for _, said := range advice.Checkpoint(*c, earlier, dropped, advice.CLI) {
fmt.Println(said)
}
Expand Down
18 changes: 16 additions & 2 deletions internal/contextpack/pack.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,17 @@ type Pack struct {
// passed over as empty or torn, so the render can say the checkpoint below
// is the latest that could be read, not the latest written.
Unread []string `json:"unread,omitempty"`
// Stranded is the folder this project's history was read from because it
// is still where logos 0.5.6 filed a name with a symbol in it (#368). The
// render names it and the rename that moves it: the folder may hold
// another project's history as well, which only the user can judge.
Stranded string `json:"stranded,omitempty"`
// Split is the folder the history was read from until the project's own
// folder got a checkpoint nobody announced — see session.Split.
Split string `json:"split,omitempty"`
// strandedFor is the scope Stranded and Split were found for, which is the
// project's own when a worktree inherited its history, not Continuity().
strandedFor string
// Drifted is where the tree stands now, filled in only when it is not where
// the checkpoint below says it was. "Repository was: memory/bugfix ·
// fcd0331" is a claim about a moment that may be forty commits gone, and
Expand Down Expand Up @@ -246,7 +257,7 @@ func (p *Pack) Empty() bool {
// Not empty: a checkpoint was set aside, and a caller that prints
// "nothing recorded" instead would hide that two repositories share
// this name.
p.OtherRepo == "" && len(p.Unread) == 0
p.OtherRepo == "" && len(p.Unread) == 0 && p.Stranded == "" && p.Split == ""
}

const (
Expand Down Expand Up @@ -324,7 +335,7 @@ func Build(ix *index.Index, embed *provider.Provider, embedModel string, req Req
// the worktree that inherits it. An unreadable file is no verdicts:
// the rulings are then handed over as they were recorded, which is
// the state before anyone reviewed them, not a wrong one.
reviews, _ = session.Reviews(ix.Vault, scope)
reviews, _ = session.Reviews(ix.Vault, session.HistoryScope(ix.Vault, scope))
// Read before sameRepository filters: a newer checkpoint from another
// repository is set aside on purpose and said by renderOtherRepo, not
// a file that could not be read.
Expand All @@ -334,6 +345,9 @@ func Build(ix *index.Index, embed *provider.Provider, embedModel string, req Req
shown = &all[0]
}
p.Unread = session.SkippedAhead(ix.Vault, scope, shown)
p.Stranded = session.Stranded(ix.Vault, scope)
p.Split = session.Split(ix.Vault, scope)
p.strandedFor = scope
}
if err == nil && len(all) > 0 && req.Dir != "" {
all, p.OtherRepo = sameRepository(all, req.Dir)
Expand Down
2 changes: 1 addition & 1 deletion internal/contextpack/relevance.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func (p *Pack) reviveRelevant(ix *index.Index, embed *provider.Provider, embedMo
rendered[normalizeKey(f)] = true
}
}
prefix := key(session.CheckpointDir+"/"+session.SafeScope(p.historyScope)) + "/"
prefix := key(session.CheckpointDir+"/"+session.HistoryScope(ix.Vault, p.historyScope)) + "/"

matched := map[string]bool{}
for _, h := range lex {
Expand Down
12 changes: 12 additions & 0 deletions internal/contextpack/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func (p *Pack) Render() string {
p.renderWindow(&b)
p.renderOtherRepo(&b)
p.renderUnread(&b)
p.renderStranded(&b)
if line := p.costLine(); line != "" {
fmt.Fprintf(&b, "\n_%s_\n", line)
}
Expand Down Expand Up @@ -218,6 +219,17 @@ func (p *Pack) renderUnread(b *strings.Builder) {
}
}

// renderStranded says the history below came from the folder an older logos
// filed this name under, and how to move it.
func (p *Pack) renderStranded(b *strings.Builder) {
if p.Stranded != "" {
fmt.Fprintf(b, "\n_%s_\n", session.StrandedNotice(p.strandedFor, p.Stranded))
}
if p.Split != "" {
fmt.Fprintf(b, "\n_%s_\n", session.SplitNotice(p.strandedFor, p.Split))
}
}

// spendCheckpoint charges the checkpoint against its share and returns the body
// to print. It is a single blob rather than a list, so it is trimmed rather than
// truncated by item count.
Expand Down
2 changes: 1 addition & 1 deletion internal/deadend/atedit.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func AtPath(db *sql.DB, vaultDir, project, rel string) ([]Ruling, error) {
// every project with any checkpoint at all would report a phantom worktree
// scope named "_convos".
func scopesOf(vaultDir, project string) ([]string, error) {
project = session.SafeScope(project)
project = session.HistoryScope(vaultDir, project)
root := filepath.Join(vaultDir, session.CheckpointDir, filepath.FromSlash(project))
entries, err := os.ReadDir(root)
if err != nil {
Expand Down
11 changes: 11 additions & 0 deletions internal/mcpserver/continuity.go
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,14 @@ func (s *Session) checkpoint(args map[string]any, handoffTo string) (string, err
// Before the commit, so the mark is in the file and not only in this reply.
claimed, claimErr := claims.Check(s.vault, c.Project, c.Verified)
c.Unseen = claimed.Unseen
// Before the commit: once this checkpoint is in the new folder, nothing
// finds the old one any more. Split covers a folder an auto record already
// took over, which nobody has been told about yet. The name is kept as
// given because Commit rewrites c.Project to its folder.
named, stranded := c.Project, session.Stranded(s.vault, c.Project)
if stranded == "" {
stranded = session.Split(s.vault, c.Project)
}
if err := session.Commit(s.DB, s.vault, c); err != nil {
return "", err
}
Expand All @@ -555,6 +563,9 @@ func (s *Session) checkpoint(args map[string]any, handoffTo string) (string, err
if said := secret.Summary(c.Redactions); said != "" {
msg += ". " + said + "."
}
if stranded != "" {
msg += " " + session.SplitNotice(named, stranded)
}
for _, said := range advice.Checkpoint(*c, earlier, dropped, advice.MCP) {
msg += " " + said
}
Expand Down
2 changes: 1 addition & 1 deletion internal/ops/ledger.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ type Ledger struct {
// structured field, quoted and cited, and what it adds around them says what
// was recorded and when — history, not a rulebook.
func WriteLedger(vaultDir, project, repoDir string) (Ledger, error) {
scope := session.SafeScope(project)
scope := session.HistoryScope(vaultDir, project)
if scope == "" {
return Ledger{}, fmt.Errorf("project name %q has no letters or digits to find its checkpoints by", project)
}
Expand Down
35 changes: 35 additions & 0 deletions internal/ops/ledger_symbols_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package ops

import (
"database/sql"
"path/filepath"
"testing"

"github.com/Coder8124/logos/internal/session"
_ "modernc.org/sqlite"
)

// Resume reads a symbol-named project's pre-#368 history from the folder the
// old rule chose; built from SafeScope, the ledger read the new, empty one and
// showed nothing for the same project.
func TestTheLedgerReadsTheHistoryResumeShows(t *testing.T) {
dir := t.TempDir()
db, err := sql.Open("sqlite", filepath.Join(dir, "t.db"))
if err != nil {
t.Fatal(err)
}
defer db.Close()
if err := session.Init(db); err != nil {
t.Fatal(err)
}
if err := session.Commit(db, dir, &session.Checkpoint{Project: "c", Decisions: []string{"keep it, because"}}); err != nil {
t.Fatal(err)
}
l, err := WriteLedger(dir, "C++", "")
if err != nil {
t.Fatal(err)
}
if l.Checkpoints != 1 || l.InForce != 1 {
t.Errorf("ledger for C++ read %d checkpoints, %d decisions; want 1, 1", l.Checkpoints, l.InForce)
}
}
8 changes: 6 additions & 2 deletions internal/session/auto.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ func WriteAuto(vaultDir string, c Checkpoint) (Checkpoint, error) {
if safeScope(c.Project) == "" {
return Checkpoint{}, fmt.Errorf("an auto checkpoint needs a project")
}
// History is looked up under the name as given: once normalised, a name
// with a symbol no longer finds the folder 0.5.6 filed it under, and the
// record lost its link to the checkpoint before it.
asGiven := c.Project
c.Project = safeScope(c.Project)
if c.Agent == "" {
c.Agent = "agent"
Expand Down Expand Up @@ -69,7 +73,7 @@ func WriteAuto(vaultDir string, c Checkpoint) (Checkpoint, error) {

var follows string
if past {
history, err := History(vaultDir, c.Project, 0)
history, err := History(vaultDir, asGiven, 0)
if err != nil {
return Checkpoint{}, err
}
Expand All @@ -79,7 +83,7 @@ func WriteAuto(vaultDir string, c Checkpoint) (Checkpoint, error) {
break
}
}
} else if prev, _ := Latest(vaultDir, c.Project); prev != nil {
} else if prev, _ := Latest(vaultDir, asGiven); prev != nil {
follows = prev.Session
}
convo := convoFolder(c.HostSession)
Expand Down
12 changes: 7 additions & 5 deletions internal/session/checkpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ const UnattachedFolder = "_unattached"
// checkpoints from hosts without hooks both land there, together, rather
// than each inventing a folder name nobody supplied.
func convoFolder(hostSession string) string {
if s := safe(hostSession); s != "" {
if s := dropSymbols(hostSession); s != "" {
return s
}
return UnattachedFolder
Expand Down Expand Up @@ -522,7 +522,8 @@ func CheckpointEntries(dir string) ([]CheckpointEntry, error) {

// History returns up to n checkpoints for a project, newest first.
func History(vaultDir, project string, n int) ([]Checkpoint, error) {
dir := filepath.Join(vaultDir, CheckpointDir, filepath.FromSlash(safeScope(project)))
scope := historyScope(vaultDir, project)
dir := filepath.Join(vaultDir, CheckpointDir, filepath.FromSlash(scope))
entries, err := CheckpointEntries(dir)
if err != nil {
return nil, err
Expand All @@ -549,7 +550,7 @@ func History(vaultDir, project string, n int) ([]Checkpoint, error) {
if c.Empty() {
continue
}
c.Slug = filepath.ToSlash(filepath.Join(CheckpointDir, safeScope(project), strings.TrimSuffix(en.Rel, ".md")))
c.Slug = filepath.ToSlash(filepath.Join(CheckpointDir, scope, strings.TrimSuffix(en.Rel, ".md")))
out = append(out, c)
}
return out, nil
Expand All @@ -564,7 +565,8 @@ func History(vaultDir, project string, n int) ([]Checkpoint, error) {
// history. But the reader is then told an older checkpoint is where work left
// off, and a newer file was right there: that has to be said (#367).
func SkippedAhead(vaultDir, project string, shown *Checkpoint) []string {
dir := filepath.Join(vaultDir, CheckpointDir, filepath.FromSlash(safeScope(project)))
scope := historyScope(vaultDir, project)
dir := filepath.Join(vaultDir, CheckpointDir, filepath.FromSlash(scope))
entries, err := CheckpointEntries(dir)
if err != nil {
return nil
Expand All @@ -576,7 +578,7 @@ func SkippedAhead(vaultDir, project string, shown *Checkpoint) []string {
var out []string
for _, en := range entries {
if en.Name > after {
out = append(out, filepath.ToSlash(filepath.Join(CheckpointDir, safeScope(project), en.Rel)))
out = append(out, filepath.ToSlash(filepath.Join(CheckpointDir, scope, en.Rel)))
}
}
sort.Sort(sort.Reverse(sort.StringSlice(out)))
Expand Down
4 changes: 2 additions & 2 deletions internal/session/convos.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ type Conversation struct {
// present unrelated work by different agents as one sitting, which is the
// fabrication UnattachedFolder exists to avoid.
func Conversations(vaultDir, projectName string) ([]Conversation, error) {
scope := safeScope(projectName)
scope := historyScope(vaultDir, projectName)
if scope == "" {
return nil, nil
}
Expand Down Expand Up @@ -149,7 +149,7 @@ func FindConversation(convs []Conversation, query string) (*Conversation, []Conv
return nil, nil, fmt.Errorf("no conversation named")
}
for i := range convs {
if convs[i].ID == q || convs[i].ID == safe(q) {
if convs[i].ID == q || convs[i].ID == dropSymbols(q) {
return &convs[i], nil, nil
}
}
Expand Down
2 changes: 1 addition & 1 deletion internal/session/notestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ func renderNotes(scope string, notes []Note) string {
for _, n := range notes {
// oneLine, because a note holding a newline would render as a bullet the
// parser cannot read back — a lost note wearing the costume of a saved one.
fmt.Fprintf(&b, "- %s <!-- ts=%d agent=%s -->\n", oneLine(n.Text), n.TS, safe(n.Agent))
fmt.Fprintf(&b, "- %s <!-- ts=%d agent=%s -->\n", oneLine(n.Text), n.TS, dropSymbols(n.Agent))
}
return b.String()
}
Expand Down
Loading
Loading