8000 feat(devel): /init command — AI-powered Enkan project generator by kawasima · Pull Request #170 · enkan/enkan · GitHub
[go: up one dir, main page]

Skip to content

feat(devel): /init command — AI-powered Enkan project generator - #170

Merged
kawasima merged 14 commits into
developfrom
feature/init-command-ai
Apr 6, 2026
Merged

kawasima merged 14 commits into
developfrom
feature/init-command-ai

Conversation

@kawasima
@kawasima kawasima commented Apr 5, 2026 •
Copy link
Copy Markdown
Collaborator

Summary

  • Adds a /init REPL command that interactively collects project requirements and calls an OpenAI-compatible chat completion API (OpenAI, Anthropic's OpenAI-compat endpoint, LM Studio, Ollama, etc.) to generate a complete Enkan application
  • Extends the REPL protocol with a NEED_INPUT status for multi-turn prompts
  • Reference files are fetched from GitHub at runtime (in parallel) so no files are bundled in the jar
  • After generation, mvn compile is run in a fix-loop (max 3 attempts), then the app can be auto-started and the REPL client auto-connected
  • Updates the browser REPL (enkan-repl.js) and CLI client so NEED_INPUT prompts render on the same line as the cursor

Changes

Protocol extension (enkan-system)

  • ReplResponse.ResponseStatus: added NEED_INPUT enum value
  • Transport: added default methods sendPrompt(String), startSpinner(String), stopSpinner(), requestConnect(int) — all no-ops by default so existing transports are unaffected

New command (enkan-repl-client)

  • InitCommand (client-local command): collects description/name/groupId/outputDir via interactive prompts, generates and reviews an implementation plan with the LLM, then writes fixed templates (DevMain.java, pom.xml, SystemFactory skeleton) + AI-generated files, compiles with a fix-loop, and optionally launches the app + auto-connects
  • JLineTransport: Transport backed by a JLine LineReader for running client-local commands (spinner animation, prompt handling, connect callback, AutoCloseable for scheduler shutdown)
  • ReplClient: registers /init as a client-local command, adds ANSI-colored prompt showing connection state, handles NEED_INPUT by keeping the cursor on the same line and signalling the main loop to collect input

Browser REPL (enkan-devel/enkan-repl.js)

  • ws.onmessage handles NEED_INPUT: renders prompt with term.write (no newline) and unlocks waitingForResponse

Configuration

Env var System property Default Notes
ENKAN_AI_API_URL enkan.ai.apiUrl https://api.anthropic.com/v1 Anthropic's OpenAI-compatible endpoint. The URL may include /chat/completions or not — it's normalized.
ENKAN_AI_API_KEY enkan.ai.apiKey (required) Bearer token
ENKAN_AI_MODEL enkan.ai.model claude-sonnet-4-5 Any model the endpoint supports

Usage

enkan ✗  ❯ /init

  Enkan Project Generator

── Project Setup ─────────────────────────
? What kind of application do you want to build? REST API for a bookstore
? Project name [rest-api-bookstore]: 
? Group ID [com.example]: 
? Output directory [./rest-api-bookstore]: 

── Plan Draft ────────────────────────────
(LLM-generated plan shown here)
Plan feedback (say 'yes' to proceed, or describe changes, 'cancel' to abort) > yes

── Generating ───────────────────────────
10000
─
  ✓ src/main/java/.../BookstoreApplicationFactory.java
  ✓ src/main/java/.../BookController.java
  ...

✓ Done! Created 5 file(s)

── Compiling ─────────────────────────────
✓ Compilation succeeded.

? Start and connect to the generated app? [Y]: 
⚡ Starting app (mvn compile exec:exec -Pdev)...
Connected to server (port = 3001)
enkan(3001)❯ 

Test plan

  • mvn test -pl enkan-repl-client -am -DexcludedGroups=integration passes (27 tests in InitCommandTest, 4 in ReplClientLocalHelpTest)
  • Manual: run enkan-repl, execute /init, verify interactive prompts, plan review, generation, compile-fix loop, auto-launch, and auto-connect all work end-to-end
  • Reference files are fetched in parallel (not bundled in jar)
  • Requires a valid ENKAN_AI_API_KEY for any OpenAI-compatible endpoint

🤖 Generated with Claude Code

kawasima and others added 2 commits April 5, 2026 21:30
…de Code CLI

Adds an interactive /init REPL command that prompts the user for requirements
and invokes claude -p to generate a complete Enkan project with controllers,
SystemFactory, ApplicationFactory, DevMain, and pom.xml.

- Add NEED_INPUT status to ReplResponse.ResponseStatus for multi-turn prompts
- Add Transport.sendPrompt() default method for prompting user input
- Add InitCommand: interactive requirements gathering + claude -p subprocess
- Register InitCommand in DevelCommandRegister as a local command
- Handle NEED_INPUT in enkan-repl.js so browser REPL accepts mid-command input

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t to enkan-repl CLI

InitCommand now lives in enkan-repl so the enkan-repl CLI client can invoke
/init without a REPL server connection.

- Move InitCommand: enkan-devel → enkan-repl (enkan.system.repl.command)
- Add JLineTransport: Transport impl backed by JLine LineReader for client-local I/O
- ReplClient: register InitCommand as a client-local command; dispatch /init
  (and any future client-local commands) before checking socket state
- enkan-devel: add enkan-repl dependency; update DevelCommandRegister import

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
kawasima and others added 11 commits April 5, 2026 21:57
Previously claude -p with Write tool produced no stdout until completion,
making /init appear frozen. Switch to stream-json output and parse assistant
text deltas and tool_use names to forward progress to the transport as it
arrives.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…clude-partial-messages

--output-format stream-json emits text as stream_event/content_block_delta/text_delta
events, not as assistant-level content. Add --include-partial-messages and --verbose
flags, and update the parser to extract text from text_delta and tool names from
tool_use content_block_start events.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ument

ProcessBuilder sets up a pipe for stdin, so claude -p was waiting for stdin
input and never receiving the prompt that was passed as a CLI argument.
Write the prompt to the process stdin stream and close it immediately.

Verified locally: stream_event/text_delta lines arrive incrementally and
text is correctly extracted by the parser.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… API

Switch /init command from spawning claude -p subprocess to calling
any OpenAI-compatible REST API (Anthropic, OpenAI, LM Studio, Ollama)
via java.net.http.HttpClient with SSE streaming.

- Force HTTP/1.1 to ensure compatibility with local inference servers
- Fall back to reasoning_content when thinking models omit content field
- Parse ### path/file + fenced code block format from response
- Write each generated file directly to the output directory
- Configure via ENKAN_AI_API_URL / ENKAN_AI_API_KEY / ENKAN_AI_MODEL
  (or system properties enkan.ai.apiUrl / apiKey / model)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…UI, compile-fix loop, and auto-connect

- Move InitCommand from enkan-repl to enkan-repl-client so it runs client-locally
- Add Transport.startSpinner/stopSpinner/requestConnect default methods
- JLineTransport: spinner animation, sendPrompt stores text for readLine, connect callback
- ReplClient: ANSI colored prompt (green=connected, yellow=disconnected), printErr/printInfo helpers
- InitCommand: fetch reference files from GitHub raw URLs at runtime
- InitCommand: spinner ("Thinking...") during LLM calls; show only filename during file writing
- InitCommand: write fixed templates (DevMain, pom.xml with dev profile, SystemFactory skeleton)
- InitCommand: compileAndFix loop — compile with mvn, send errors to LLM, apply fixes (max 3 attempts)
- InitCommand: launchAndConnect — optionally start app via mvn, poll port file, call transport.requestConnect
- InitCommand: toJsonString uses switch loop to avoid double-escape bugs
- InitCommand: path traversal prevention via normalize().startsWith()
- InitCommand: containsForbiddenFramework check to reject Spring Boot generation
- Add InitCommandTest covering plan review, cancellation, markdown rendering, forbidden framework detection
- DevelCommandRegister: register /init as local command

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Share a single HttpClient instance across all HTTP calls (avoid per-call allocation)
- Cache reference file fetches once per execute(); planning and generation phases now share the same in-memory copies instead of making 8 separate GitHub requests
- Read Enkan parent version dynamically from the jar manifest (Implementation-Version) so generated pom.xml stays correct after releases; falls back to the current snapshot when running outside a packaged jar
- Replace unescapeJson() replace-chain with a character-by-character loop (symmetric with toJsonString; eliminates fragile ordering dependency)
- Capture the Process handle returned by launchAndConnect() and log its PID

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove stale enkan-repl dependency from enkan-devel/pom.xml; InitCommand
  moved to enkan-repl-client so this dependency was no longer valid
- Default referenceCache to Collections.emptyMap() to prevent NPE when
  reviewPlanInteractively/generateWithApi are called outside execute()
- Add getOrFetchReferenceCache() lazy accessor so all callers are safe
  regardless of call order
- Fix findJsonStringEnd return-value check: use end < 0 || end == start
  instead of end <= start for clarity (end == -1 is unambiguous)
- Remove socket == null guard from /help dispatch so the help text is shown
  even when connected to a server
- JLineTransport implements AutoCloseable; close() stops spinner and shuts
  down the scheduler thread; ReplClient wraps JLineTransport in try-with-resources

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove silent catch(Exception) around local command execution; JLineTransport.close()
  does not throw so the empty catch block was pure dead code that would hide future bugs
- Drop eager referenceCache load from execute() so getOrFetchReferenceCache() becomes
  the single source of truth (eliminates the dual init paths flagged in review)
- Change enkanVersion() fallback from "0.14.2-SNAPSHOT" to "UNKNOWN" so a failure to
  read the jar manifest surfaces clearly in generated pom.xml instead of silently
  pinning an outdated version

Test coverage additions (8 → 17 tests):
- enkanVersion fallback
- inferProjectName stop-word handling, length cap, numbers/symbols, empty input
- isApprovalInput / isCancelInput English + Japanese + whitespace trimming
- renderMarkdownForTerminal null/blank handling
- containsForbiddenFramework null/blank and case insensitivity

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move enkanVersion() next to other helper methods (was awkwardly sandwiched
  between constant declarations)
- Add referencesFetched flag so getOrFetchReferenceCache() attempts the network
  fetch exactly once per instance; offline runs no longer re-hit GitHub on
  every prompt
- Catch PromptAbortedException separately from generateWithApi() so user aborts
  inside launchAndConnect() show "Init cancelled." instead of "Generation
  failed: null"
- Use safeMessage() for the generic Exception catch so null messages never
  leak into the error output
- CapturingTransport overrides sendPrompt() and records prompts separately;
  prompt assertions now check promptLines (clearer test intent)
- Promote extractFilePath, isFixedTemplateFile, unescapeJson, extractJsonStringField,
  and writeGeneratedFiles to package-private for targeted testing

Test coverage additions (17 → 25 tests):
- extractFilePath markdown header formats and non-path rejection
- isFixedTemplateFile recognition of pom.xml/DevMain/Main/*SystemFactory,
  including Windows-style backslash paths
- unescapeJson standard escapes, trailing lone backslash, unknown escapes, empty
- extractJsonStringField content/reasoning_content excludeKey handling
- writeGeneratedFiles happy path, path traversal rejection, fixed-template skipping

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…se duplicate handlers

- Fetch the 4 reference URLs in parallel via HttpClient.sendAsync; worst-case
  wall time drops from ~40s to ~10s on slow networks
- Replace referenceCache + referencesFetched pair with a null sentinel; one
  field is enough and the semantics are clearer (null = not yet attempted)
- Hoist outPath.normalize() out of the writeGeneratedFiles loop — it was
  recomputed on every file for the startsWith check
- Initialize the SSE response StringBuilder with 32K capacity to reduce
  reallocation for large generation outputs
- Collapse execute()'s two try blocks into one; PromptAbortedException now
  has a single handler rather than duplicated branches
- Drop narrate-what comments ("// DevMain.java", "// Write fixed templates
  first", "// Check if this match is actually inside excludeKey"); the
  surrounding identifiers already say what the code does

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Resolve apiUrl/apiKey/model once at execute() entry and store them as
  instance fields; drop the duplicate config() reads from reviewPlanInteractively
  and generateWithApi
- Shrink signatures: requestChatCompletion, generatePlanWithApi, revisePlanWithApi,
  and compileAndFix no longer take apiUrl/apiKey/model (they read the fields)
- Wrap CompletableFuture.join() in fetchAllReferences() with try/catch for
  CompletionException/CancellationException so a single misbehaving future
  cannot surface as "Generation failed: ..." from the outer handler
- Update InitCommandTest overrides to match the new signatures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an AI-assisted /init workflow to generate Enkan projects from the REPL, and extends the REPL protocol/client UX to support multi-turn prompting.

Changes:

  • Extend REPL protocol with a new NEED_INPUT status and Transport.sendPrompt() for interactive prompts.
  • Add a new client-local /init command (plus JLine-backed transport) to gather requirements, call an LLM, write a project scaffold, and optionally compile/fix/run.
  • Update both CLI and browser REPL clients to better support local commands/help and interactive input flow.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
enkan-system/src/main/java/enkan/system/Transport.java Adds sendPrompt() and no-op hooks (spinner/connect) for richer client UX.
enkan-system/src/main/java/enkan/system/ReplResponse.java Adds NEED_INPUT response status flag.
enkan-repl-client/src/main/java/enkan/system/repl/command/InitCommand.java Implements interactive AI-backed project generation and file writing.
enkan-repl-client/src/main/java/enkan/system/repl/client/ReplClient.java Adds local command help/completion and wiring for /init; adjusts output rendering.
enkan-repl-client/src/main/java/enkan/system/repl/client/JLineTransport.java New Transport implementation for client-local commands in the CLI.
enkan-repl-client/src/test/java/enkan/system/repl/command/InitCommandTest.java Adds unit tests for init prompt flow, markdown rendering, parsing/writing safeguards.
enkan-repl-client/src/test/java/enkan/system/repl/client/ReplClientLocalHelpTest.java Tests local help and local command list includes /init.
enkan-repl-client/pom.xml Adds runtime slf4j-simple dependency for CLI logging.
enkan-devel/src/main/resources/enkan/repl/enkan-repl.js Handles NEED_INPUT by unlocking terminal input between prompt turns.
enkan-devel/src/main/java/enkan/system/devel/DevelCommandRegister.java Optionally registers /init via reflection when available on the classpath.
Comments suppressed due to low confidence (1)

enkan-repl-client/src/main/java/enkan/system/repl/client/ReplClient.java:171

  • NEED_INPUT responses are not forwarded to the main loop via pipe, so after a command is sent the client can block indefinitely waiting for a DONE signal and never return to readLine() to collect the requested input. The renderer should signal NEED_INPUT (similar to DONE) and the main loop should resume input collection accordingly.
                            reader.getTerminal().writer().flush();
                            pipe.send("shutdown");
                            break;
                        } else if (res.getStatus().contains(DONE)) {
                            pipe.send("done");

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

ReplResponse res = fressian.read(msg.pop().getData(), ReplResponse.class);
if (res.getOut() != null) {
reader.getTerminal().writer().println(res.getOut());
reader.getTerminal().writer().print(res.getOut());
Copilot AI Apr 6, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the renderer thread, res.getOut() is now written with print() instead of println(). Most server responses (e.g., JShell output lines) are sent without trailing newlines, so this change will concatenate lines and can also cause the next prompt to appear on the same line. Consider restoring newline behavior for non-prompt output (or only using print() when NEED_INPUT is present).

Suggested change
reader.getTerminal().writer().print(res.getOut());
if (res.getStatus().contains(ReplResponse.ResponseStatus.NEED_INPUT)) {
reader.getTerminal().writer().print(res.getOut());
} else {
reader.getTerminal().writer().println(res.getOut());
}

Copilot uses AI. Check for mistakes.
Comment on lines 342 to +345
if (msg.status && msg.status.indexOf('DONE') !== -1) {
showPrompt();
} else if (msg.status && msg.status.indexOf('NEED_INPUT') !== -1) {
waitingForResponse = false;
Copilot AI Apr 6, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On NEED_INPUT the code only flips waitingForResponse = false, but does not re-render a prompt or manage cursor placement. If the intention is for msg.out to be the prompt, make sure the terminal output path treats NEED_INPUT as a prompt (no auto-newline) and leaves the cursor ready for typing.

Copilot uses AI. Check for mistakes.
Comment on lines +50 to +53
private static final String DEFAULT_API_URL = "https://api.anthropic.com/v1";
private static final String DEFAULT_MODEL = "claude-sonnet-4-5";
private static final Pattern HEADING_PATTERN = Pattern.compile("^\\s{0,3}#{1,6}\\s+");
private static final int MAX_GENERATION_ATTEMPTS = 2;
Copilot AI Apr 6, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default configuration claims Anthropic compatibility, but the client always calls POST {apiUrl}/chat/completions with an OpenAI-style payload and a default model name (claude-sonnet-4-5). With the current defaults (https://api.anthropic.com/v1), the resulting endpoint (/v1/chat/completions) is not a valid Anthropic API path, so /init will fail unless users override ENKAN_AI_API_URL. Either change defaults to an actually OpenAI-compatible base URL, or implement provider-specific endpoints/payloads (e.g. Anthropic /v1/messages).

Copilot uses AI. Check for mistakes.
Comment on lines +626 to +635
String requestChatCompletion(String systemPrompt, String userPrompt, String spinnerLabel, Transport transport)
throws IOException, InterruptedException {
if (transport != null) transport.startSpinner(spinnerLabel);
try {
String requestBody = buildRequestBody(model, systemPrompt, userPrompt, true);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(apiUrl + "/chat/completions"))
.version(HttpClient.Version.HTTP_1_1)
.timeout(Duration.ofMinutes(10))
.header("Content-Type", "application/json")
Copilot AI Apr 6, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requestChatCompletion() hardcodes the OpenAI streaming endpoint suffix "/chat/completions". If you intend to support multiple providers, make the path configurable or derive it from the provider type; otherwise the DEFAULT_API_URL needs to match this contract. Also consider setting appropriate streaming headers (e.g., Accept: text/event-stream) if required by the backend.

Suggested change
String requestChatCompletion(String systemPrompt, String userPrompt, String spinnerLabel, Transport transport)
throws IOException, InterruptedException {
if (transport != null) transport.startSpinner(spinnerLabel);
try {
String requestBody = buildRequestBody(model, systemPrompt, userPrompt, true);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(apiUrl + "/chat/completions"))
.version(HttpClient.Version.HTTP_1_1)
.timeout(Duration.ofMinutes(10))
.header("Content-Type", "application/json")
private URI resolveChatCompletionUri() {
String normalizedApiUrl = apiUrl.endsWith("/") ? apiUrl.substring(0, apiUrl.length() - 1) : apiUrl;
if (normalizedApiUrl.endsWith("/chat/completions") || normalizedApiUrl.endsWith("/messages")) {
return URI.create(normalizedApiUrl);
}
return URI.create(normalizedApiUrl + "/chat/completions");
}
String requestChatCompletion(String systemPrompt, String userPrompt, String spinnerLabel, Transport transport)
throws IOException, InterruptedException {
if (transport != null) transport.startSpinner(spinnerLabel);
try {
String requestBody = buildRequestBody(model, systemPrompt, userPrompt, true);
HttpRequest request = HttpRequest.newBuilder()
.uri(resolveChatCompletionUri())
.version(HttpClient.Version.HTTP_1_1)
.timeout(Duration.ofMinutes(10))
.header("Content-Type", "application/json")
.header("Accept", "text/event-stream")

Copilot uses AI. Check for mistakes.
Comment on lines +760 to +768
String path = extractFilePath(line);
if (path != null) {
currentFile = isFixedTemplateFile(path) ? null : path;
if (currentFile != null) {
transport.sendOut(GREEN + " ✓" + RESET + " " + currentFile + "\n");
}
} else if (currentFile != null && (line.startsWith("```"))) {
inBlock = true;
codeLines.setLength(0);
Copilot AI Apr 6, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

writeGeneratedFiles() skips fixed-template files by setting currentFile = null, but then it does not enter inBlock when the fenced code block starts. That means the skipped file’s code lines are still parsed as normal text and could accidentally be interpreted as new file headers (e.g., a code line starting with ###). Track/consume fenced blocks even when the current file is being skipped so the parser stays aligned and cannot be confused by content inside skipped blocks.

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +43
/**
* REPL command that interactively generates a new Enkan application project
* using an OpenAI-compatible chat completion API (Anthropic, OpenAI, LM Studio, Ollama, etc.).
*
* <p>Usage: {@code /init}
*
* <p>Configuration via environment variables or system properties:
* <ul>
* <li>{@code ENKAN_AI_API_URL} / {@code enkan.ai.apiUrl} — base URL (default: {@code https://api.anthropic.com/v1})</li>
* <li>{@code ENKAN_AI_API_KEY} / {@code enkan.ai.apiKey} — API key</li>
* <li>{@code ENKAN_AI_MODEL} / {@code enkan.ai.model} — model name (default: {@code claude-sonnet-4-5})</li>
* </ul>
*
* <p>This command is a client-local command: it can be run without a REPL server
* connection, making it usable directly from the {@code enkan-repl} CLI client.
*
Copilot AI Apr 6, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description says /init spawns claude -p, but InitCommand is implemented as an HTTP client for an OpenAI-compatible /chat/completions API. Either update the implementation to match the documented CLI-based approach, or update the PR description / command docs so users configure the correct backend and understand the dependency (HTTP API vs local claude).

Copilot uses AI. Check for mistakes.
Comment on lines 341 to 346
}
if (msg.status && msg.status.indexOf('DONE') !== -1) {
showPrompt();
} else if (msg.status && msg.status.indexOf('NEED_INPUT') !== -1) {
waitingForResponse = false;
}
Copilot AI Apr 6, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For NEED_INPUT, input is unlocked but msg.out is still rendered with term.writeln(msg.out) above, which always appends a newline. If msg.out is intended to be an inline prompt (e.g. ending with ": "), consider rendering output differently when NEED_INPUT is present (e.g. term.write without newline) so the user can type on the same line as the prompt.

Copilot uses AI. Check for mistakes.
- ReplClient renderer: restore println() for non-prompt output; only use print()
  when NEED_INPUT is set, so multi-line JShell output no longer concatenates
- ReplClient renderer: forward NEED_INPUT to the main loop via pipe.send("need-input")
  so the client no longer blocks waiting for DONE before returning to readLine()
- enkan-repl.js: render NEED_INPUT messages with term.write (no newline) so the
  prompt keeps the cursor on the same line in the browser terminal
- InitCommand.writeGeneratedFiles: track a separate skipBlock flag so fenced
  code blocks under skipped fixed-template headers are still consumed;
  prevents code lines starting with "### " or "**" from being misread as new
  file headers
- InitCommand.resolveChatCompletionUri(): normalize the configured API URL —
  accept both base URLs (/v1) and full paths (/v1/chat/completions), tolerate
  a trailing slash
- InitCommand.requestChatCompletion: add Accept: text/event-stream header
- InitCommand: package-private apiUrl/apiKey/model fields for testability
- InitCommand class Javadoc: clarify that defaults target Anthropic's
  OpenAI-compatible endpoint, and document URL normalization
- Tests: add resolveChatCompletionUri URL normalization cases and a regression
  test for writeGeneratedFiles consuming fenced blocks under skipped headers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kawasima
kawasima merged commit 168c631 into develop Apr 6, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0