Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
35 changes: 35 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,41 @@ return fmt.Errorf("unknown strategy: %s", name)
- `root.go` - Sets `SilenceErrors: true` on root command
- `main.go` - Checks for `SilentError` before printing

### Settings

All settings access should go through the `settings` package (`cmd/entire/cli/settings/`).

**Why a separate package:**
The `settings` package exists to avoid import cycles. The `cli` package imports `strategy`, so `strategy` cannot import `cli`. The `settings` package provides shared settings loading that both can use.

**Usage:**
```go
import "entire.io/cli/cmd/entire/cli/settings"

// Load full settings object
s, err := settings.Load()
if err != nil {
// handle error
}
if s.Enabled {
// ...
}

// Or use convenience functions
if settings.IsSummarizeEnabled() {
// ...
}
```

**Do NOT:**
- Read `.entire/settings.json` or `.entire/settings.local.json` directly with `os.ReadFile`
- Duplicate settings parsing logic in other packages
- Create new settings helpers without adding them to the `settings` package

**Key files:**
- `settings/settings.go` - `EntireSettings` struct, `Load()`, and helper methods
- `config.go` - Higher-level config functions that use settings (for `cli` package consumers)

### Logging vs User Output

- **Internal/debug logging**: Use `logging.Debug/Info/Warn/Error(ctx, msg, attrs...)` from `cmd/entire/cli/logging/`. Writes to `.entire/logs/`.
Expand Down
35 changes: 28 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,34 @@ Personal overrides, gitignored by default:

### Configuration Options

| Option | Values | Description |
| -------------------------------- | -------------------------------- | ---------------------------------------------- |
| `strategy` | `manual-commit`, `auto-commit` | Session capture strategy |
| `enabled` | `true`, `false` | Enable/disable Entire |
| `agent` | `claude-code`, `gemini`, etc. | AI agent to integrate with |
| `log_level` | `debug`, `info`, `warn`, `error` | Logging verbosity |
| `strategy_options.push_sessions` | `true`, `false` | Auto-push `entire/sessions` branch on git push |
| Option | Values | Description |
| -------------------------------------- | -------------------------------- | ---------------------------------------------- |
| `strategy` | `manual-commit`, `auto-commit` | Session capture strategy |
| `enabled` | `true`, `false` | Enable/disable Entire |
| `agent` | `claude-code`, `gemini`, etc. | AI agent to integrate with |
| `log_level` | `debug`, `info`, `warn`, `error` | Logging verbosity |
| `strategy_options.push_sessions` | `true`, `false` | Auto-push `entire/sessions` branch on git push |
| `strategy_options.summarize.enabled` | `true`, `false` | Auto-generate AI summaries at commit time |

### Auto-Summarization

When enabled, Entire automatically generates AI summaries for checkpoints at commit time. Summaries capture intent, outcome, learnings, friction points, and open items from the session.

```json
{
"strategy_options": {
"summarize": {
"enabled": true
}
}
}
```

**Requirements:**
- Claude CLI must be installed and authenticated (`claude` command available in PATH)
- Summary generation is non-blocking: failures are logged but don't prevent commits

**Note:** Currently uses Claude CLI for summary generation. Other AI backends may be supported in future versions.

### Settings Priority

Expand Down
8 changes: 8 additions & 0 deletions cmd/entire/cli/checkpoint/checkpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,14 @@ type WriteCommittedOptions struct {
// InitialAttribution is line-level attribution calculated at commit time
// comparing checkpoint tree (agent work) to committed tree (may include human edits)
InitialAttribution *InitialAttribution

// Summary is an optional AI-generated summary for this checkpoint.
// This field may be nil when:
// - summarization is disabled in settings
// - summary generation failed (non-blocking, logged as warning)
// - the transcript was empty or too short to summarize
// - the checkpoint predates the summarization feature
Summary *Summary
}

// ReadCommittedResult contains the result of reading a committed checkpoint.
Expand Down
1 change: 1 addition & 0 deletions cmd/entire/cli/checkpoint/committed.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ func (s *GitStore) writeMetadataJSON(opts WriteCommittedOptions, basePath string
TranscriptLinesAtStart: opts.TranscriptLinesAtStart,
TokenUsage: opts.TokenUsage,
InitialAttribution: opts.InitialAttribution,
Summary: opts.Summary,
}

// Merge with existing metadata if present (multi-session checkpoint)
Expand Down
33 changes: 9 additions & 24 deletions cmd/entire/cli/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
"entire.io/cli/cmd/entire/cli/checkpoint/id"
"entire.io/cli/cmd/entire/cli/logging"
"entire.io/cli/cmd/entire/cli/strategy"
"entire.io/cli/cmd/entire/cli/summarise"
"entire.io/cli/cmd/entire/cli/summarize"
"entire.io/cli/cmd/entire/cli/trailers"
"entire.io/cli/cmd/entire/cli/transcript"

Expand Down Expand Up @@ -285,7 +285,7 @@ func generateCheckpointSummary(w, _ io.Writer, store *checkpoint.GitStore, check

// Check if transcript exists
if len(result.Transcript) == 0 {
return fmt.Errorf("checkpoint %s has no transcript to summarise", checkpointID)
return fmt.Errorf("checkpoint %s has no transcript to summarize", checkpointID)
}

// Scope the transcript to only this checkpoint's portion
Expand All @@ -294,26 +294,11 @@ func generateCheckpointSummary(w, _ io.Writer, store *checkpoint.GitStore, check
return fmt.Errorf("checkpoint %s has no transcript content for this checkpoint (scoped)", checkpointID)
}

// Build condensed transcript for summarisation from the scoped portion
condensed, err := summarise.BuildCondensedTranscriptFromBytes(scopedTranscript)
if err != nil {
return fmt.Errorf("failed to parse transcript: %w", err)
}
if len(condensed) == 0 {
return fmt.Errorf("checkpoint %s transcript has no content to summarise", checkpointID)
}

input := summarise.Input{
Transcript: condensed,
FilesTouched: result.Metadata.FilesTouched,
}

// Generate summary using Claude CLI
// Generate summary using shared helper
ctx := context.Background()
logging.Info(ctx, "generating checkpoint summary")

var generator summarise.Generator = &summarise.ClaudeGenerator{}
summary, err := generator.Generate(ctx, input)
summary, err := summarize.GenerateFromTranscript(ctx, scopedTranscript, result.Metadata.FilesTouched, nil)
if err != nil {
return fmt.Errorf("failed to generate summary: %w", err)
}
Expand Down Expand Up @@ -502,14 +487,14 @@ func extractPromptsFromTranscript(transcriptBytes []byte) []string {
return nil
}

condensed, err := summarise.BuildCondensedTranscriptFromBytes(transcriptBytes)
condensed, err := summarize.BuildCondensedTranscriptFromBytes(transcriptBytes)
if err != nil {
return nil
}

var prompts []string
for _, entry := range condensed {
if entry.Type == summarise.EntryTypeUser && entry.Content != "" {
if entry.Type == summarize.EntryTypeUser && entry.Content != "" {
prompts = append(prompts, entry.Content)
}
}
Expand Down Expand Up @@ -632,16 +617,16 @@ func formatTranscriptBytes(transcriptBytes []byte, fallback string) string {
return " (none)\n"
}

condensed, err := summarise.BuildCondensedTranscriptFromBytes(transcriptBytes)
condensed, err := summarize.BuildCondensedTranscriptFromBytes(transcriptBytes)
if err != nil || len(condensed) == 0 {
if fallback != "" {
return fallback + "\n"
}
return " (failed to parse transcript)\n"
}

input := summarise.Input{Transcript: condensed}
return summarise.FormatCondensedTranscript(input)
input := summarize.Input{Transcript: condensed}
return summarize.FormatCondensedTranscript(input)
}

// formatSummaryDetails formats the detailed sections of an AI summary.
Expand Down
Loading