Skip to content

Commit 672506e

Browse files
authored
Merge pull request #16 from getcodesetu/feature/roadmap-now-next-soon
Feature/roadmap now next soon
2 parents 1e38f18 + 8ad491f commit 672506e

55 files changed

Lines changed: 3774 additions & 26 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.vscode/launch.json‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"name": "Run CodeSetu Extension",
6+
"type": "extensionHost",
7+
"request": "launch",
8+
"args": ["--extensionDevelopmentPath=${workspaceFolder}/apps/vscode"],
9+
"outFiles": ["${workspaceFolder}/apps/vscode/dist/**/*.cjs"],
10+
"preLaunchTask": "build-codesetu"
11+
}
12+
]
13+
}

‎.vscode/tasks.json‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"version": "2.0.0",
3+
"tasks": [
4+
{
5+
"label": "build-codesetu",
6+
"type": "shell",
7+
"command": "corepack pnpm build",
8+
"problemMatcher": [],
9+
"presentation": { "reveal": "silent", "panel": "shared" },
10+
"detail": "Build @codesetu/core + the VSCode extension before launching the dev host."
11+
}
12+
]
13+
}

‎CHANGELOG.md‎

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,44 @@ Versioning: [Semantic Versioning](https://semver.org/)
99

1010
## [Unreleased]
1111

12+
### Planned for v0.1
13+
14+
- Inline FIM completions via Sarvam-30B
15+
- Chat panel for VSCode
16+
- Chat panel for JetBrains (IntelliJ, PyCharm, WebStorm)
17+
- `/edit` command with diff view
18+
- Codebase indexing (`@workspace`)
19+
- On-prem Docker installer
20+
21+
---
22+
23+
## [0.4.1] - 2026-06-18
24+
1225
### Added
1326

27+
- **Inline completion polish** (VSCode): inline FIM completions are now
28+
debounced (`codesetu.inlineCompletions.debounceMs`, default 200ms), abandon
29+
superseded requests on cancellation, and reuse a one-entry cache so
30+
re-triggering at the same spot avoids a redundant model call.
31+
- **Code-block actions in chat** (VSCode): every fenced code block in an
32+
assistant reply gets **Copy** and **Insert** buttons — Insert drops the code
33+
into the editor the user was last in (replacing the selection if any).
34+
- **@-mention file pinning** (VSCode): type `@` in the composer to pin
35+
workspace files as primary context. Pins show as removable chips, persist
36+
across turns/reloads, and are sent as a dedicated "pinned files" section
37+
(distinct from auto-collected snippets).
38+
- **Edit with CodeSetu** (VSCode): new `codesetu.editSelection` command
39+
(selection context menu + palette) — describe a change, preview it as a
40+
native diff, and apply via a WorkspaceEdit only on accept.
41+
- **Agent Mode checkpoints** (VSCode): each agent turn snapshots the files it
42+
writes; **CodeSetu: Revert Last Agent Edits** restores them in one click
43+
(rewriting modified files, deleting newly created ones). `bash` side effects
44+
are out of scope.
45+
- **Conversation persistence** (VSCode): the chat transcript is saved per
46+
workspace and restored on reload; a **New chat** action (composer menu +
47+
`codesetu.newChat`) clears it.
48+
- **Context-usage gauge** (VSCode): a "~N ctx" chip by the model picker shows
49+
the estimated tokens in the assembled context for each turn.
1450
- **Agent Mode** (VSCode + JetBrains): an opt-in composer toggle that turns chat
1551
into a tool-calling agent which reads, edits, and runs commands to complete a
1652
task. Tools: `read_file`, `write_file`, `edit_file`, `bash`, read-only
@@ -48,15 +84,6 @@ Versioning: [Semantic Versioning](https://semver.org/)
4884
allowlisted `connect-src` derived from the configured speech endpoints,
4985
instead of being unconstrained.
5086

51-
### Planned for v0.1
52-
53-
- Inline FIM completions via Sarvam-30B
54-
- Chat panel for VSCode
55-
- Chat panel for JetBrains (IntelliJ, PyCharm, WebStorm)
56-
- `/edit` command with diff view
57-
- Codebase indexing (`@workspace`)
58-
- On-prem Docker installer
59-
6087
---
6188

6289
<!-- Add new releases above this line -->

‎apps/jetbrains/gradle.properties‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ org.gradle.parallel=true
66
# Plugin version baseline. CI overrides with -PpluginVersion=<major>.<minor>.<run_number>.
77
# Bump the major.minor here when you want a semantic version bump; CI keeps the
88
# patch incrementing automatically.
9-
pluginVersion=0.4.0
9+
pluginVersion=0.4.1
1010

1111
# Tell Gradle's toolchain detection where to find JDK 21 installed via Homebrew.
1212
# Falls back to other auto-detected JDKs if this path doesn't exist on a contributor's machine.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package ai.codesetu.actions
2+
3+
import ai.codesetu.toolwindow.CodeSetuChatService
4+
import com.intellij.openapi.actionSystem.AnAction
5+
import com.intellij.openapi.actionSystem.AnActionEvent
6+
7+
/**
8+
* Undo the file edits made by the most recent CodeSetu agent turn, restoring
9+
* each touched file to its pre-turn state. Structured edits only — `bash` side
10+
* effects are not tracked.
11+
*/
12+
class RevertLastAgentEditsAction : AnAction() {
13+
override fun actionPerformed(e: AnActionEvent) {
14+
val project = e.project ?: return
15+
CodeSetuChatService.getInstance(project).revertLastAgentEdits()
16+
}
17+
}

‎apps/jetbrains/src/main/kotlin/ai/codesetu/agent/GetDiagnosticsTool.kt‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package ai.codesetu.agent
22

3-
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl
3+
import com.intellij.codeInsight.daemon.impl.HighlightInfo
44
import com.intellij.lang.annotation.HighlightSeverity
55
import com.intellij.openapi.application.ReadAction
6+
import com.intellij.openapi.editor.impl.DocumentMarkupModel
67
import com.intellij.openapi.fileEditor.FileDocumentManager
78
import com.intellij.openapi.fileEditor.FileEditorManager
89
import com.intellij.openapi.project.Project
@@ -81,15 +82,20 @@ class GetDiagnosticsTool(private val project: Project) : AgentTool {
8182
for (file in files) {
8283
if (out.size >= MAX_DIAGNOSTICS) break
8384
val document = FileDocumentManager.getInstance().getDocument(file) ?: continue
84-
val highlights = DaemonCodeAnalyzerImpl.getHighlights(document, minSeverity, project)
85+
// Read the daemon's highlights from the document markup model (public
86+
// API) rather than the @Internal DaemonCodeAnalyzerImpl. Syntax-only
87+
// highlighters return null from fromRangeHighlighter and are skipped.
88+
val markupModel = DocumentMarkupModel.forDocument(document, project, true)
8589
val relative =
8690
if (basePath != null && file.path.startsWith("$basePath/")) {
8791
file.path.removePrefix("$basePath/")
8892
} else {
8993
file.path
9094
}
91-
for (info in highlights) {
95+
for (highlighter in markupModel.allHighlighters) {
9296
if (out.size >= MAX_DIAGNOSTICS) break
97+
val info = HighlightInfo.fromRangeHighlighter(highlighter) ?: continue
98+
if (info.severity < minSeverity) continue
9399
val description = info.description ?: continue
94100
val severity =
95101
when {
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package ai.codesetu.agent
2+
3+
import java.io.File
4+
import java.nio.charset.StandardCharsets
5+
import java.nio.file.Files
6+
7+
data class RevertResult(val restored: Int, val deleted: Int, val failed: Int)
8+
9+
/**
10+
* Records the pre-edit state of every file the agent writes during a turn, so
11+
* the whole turn's file changes can be undone in one click.
12+
*
13+
* Scope: this captures structured edits (write_file / edit_file, which both go
14+
* through [AgentHost.writeFile]). Side effects of `bash` commands are NOT
15+
* tracked and cannot be reverted this way. Pure filesystem — no platform deps —
16+
* so it stays unit-testable; the caller refreshes the VFS after a revert.
17+
* Mirrors the VS Code WorkspaceCheckpoint.
18+
*/
19+
class WorkspaceCheckpoint(private val root: String?) {
20+
private data class Snapshot(val file: File, val relPath: String, val original: String?)
21+
22+
// Keyed by canonical absolute path; only the FIRST snapshot per file is kept,
23+
// so revert restores the state from before the turn began.
24+
private val snapshots = LinkedHashMap<String, Snapshot>()
25+
26+
fun capture(path: String) {
27+
val base = File(root ?: System.getProperty("user.dir")).canonicalFile
28+
val file = File(path).let { if (it.isAbsolute) it else File(base, path) }.canonicalFile
29+
val key = file.path
30+
if (snapshots.containsKey(key)) return
31+
val original =
32+
if (file.isFile) {
33+
try {
34+
String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8)
35+
} catch (error: Exception) {
36+
null
37+
}
38+
} else {
39+
null // did not exist (or unreadable) — revert deletes it
40+
}
41+
val rel = runCatching { base.toPath().relativize(file.toPath()).toString() }.getOrNull()
42+
snapshots[key] = Snapshot(file, rel?.ifEmpty { path } ?: path, original)
43+
}
44+
45+
fun isEmpty(): Boolean = snapshots.isEmpty()
46+
47+
fun changedFiles(): List<String> = snapshots.values.map { it.relPath }.sorted()
48+
49+
/** Restore every captured file to its pre-turn state. */
50+
fun revert(): RevertResult {
51+
var restored = 0
52+
var deleted = 0
53+
var failed = 0
54+
for (snap in snapshots.values) {
55+
try {
56+
if (snap.original == null) {
57+
Files.deleteIfExists(snap.file.toPath())
58+
deleted += 1
59+
} else {
60+
snap.file.parentFile?.mkdirs()
61+
Files.write(snap.file.toPath(), snap.original.toByteArray(StandardCharsets.UTF_8))
62+
restored += 1
63+
}
64+
} catch (error: Exception) {
65+
failed += 1
66+
}
67+
}
68+
return RevertResult(restored, deleted, failed)
69+
}
70+
}
71+
72+
/**
73+
* Wrap an [AgentHost] so each write is snapshotted into the returned checkpoint
74+
* before it lands. The wrapped host is otherwise identical to the delegate.
75+
*/
76+
fun checkpointingHost(delegate: AgentHost): Pair<AgentHost, WorkspaceCheckpoint> {
77+
val checkpoint = WorkspaceCheckpoint(delegate.rootPath())
78+
val wrapped =
79+
object : AgentHost by delegate {
80+
override fun writeFile(path: String, content: String) {
81+
checkpoint.capture(path)
82+
delegate.writeFile(path, content)
83+
}
84+
}
85+
return wrapped to checkpoint
86+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package ai.codesetu.completion
2+
3+
import ai.codesetu.provider.CodeSetuProviderClient
4+
import ai.codesetu.settings.CodeSetuSettingsState
5+
import com.intellij.codeInsight.inline.completion.DebouncedInlineCompletionProvider
6+
import com.intellij.codeInsight.inline.completion.InlineCompletionEvent
7+
import com.intellij.codeInsight.inline.completion.InlineCompletionProviderID
8+
import com.intellij.codeInsight.inline.completion.InlineCompletionRequest
9+
import com.intellij.codeInsight.inline.completion.elements.InlineCompletionGrayTextElement
10+
import com.intellij.codeInsight.inline.completion.suggestion.InlineCompletionSingleSuggestion
11+
import com.intellij.codeInsight.inline.completion.suggestion.InlineCompletionSuggestion
12+
import com.intellij.openapi.application.readAction
13+
import com.intellij.openapi.util.UserDataHolderBase
14+
import java.util.concurrent.atomic.AtomicReference
15+
import kotlin.time.Duration
16+
import kotlin.time.Duration.Companion.milliseconds
17+
import kotlinx.coroutines.Dispatchers
18+
import kotlinx.coroutines.flow.flowOf
19+
import kotlinx.coroutines.withContext
20+
21+
/**
22+
* Ghost-text fill-in-the-middle completions as you type. Extends the platform's
23+
* [DebouncedInlineCompletionProvider] so a burst of keystrokes only fires one
24+
* request and a superseding keystroke cancels the in-flight coroutine. A
25+
* one-entry cache serves an identical re-request (same prefix + suffix) without
26+
* another round-trip. Mirrors the VS Code inline-completion provider.
27+
*/
28+
class CodeSetuInlineCompletionProvider : DebouncedInlineCompletionProvider() {
29+
private val client = CodeSetuProviderClient()
30+
private val cache = AtomicReference<CacheEntry?>(null)
31+
32+
override val id = InlineCompletionProviderID("CodeSetu")
33+
34+
override fun isEnabled(event: InlineCompletionEvent): Boolean {
35+
if (!CodeSetuSettingsState.getInstance().state.inlineCompletionsEnabled) return false
36+
// Trigger while typing and on an explicit manual call; ignore the noisier
37+
// events (lookup, focus) that would fire requests the user didn't ask for.
38+
return event is InlineCompletionEvent.DocumentChange || event is InlineCompletionEvent.DirectCall
39+
}
40+
41+
override suspend fun getDebounceDelay(request: InlineCompletionRequest): Duration =
42+
CodeSetuSettingsState.getInstance().state.fimDebounceMs.coerceAtLeast(0).milliseconds
43+
44+
override suspend fun getSuggestionDebounced(
45+
request: InlineCompletionRequest,
46+
): InlineCompletionSuggestion {
47+
val settings = CodeSetuSettingsState.getInstance().state
48+
val (text, offset) = readAction { request.document.text to request.endOffset }
49+
val fim = buildFimContext(text, offset, settings.fimMaxPrefixChars, settings.fimMaxSuffixChars)
50+
if (fim.prompt.isEmpty() && fim.suffix.isEmpty()) return InlineCompletionSuggestion.Empty
51+
52+
cache.get()?.let { entry ->
53+
if (entry.prompt == fim.prompt && entry.suffix == fim.suffix) return grayText(entry.text)
54+
}
55+
56+
val completion = withContext(Dispatchers.IO) {
57+
runCatching {
58+
client.completeFim(
59+
prompt = fim.prompt,
60+
suffix = fim.suffix,
61+
maxTokens = settings.fimMaxTokens,
62+
stop = listOf("\n\n", "\n```"),
63+
)
64+
}.getOrDefault("")
65+
}
66+
67+
if (completion.isEmpty()) return InlineCompletionSuggestion.Empty
68+
cache.set(CacheEntry(fim.prompt, fim.suffix, completion))
69+
return grayText(completion)
70+
}
71+
72+
private fun grayText(text: String): InlineCompletionSuggestion =
73+
InlineCompletionSingleSuggestion.build(
74+
UserDataHolderBase(),
75+
flowOf(InlineCompletionGrayTextElement(text)),
76+
)
77+
78+
private data class CacheEntry(val prompt: String, val suffix: String, val text: String)
79+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package ai.codesetu.completion
2+
3+
/** The fill-in-the-middle context around the caret: text before and after. */
4+
data class FimContext(val prompt: String, val suffix: String)
5+
6+
/**
7+
* Build the fill-in-the-middle context around a caret offset, bounding the
8+
* prefix and suffix to a character budget so large files don't blow the request
9+
* size. Mirrors the VS Code `buildFimContext` helper so both IDEs frame the same
10+
* window for the model.
11+
*/
12+
fun buildFimContext(
13+
text: String,
14+
offset: Int,
15+
maxPrefixChars: Int,
16+
maxSuffixChars: Int,
17+
): FimContext {
18+
val safeOffset = offset.coerceIn(0, text.length)
19+
val prefix = text.substring(0, safeOffset)
20+
val suffix = text.substring(safeOffset)
21+
return FimContext(
22+
prompt = prefix.takeLast(maxPrefixChars.coerceAtLeast(0)),
23+
suffix = suffix.take(maxSuffixChars.coerceAtLeast(0)),
24+
)
25+
}

0 commit comments

Comments
 (0)