diff --git a/cmd/entire/cli/explain.go b/cmd/entire/cli/explain.go index d4a6511be..81db8e52d 100644 --- a/cmd/entire/cli/explain.go +++ b/cmd/entire/cli/explain.go @@ -816,7 +816,7 @@ func computeReachableFromMain(repo *git.Repository) map[plumbing.Hash]bool { } // Walk main's first-parent chain to build the set - _ = walkFirstParentCommits(repo, mainBranchHash, 1000, func(c *object.Commit) error { //nolint:errcheck // Best-effort + _ = walkFirstParentCommits(repo, mainBranchHash, strategy.MaxCommitTraversalDepth, func(c *object.Commit) error { //nolint:errcheck // Best-effort reachableFromMain[c.Hash] = true return nil }) diff --git a/cmd/entire/cli/hooks_geminicli_handlers.go b/cmd/entire/cli/hooks_geminicli_handlers.go index c6520179f..00d6174e1 100644 --- a/cmd/entire/cli/hooks_geminicli_handlers.go +++ b/cmd/entire/cli/hooks_geminicli_handlers.go @@ -414,9 +414,10 @@ func handleGeminiBeforeAgent() error { } if input.UserPrompt != "" { // Truncate long prompts for logging + const maxPromptPreviewLen = 100 promptPreview := input.UserPrompt - if len(promptPreview) > 100 { - promptPreview = promptPreview[:100] + "..." + if len(promptPreview) > maxPromptPreviewLen { + promptPreview = promptPreview[:maxPromptPreviewLen] + "..." } logArgs = append(logArgs, slog.String("prompt_preview", promptPreview)) } diff --git a/cmd/entire/cli/rewind.go b/cmd/entire/cli/rewind.go index 58014b68c..375fe53c6 100644 --- a/cmd/entire/cli/rewind.go +++ b/cmd/entire/cli/rewind.go @@ -1155,7 +1155,7 @@ func countCommitsBetween(repo *git.Repository, ancestor, descendant plumbing.Has count := 0 current := descendant - for count < 1000 { // Safety limit + for count < strategy.MaxCommitTraversalDepth { // Safety limit if current == ancestor { return count, nil } diff --git a/cmd/entire/cli/strategy/auto_commit.go b/cmd/entire/cli/strategy/auto_commit.go index ddc73607a..17dadea6f 100644 --- a/cmd/entire/cli/strategy/auto_commit.go +++ b/cmd/entire/cli/strategy/auto_commit.go @@ -1054,8 +1054,8 @@ func (s *AutoCommitStrategy) findReferencedCheckpoints(repo *git.Repository) map count := 0 _ = iter.ForEach(func(c *object.Commit) error { //nolint:errcheck // Best effort count++ - if count > 1000 { - return errors.New("limit reached") + if count > MaxCommitTraversalDepth { + return errStop } if visited[c.Hash] { return nil diff --git a/cmd/entire/cli/strategy/common.go b/cmd/entire/cli/strategy/common.go index ec998e090..f195a10da 100644 --- a/cmd/entire/cli/strategy/common.go +++ b/cmd/entire/cli/strategy/common.go @@ -32,6 +32,10 @@ const ( branchMaster = "master" ) +// MaxCommitTraversalDepth is the safety limit for walking git commit history. +// Prevents unbounded traversal in repositories with very long histories. +const MaxCommitTraversalDepth = 1000 + // errStop is a sentinel error used to break out of git log iteration. // Shared across strategies that iterate through git commits. // NOTE: A similar sentinel exists in checkpoint/temporary.go - this is intentional. @@ -49,7 +53,7 @@ func IsEmptyRepository(repo *git.Repository) bool { // IsAncestorOf checks if commit is an ancestor of (or equal to) target. // Returns true if target can reach commit by following parent links. -// Limits search to 1000 commits to avoid excessive traversal. +// Limits search to MaxCommitTraversalDepth commits to avoid excessive traversal. func IsAncestorOf(repo *git.Repository, commit, target plumbing.Hash) bool { if commit == target { return true @@ -65,7 +69,7 @@ func IsAncestorOf(repo *git.Repository, commit, target plumbing.Hash) bool { count := 0 _ = iter.ForEach(func(c *object.Commit) error { //nolint:errcheck // Best-effort search, errors are non-fatal count++ - if count > 1000 { + if count > MaxCommitTraversalDepth { return errStop } if c.Hash == commit { diff --git a/cmd/entire/cli/strategy/manual_commit_condensation.go b/cmd/entire/cli/strategy/manual_commit_condensation.go index 6f7475694..acb4c198d 100644 --- a/cmd/entire/cli/strategy/manual_commit_condensation.go +++ b/cmd/entire/cli/strategy/manual_commit_condensation.go @@ -532,9 +532,10 @@ func generateContextFromPrompts(prompts []string) []byte { for i, prompt := range prompts { // Truncate very long prompts for readability + const maxDisplayPromptLen = 500 displayPrompt := prompt - if len(displayPrompt) > 500 { - displayPrompt = displayPrompt[:500] + "..." + if len(displayPrompt) > maxDisplayPromptLen { + displayPrompt = displayPrompt[:maxDisplayPromptLen] + "..." } buf.WriteString(fmt.Sprintf("### Prompt %d\n\n", i+1)) buf.WriteString(displayPrompt) diff --git a/redact/redact.go b/redact/redact.go index f9212b384..1a35fe2dd 100644 --- a/redact/redact.go +++ b/redact/redact.go @@ -22,6 +22,11 @@ var secretPattern = regexp.MustCompile(`[A-Za-z0-9/+_=-]{10,}`) // and tokens which tend to have entropy well above 5.0. const entropyThreshold = 4.5 + +// RedactedPlaceholder is the replacement text used for redacted secrets. +const RedactedPlaceholder = "REDACTED" + +// String replaces high-entropy strings matching secretPattern with REDACTED. var ( gitleaksDetector *detect.Detector gitleaksDetectorOnce sync.Once @@ -98,7 +103,7 @@ func String(s string) string { prev := 0 for _, r := range merged { b.WriteString(s[prev:r.start]) - b.WriteString("REDACTED") + b.WriteString(RedactedPlaceholder) prev = r.end } b.WriteString(s[prev:])