Skip to content

Commit 355bc81

Browse files
committed
test message
2 parents f0b7a2b + 2092390 commit 355bc81

4 files changed

Lines changed: 84 additions & 4 deletions

File tree

cmd/iterate/repl.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,7 @@ func suggestAtFiles(prompt, repoPath string) {
589589
var candidates []string
590590
seen := make(map[string]bool)
591591
for _, w := range words {
592+
// Strip trailing punctuation.
592593
w = strings.TrimRight(w, ".,;:!?\"')")
593594
if len(w) < minWordLen {
594595
continue

cmd/iterate/repl_streaming.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,9 +387,43 @@ func printFinalStats(elapsed, ttft time.Duration, beforeTokens int, requestCostU
387387
selector.PrintStatusLine(elapsed, delta)
388388
fmt.Println()
389389

390+
// Proactive context window warnings — shown once per threshold crossing.
391+
printContextWarning(sess.InputTokens + sess.OutputTokens)
392+
390393
slog.Debug("request completed", "elapsed_ms", elapsed.Milliseconds(), "ttft_ms", ttft.Milliseconds(), "response_chars", len(fullContent), "total_tokens", sess.Tokens, "cost_usd", requestCostUSD)
391394
}
392395

396+
// contextWarnState tracks which threshold warnings have been shown this session.
397+
var contextWarnState struct {
398+
shown70 bool
399+
shown85 bool
400+
shown95 bool
401+
}
402+
403+
// printContextWarning prints a one-time advisory when the context crosses 70/85/95%.
404+
func printContextWarning(usedTokens int) {
405+
window := selector.ContextWindow
406+
if window <= 0 {
407+
window = 200_000
408+
}
409+
pct := float64(usedTokens) * 100 / float64(window)
410+
411+
switch {
412+
case pct >= 95 && !contextWarnState.shown95:
413+
contextWarnState.shown95 = true
414+
fmt.Printf("%s⚠ Context at %.0f%% — use /compact or /compact llm to free space%s\n\n",
415+
colorRed, pct, colorReset)
416+
case pct >= 85 && !contextWarnState.shown85:
417+
contextWarnState.shown85 = true
418+
fmt.Printf("%s⚠ Context at %.0f%% — approaching limit, consider /compact%s\n\n",
419+
colorYellow, pct, colorReset)
420+
case pct >= 70 && !contextWarnState.shown70:
421+
contextWarnState.shown70 = true
422+
fmt.Printf("%s Context at %.0f%% — use /context to monitor usage%s\n\n",
423+
colorDim, pct, colorReset)
424+
}
425+
}
426+
393427
// authErrorHint returns a human-readable fix suggestion for known API error
394428
// patterns, or an empty string when the error doesn't look auth-related.
395429
func authErrorHint(errMsg string) string {

internal/commands/mode.go

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package commands
22

33
import (
4+
"context"
45
"fmt"
56
"os"
67
"os/exec"
@@ -69,6 +70,13 @@ func registerDisplayCommands(r *Registry) {
6970
"/explain", "explain code in path", cmdExplain,
7071
"/view", "view file with line numbers", cmdView,
7172
)
73+
r.Register(Command{
74+
Name: "/chain",
75+
Aliases: []string{},
76+
Description: "run prompts sequentially, separated by ;; (e.g. /chain fix tests ;; commit the changes)",
77+
Category: "mode",
78+
Handler: cmdChain,
79+
})
7280
registerDisplayNavCommands(r)
7381
}
7482

@@ -283,7 +291,7 @@ func cmdSummarize(ctx Context) Result {
283291
"what was implemented, and any decisions made. Be brief.\n\n"+
284292
"(Conversation has %d messages)", len(msgs))
285293
if ctx.REPL.StreamAndPrint != nil {
286-
ctx.REPL.StreamAndPrint(nil, ctx.Agent, prompt, ctx.RepoPath)
294+
ctx.REPL.StreamAndPrint(context.Background(), ctx.Agent, prompt, ctx.RepoPath)
287295
} else {
288296
PrintError("agent stream not available")
289297
}
@@ -542,3 +550,40 @@ func cmdRender(ctx Context) Result {
542550
}
543551
return Result{Handled: true}
544552
}
553+
554+
// cmdChain runs multiple prompts sequentially, separated by ";;".
555+
// Example: /chain write tests for auth.go ;; run the tests ;; fix any failures
556+
func cmdChain(ctx Context) Result {
557+
if ctx.REPL.StreamAndPrint == nil || ctx.Agent == nil {
558+
PrintError("agent not available")
559+
return Result{Handled: true}
560+
}
561+
562+
raw := ctx.Args()
563+
if raw == "" {
564+
fmt.Printf("%sUsage: /chain prompt1 ;; prompt2 ;; prompt3%s\n", ColorDim, ColorReset)
565+
fmt.Printf("%sEach step runs after the previous one completes.%s\n\n", ColorDim, ColorReset)
566+
return Result{Handled: true}
567+
}
568+
569+
steps := strings.Split(raw, ";;")
570+
var prompts []string
571+
for _, s := range steps {
572+
s = strings.TrimSpace(s)
573+
if s != "" {
574+
prompts = append(prompts, s)
575+
}
576+
}
577+
if len(prompts) == 0 {
578+
PrintError("no prompts found — separate them with ;;")
579+
return Result{Handled: true}
580+
}
581+
582+
fmt.Printf("%s── Chain: %d steps ──────────────────────%s\n\n", ColorDim, len(prompts), ColorReset)
583+
for i, prompt := range prompts {
584+
fmt.Printf("%s[%d/%d]%s %s%s%s\n\n", ColorDim, i+1, len(prompts), ColorReset, ColorYellow, prompt, ColorReset)
585+
ctx.REPL.StreamAndPrint(context.Background(), ctx.Agent, prompt, ctx.RepoPath)
586+
}
587+
fmt.Printf("%s── Chain complete ───────────────────────%s\n\n", ColorLime, ColorReset)
588+
return Result{Handled: true}
589+
}

internal/evolution/engine.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,9 @@ func (e *Engine) loadPRState() {
167167
}
168168

169169
// Validate the PR is still open on GitHub before trusting the state.
170-
// This guards against the scenario where the PR was merged/closed/deleted
171-
// externally and pr_state.json was not cleared (e.g. a crash in phase 5).
172-
if state.PRNumber > 0 && e.repo != "" {
170+
// Only done in CI (GITHUB_ACTIONS=true) to avoid hitting the API in tests
171+
// or local development where credentials may not be available.
172+
if state.PRNumber > 0 && e.repo != "" && os.Getenv("GITHUB_ACTIONS") == "true" {
173173
out, ghErr := e.runGHPRState(state.PRNumber)
174174
if ghErr != nil || (out != "OPEN" && out != "") {
175175
e.logger.Warn("pr_state.json refers to a non-open PR, clearing it",

0 commit comments

Comments
 (0)