package session import ( "fmt" "sort" "strings" ) // DetectExternalChanges compares the current workspace state against the // EndManifest recorded in .ctask/last-session-summary.json. // // Returns (nil, nil) if no summary exists (pre-v0.4 workspace or first // session ever) — callers should treat this as "silently skip the warning". // // Returns a non-nil diff with empty slices when nothing changed outside a // ctask session. func DetectExternalChanges(wsDir string) (*ManifestDiff, error) { summary, err := ReadSummary(SummaryPath(wsDir)) if err != nil { return nil, err } if summary == nil { return nil, nil } previous := &Manifest{ CapturedAt: summary.EndedAt, Files: append([]FileEntry{}, summary.EndManifest...), } current, err := CaptureManifest(wsDir) if err != nil { return nil, err } return DiffManifests(previous, current), nil } // FormatStaleWarning renders the Layer-3 warning shown when the workspace // was modified outside a ctask session since the last session ended. func FormatStaleWarning(summary *SessionSummary, diff *ManifestDiff) string { var b strings.Builder b.WriteString("[ctask] Workspace modified since last session ended:\n\n") fmt.Fprintf(&b, " Last session: %s (%s, %s)\n\n", summary.EndedAt.Local().Format("2006-01-02 15:04"), summary.Hostname, summary.Agent) b.WriteString(" Modified since then:\n") lines := append([]string{}, diff.Modified...) sort.Strings(lines) for _, f := range lines { fmt.Fprintf(&b, " %s (modified)\n", f) } added := append([]string{}, diff.Added...) sort.Strings(added) for _, f := range added { fmt.Fprintf(&b, " %s (new file)\n", f) } deleted := append([]string{}, diff.Deleted...) sort.Strings(deleted) for _, f := range deleted { fmt.Fprintf(&b, " %s (deleted)\n", f) } b.WriteString("\n These changes were not made during a ctask session.\n") b.WriteString(" Review before continuing? [Y/n] ") return b.String() } // HasChanges returns true if diff reports any added/modified/deleted entries. func HasChanges(d *ManifestDiff) bool { if d == nil { return false } return len(d.Added) > 0 || len(d.Modified) > 0 || len(d.Deleted) > 0 }