Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
08b52a9
docs: add code-verified gap audit and Phase-1 interactive-runtime plan
Jun 21, 2026
95b678e
feat(repl): add Terminal abstraction with x/term OSTerminal and FakeT…
Jun 21, 2026
1029cfe
chore(repl): mark golang.org/x/term as a direct dependency
Jun 21, 2026
86d5882
feat(repl): add stdin byte-stream to escape-sequence segmenter
Jun 21, 2026
99d3d9f
perf(repl): reuse read buffer in SequenceScanner; clarify invalid-byt…
Jun 21, 2026
e8bfc78
feat(repl): add terminal event loop with submit, exit, and non-tty fa…
Jun 21, 2026
edfc4ab
docs(repl): document runLineMode cancel limitation and placeholder; i…
Jun 21, 2026
4436f83
feat(repl): render live turn events into the screen transcript
Jun 21, 2026
bfca0d6
feat(tool): add PermissionAsker seam so the Ask branch can prompt int…
Jun 21, 2026
041dd60
fix(tool): fail safe on non-Allow asker decisions; cover ask-error an…
Jun 21, 2026
22e8d7f
feat(repl): bridge interactive permission dialogs to the executor Asker
Jun 21, 2026
1392c78
fix(repl): queue concurrent permission asks and deny pending asks on …
Jun 21, 2026
9ab8823
feat(claude): launch interactive REPL instead of the scaffold stub
Jun 21, 2026
05c1e8b
fix(repl): cancel turn context on loop exit; guard against concurrent…
Jun 21, 2026
e797867
test(claude): replace obsolete scaffold test with interactive no-cred…
Jun 21, 2026
05ed885
test(repl): wait for asker decision with timeout instead of racy default
Jun 21, 2026
f42b4d3
docs: add master roadmap + P2-P7 TDD migration plans to 100% parity
Jun 21, 2026
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
42 changes: 39 additions & 3 deletions cmd/claude/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
remotepkg "ccgo/internal/remote"
"ccgo/internal/session"
"ccgo/internal/tool"
"ccgo/internal/repl"
filetools "ccgo/internal/tools/file"
tasktools "ccgo/internal/tools/task"
)
Expand Down Expand Up @@ -266,12 +267,40 @@ func run(args []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) int
}
return 0
}
if _, err := state.ConversationRunner(); err != nil {
effectiveMode, err := effectivePermissionMode(*permissionMode, *skipPermissions)
if err != nil {
fmt.Fprintf(stderr, "ccgo: %v\n", err)
return 1
}
ctx := context.Background()
runner, err := interactiveRunner(ctx, state, cliOptions{
Model: *modelName,
MaxTokens: *maxTokens,
MaxTurns: *maxTurns,
PermissionMode: effectiveMode,
SkipPermissions: *skipPermissions,
MCPConfig: *mcpConfig,
Stream: *stream,
SystemPrompt: *systemPrompt,
AppendSystem: *appendSystemPrompt,
AllowedTools: append([]string(nil), allowedTools...),
DeniedTools: append([]string(nil), deniedTools...),
AddDirs: append([]string(nil), addDirs...),
})
if err != nil {
fmt.Fprintf(stderr, "ccgo: %v\n", err)
return 1
}
history, err := resumeHistory(state, &runner, cliOptions{Resume: *resume, Continue: *continueMode})
if err != nil {
fmt.Fprintf(stderr, "ccgo: %v\n", err)
return 1
}
term := repl.NewOSTerminal(os.Stdin, os.Stdout)
if err := repl.RunInteractive(ctx, term, runner, history); err != nil {
fmt.Fprintf(stderr, "ccgo: %v\n", err)
return 1
}

fmt.Fprintf(stdout, "ccgo scaffold ready\nsession_id=%s\ncwd=%s\n", state.SessionID(), state.CWD())
return 0
}

Expand Down Expand Up @@ -3205,6 +3234,13 @@ func normalizeCLIFormatValue(raw string) string {
}
}

// interactiveRunner builds a fully-wired runner for the interactive REPL.
// It delegates to headlessRunner; kept as a separate seam for future
// interactive-only wiring (e.g., interactive default permission mode).
func interactiveRunner(ctx context.Context, state *bootstrap.State, options cliOptions) (conversation.Runner, error) {
return headlessRunner(ctx, state, options)
}

func headlessRunner(ctx context.Context, state *bootstrap.State, options cliOptions) (conversation.Runner, error) {
runner, err := state.ConversationRunner()
if err != nil {
Expand Down
22 changes: 11 additions & 11 deletions cmd/claude/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -984,25 +984,25 @@ func TestRunHelpExitsSuccessfully(t *testing.T) {
}
}

func TestRunCWDFlagSetsScaffoldWorkingDirectory(t *testing.T) {
func TestRunInteractiveWithoutCredentialsFails(t *testing.T) {
project := t.TempDir()
resolvedProject, err := filepath.EvalSymlinks(project)
if err != nil {
t.Fatal(err)
}
t.Setenv("CLAUDE_CONFIG_DIR", t.TempDir())
// Clear every credential env var the auth path reads:
t.Setenv("ANTHROPIC_API_KEY", "")
t.Setenv("CLAUDE_CODE_OAUTH_REFRESH_TOKEN", "")
t.Setenv("CLAUDE_CODE_OAUTH_SCOPES", "")

var stdout, stderr bytes.Buffer
code := run([]string{"--cwd", project}, strings.NewReader(""), &stdout, &stderr)
if code != 0 {
t.Fatalf("exit = %d stderr=%s", code, stderr.String())
}
if !strings.Contains(stdout.String(), "cwd="+resolvedProject) {
t.Fatalf("stdout = %q", stdout.String())
if code != 1 {
t.Fatalf("exit = %d stdout=%q stderr=%q", code, stdout.String(), stderr.String())
}
if stderr.Len() != 0 {
if !strings.Contains(stderr.String(), "missing Anthropic credentials") {
t.Fatalf("stderr = %q", stderr.String())
}
if strings.Contains(stdout.String(), "scaffold ready") {
t.Fatalf("scaffold stub should be gone, got stdout = %q", stdout.String())
}
}

func TestRunPrintReadsPromptFromStdinAndSettingsModel(t *testing.T) {
Expand Down
266 changes: 266 additions & 0 deletions docs/gap-audit-2026-06-21.md

Large diffs are not rendered by default.

347 changes: 347 additions & 0 deletions docs/superpowers/plans/2026-06-21-00-master-roadmap.md

Large diffs are not rendered by default.

Loading
Loading