Skip to content
Open
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
9 changes: 7 additions & 2 deletions internal/integrations/agentsmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import (
"github.com/marcus/nightshift/internal/config"
)

var (
agentsMDHeaderRE = regexp.MustCompile(`^#+\s*(.+)`)
agentsMDBulletRE = regexp.MustCompile(`^[-*]\s+(.+)`)
)

// AgentsMDReader reads agents.md files for agent behavior configuration.
type AgentsMDReader struct {
enabled bool
Expand Down Expand Up @@ -106,8 +111,8 @@ func parseAgentsMD(content string) agentsMDParsed {
"constraint": "safety",
}

headerRE := regexp.MustCompile(`^#+\s*(.+)`)
bulletRE := regexp.MustCompile(`^[-*]\s+(.+)`)
headerRE := agentsMDHeaderRE
bulletRE := agentsMDBulletRE

for scanner.Scan() {
line := scanner.Text()
Expand Down
48 changes: 48 additions & 0 deletions internal/integrations/bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package integrations

import "testing"

var sampleClaudeMD = `# Project

## Conventions
- Use Go standard library where possible
- Run tests before committing
- Keep functions under 50 lines

## Tasks
- Refactor database layer
- Add integration tests

## Constraints
- No external HTTP calls in unit tests
- Must support Go 1.21+
`

var sampleAgentsMD = `# Agent Configuration

## Allowed Actions
- Read any file in the repository
- Run go test and go build
- Create git branches

## Forbidden Actions
- Push to main directly
- Delete production databases
- Modify CI/CD pipelines without review

## Tool Restrictions
- No shell access outside project directory
- File writes limited to src/ and test/
`

func BenchmarkParseClaudeMD(b *testing.B) {
for b.Loop() {
_ = parseClaudeMD(sampleClaudeMD)
}
}

func BenchmarkParseAgentsMD(b *testing.B) {
for b.Loop() {
_ = parseAgentsMD(sampleAgentsMD)
}
}
11 changes: 8 additions & 3 deletions internal/integrations/claudemd.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import (
"github.com/marcus/nightshift/internal/config"
)

var (
claudeMDHeaderRE = regexp.MustCompile(`^#+\s*(.+)`)
claudeMDBulletRE = regexp.MustCompile(`^[-*]\s+(.+)`)
)

// ClaudeMDReader reads claude.md files for project context.
type ClaudeMDReader struct {
enabled bool
Expand Down Expand Up @@ -95,9 +100,9 @@ func parseClaudeMD(content string) claudeMDParsed {
"safety": HintConstraint,
}

// Patterns for extracting hints
headerRE := regexp.MustCompile(`^#+\s*(.+)`)
bulletRE := regexp.MustCompile(`^[-*]\s+(.+)`)
// Patterns for extracting hints (compiled at package level)
headerRE := claudeMDHeaderRE
bulletRE := claudeMDBulletRE

for scanner.Scan() {
line := scanner.Text()
Expand Down
5 changes: 4 additions & 1 deletion internal/reporting/run_report.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ func RenderRunReport(results *RunResults, logPath string) (string, error) {
return "", fmt.Errorf("results cannot be nil")
}

var completed, failed, skipped []TaskResult
n := len(results.Tasks)
completed := make([]TaskResult, 0, n)
failed := make([]TaskResult, 0, n)
skipped := make([]TaskResult, 0, n)
for _, task := range results.Tasks {
switch task.Status {
case "completed":
Expand Down
2 changes: 1 addition & 1 deletion internal/stats/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ func (s *Stats) loadReports() []*reporting.RunResults {
return nil
}

var results []*reporting.RunResults
results := make([]*reporting.RunResults, 0, len(entries))
for _, entry := range entries {
if entry.IsDir() {
continue
Expand Down
35 changes: 20 additions & 15 deletions internal/tmux/scraper.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,16 +194,27 @@ func ScrapeCodexUsage(ctx context.Context) (UsageResult, error) {
var claudeWeekRegex = regexp.MustCompile(`(?i)current\s+week`)
var codexWeekRegex = regexp.MustCompile(`(?i)weekly\s+limit`)

// Pre-compiled regexes for parse functions (avoid recompiling on every call).
var (
claudeWeeklyAllModelsPctRe = regexp.MustCompile(`(?is)current\s+week\s*\(all\s+models\).*?(\d{1,3}(?:\.\d+)?)%`)
claudeWeeklyFallbackPctRe = regexp.MustCompile(`(?is)current\s+week.*?(\d{1,3}(?:\.\d+)?)%`)
codexWeeklyPctRe = regexp.MustCompile(`(?i)weekly\s+limit[^\n]*?(\d{1,3}(?:\.\d+)?)%\s*(left|used)?`)
claudeSessionResetRe = regexp.MustCompile(`(?is)current\s+session.*?resets\s+(.+?)(?:\n|$)`)
claudeWeeklyResetRe = regexp.MustCompile(`(?is)current\s+week\s*\(all\s+models\).*?resets\s+(.+?)(?:\n|$)`)
codexSessionResetRe = regexp.MustCompile(`(?i)5h\s+limit[^\n]*\(resets\s+(\d{1,2}:\d{2}(?:\s+on\s+\d{1,2}\s+\w+)?)\)`)
codexWeeklyResetRe = regexp.MustCompile(`(?i)weekly\s+limit[^\n]*\(resets\s+(\d{1,2}:\d{2}\s+on\s+\d{1,2}\s+\w+)\)`)
codexResetFallbackRe = regexp.MustCompile(`\(resets\s+(\d{1,2}:\d{2}\s+on\s+\d{1,2}\s+\w+)\)`)
)

func parseClaudeWeeklyPct(output string) (float64, error) {
output = StripANSI(output)
// Match "Current week" followed by a percentage, possibly on the next line.
// The (?s) flag makes . match newlines so the pattern crosses lines.
re := regexp.MustCompile(`(?is)current\s+week\s*\(all\s+models\).*?(\d{1,3}(?:\.\d+)?)%`)
if match := re.FindStringSubmatch(output); len(match) == 2 {
if match := claudeWeeklyAllModelsPctRe.FindStringSubmatch(output); len(match) == 2 {
return parsePct(match[1])
}
// Fallback: any "Current week" header followed by a percentage
re2 := regexp.MustCompile(`(?is)current\s+week.*?(\d{1,3}(?:\.\d+)?)%`)
re2 := claudeWeeklyFallbackPctRe
if match := re2.FindStringSubmatch(output); len(match) == 2 {
return parsePct(match[1])
}
Expand All @@ -213,8 +224,7 @@ func parseClaudeWeeklyPct(output string) (float64, error) {
func parseCodexWeeklyPct(output string) (float64, error) {
output = StripANSI(output)
// Codex /status shows "77% left" -- extract the number and qualifier.
re := regexp.MustCompile(`(?i)weekly\s+limit[^\n]*?(\d{1,3}(?:\.\d+)?)%\s*(left|used)?`)
if match := re.FindStringSubmatch(output); len(match) >= 2 {
if match := codexWeeklyPctRe.FindStringSubmatch(output); len(match) >= 2 {
pct, err := parsePct(match[1])
if err != nil {
return 0, err
Expand Down Expand Up @@ -297,15 +307,13 @@ func parseClaudeResetTimes(output string) (sessionReset, weeklyReset string) {

// Session reset: appears after "Current session" and before "Current week".
// Format: "Resets 9pm (America/Los_Angeles)" or "Resets 8:59pm (America/Los_Angeles)"
sessionRe := regexp.MustCompile(`(?is)current\s+session.*?resets\s+(.+?)(?:\n|$)`)
if m := sessionRe.FindStringSubmatch(output); len(m) == 2 {
if m := claudeSessionResetRe.FindStringSubmatch(output); len(m) == 2 {
sessionReset = strings.TrimSpace(m[1])
}

// Weekly reset: appears after "Current week (all models)".
// Format: "Resets Feb 8 at 10am (America/Los_Angeles)" or "Resets Feb 8 at 9:59am (America/Los_Angeles)"
weeklyRe := regexp.MustCompile(`(?is)current\s+week\s*\(all\s+models\).*?resets\s+(.+?)(?:\n|$)`)
if m := weeklyRe.FindStringSubmatch(output); len(m) == 2 {
if m := claudeWeeklyResetRe.FindStringSubmatch(output); len(m) == 2 {
weeklyReset = strings.TrimSpace(m[1])
}

Expand All @@ -323,14 +331,12 @@ func parseCodexResetTimes(output string) (sessionReset, weeklyReset string) {
output = StripANSI(output)

// Session (5h) reset: "(resets HH:MM)" or "(resets HH:MM on D Mon)"
sessionRe := regexp.MustCompile(`(?i)5h\s+limit[^\n]*\(resets\s+(\d{1,2}:\d{2}(?:\s+on\s+\d{1,2}\s+\w+)?)\)`)
if m := sessionRe.FindStringSubmatch(output); len(m) == 2 {
if m := codexSessionResetRe.FindStringSubmatch(output); len(m) == 2 {
sessionReset = m[1]
}

// Weekly reset: "(resets HH:MM on D Mon)"
weeklyRe := regexp.MustCompile(`(?i)weekly\s+limit[^\n]*\(resets\s+(\d{1,2}:\d{2}\s+on\s+\d{1,2}\s+\w+)\)`)
if m := weeklyRe.FindStringSubmatch(output); len(m) == 2 {
if m := codexWeeklyResetRe.FindStringSubmatch(output); len(m) == 2 {
weeklyReset = m[1]
}

Expand All @@ -339,8 +345,7 @@ func parseCodexResetTimes(output string) (sessionReset, weeklyReset string) {
// Only use the fallback when we find a match distinct from the session reset
// (avoids misidentifying the 5h line as weekly when it's the only line).
if weeklyReset == "" {
fallbackRe := regexp.MustCompile(`\(resets\s+(\d{1,2}:\d{2}\s+on\s+\d{1,2}\s+\w+)\)`)
matches := fallbackRe.FindAllStringSubmatch(output, -1)
matches := codexResetFallbackRe.FindAllStringSubmatch(output, -1)
if len(matches) > 0 {
candidate := matches[len(matches)-1][1]
if candidate != sessionReset {
Expand Down
44 changes: 44 additions & 0 deletions internal/tmux/scraper_bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package tmux

import "testing"

// Representative Claude /usage output for benchmarks.
var claudeUsageOutput = `
Current session
████████████████████░░░░░░░░ 0% used
Resets 9pm (America/Los_Angeles)

Current week (all models)
████████████████████░░░░░░░░ 59% used
Resets Feb 8 at 10am (America/Los_Angeles)
`

// Representative Codex /status output for benchmarks.
var codexStatusOutput = `
5h limit: [████████████████████░░░░] 100% left (resets 02:50 on 8 Feb)
Weekly limit: [██████░░░░░░░░░░░░░░░░░░] 77% left (resets 20:08 on 9 Feb)
`

func BenchmarkParseClaudeWeeklyPct(b *testing.B) {
for b.Loop() {
_, _ = parseClaudeWeeklyPct(claudeUsageOutput)
}
}

func BenchmarkParseCodexWeeklyPct(b *testing.B) {
for b.Loop() {
_, _ = parseCodexWeeklyPct(codexStatusOutput)
}
}

func BenchmarkParseClaudeResetTimes(b *testing.B) {
for b.Loop() {
_, _ = parseClaudeResetTimes(claudeUsageOutput)
}
}

func BenchmarkParseCodexResetTimes(b *testing.B) {
for b.Loop() {
_, _ = parseCodexResetTimes(codexStatusOutput)
}
}
2 changes: 1 addition & 1 deletion internal/trends/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func (a *Analyzer) getHourlyAverages(provider string, lookbackDays int) ([]hourl
}
defer func() { _ = rows.Close() }()

values := make([]hourlyAverage, 0)
values := make([]hourlyAverage, 0, 24)
for rows.Next() {
var avg hourlyAverage
if err := rows.Scan(&avg.hour, &avg.avg); err != nil {
Expand Down
Loading