Skip to content
Merged
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
12 changes: 11 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
# 统一仓库内常见文本文件为 LF,避免 Windows 下出现无意义的换行警告
*.go text eol=lf
*.md text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
go.mod text eol=lf
go.sum text eol=lf
.gitignore text eol=lf
.gitattributes text eol=lf

# 强制 scripts 目录下的 Shell 脚本始终使用 LF 换行符
scripts/*.sh text eol=lf
scripts/*.sh text eol=lf
15 changes: 12 additions & 3 deletions docs/neocode-coding-agent-mvp-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@

`用户输入 -> Agent 推理 -> 调用工具 -> 获取结果 -> 继续推理 -> UI 展示`

MVP 聚焦五个模块
MVP 聚焦六个模块

1. provider:统一不同模型/API 的调用方式
2. TUI:用户交互入口,承载输入、对话、侧边栏、会话
3. tools:统一工具定义、参数校验、执行与结果封装
4. config:管理本地配置、provider 切换、模型选择
5. agent runtime:驱动整个 agent loop,是系统核心
5. context:负责 system prompt、显式上下文源与历史消息裁剪
6. agent runtime:驱动整个 agent loop,是系统核心

---

Expand Down Expand Up @@ -46,6 +47,7 @@ flowchart LR
- TUI:负责交互和渲染
- Application:负责启动和依赖注入
- Runtime:负责 Agent Loop 和状态编排
- Context:负责模型请求前的上下文构建
- Provider:负责模型调用抽象
- Tool Manager:负责工具注册、校验、执行
- Config:负责配置加载与选择
Expand Down Expand Up @@ -295,7 +297,7 @@ type UserInput struct {
Runtime 内部建议拆分:

- `SessionStore`:管理会话和消息历史
- `PromptBuilder`:组装 prompt/messages/tools
- `context.Builder`:组装核心 prompt、显式上下文源与裁剪后的消息
- `Executor`:执行 loop
- `EventBus`:向 TUI 推送运行事件

Expand Down Expand Up @@ -379,6 +381,13 @@ sequenceDiagram
│ │ ├── loader.go
│ │ ├── model.go
│ │ └── validate.go
│ ├── context/
│ │ ├── builder.go
│ │ ├── metadata.go
│ │ ├── prompt.go
│ │ ├── source_rules.go
│ │ ├── source_system.go
│ │ └── trim.go
│ ├── provider/
│ │ ├── provider.go
│ │ ├── openai/
Expand Down
30 changes: 30 additions & 0 deletions docs/runtime-provider-event-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,36 @@
8. 执行返回的工具调用,并保存每一个工具结果。
9. 如果仍需继续推理,则进入下一轮;否则结束。

### Context Builder 输入与职责

- `runtime` 只向 `context.Builder` 传递本轮所需元数据:
- 历史消息
- `workdir`
- `shell`
- 当前 `provider`
- 当前 `model`
- `context.Builder` 负责统一组装:
- 固定核心 system prompt
- 从 `workdir` 向上发现的 `AGENTS.md`
- 系统状态摘要(`workdir` / `shell` / `provider` / `model` / git branch / git dirty)
- 裁剪后的历史消息
- `runtime` 不直接读取规则文件,也不直接查询 git 状态。
- `provider` 只消费最终生成的 `SystemPrompt`、消息列表和工具 schema,不感知上下文来源。

### System Prompt 注入顺序

当前 `system prompt` 按以下顺序拼装:

1. 固定核心指令
2. `Project Rules` section
3. `System State` section

其中:

- 规则文件只支持大写文件名 `AGENTS.md`
- 多份命中结果按“从全局到局部”的顺序注入
- git 只注入摘要,不注入完整 `git status`

## 流式桥接

- Provider 发出 `StreamEvent`
Expand Down
26 changes: 22 additions & 4 deletions internal/context/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ package context
import "context"

// DefaultBuilder preserves the current runtime context-building behavior.
type DefaultBuilder struct{}
type DefaultBuilder struct {
gitRunner gitCommandRunner
}

// NewBuilder returns the default context builder implementation.
func NewBuilder() Builder {
return &DefaultBuilder{}
return &DefaultBuilder{
gitRunner: runGitCommand,
}
}

// Build assembles the provider-facing context for the current round.
Expand All @@ -16,8 +20,22 @@ func (b *DefaultBuilder) Build(ctx context.Context, input BuildInput) (BuildResu
return BuildResult{}, err
}

rules, err := loadProjectRules(ctx, input.Metadata.Workdir)
if err != nil {
return BuildResult{}, err
}

systemState, err := collectSystemState(ctx, input.Metadata, b.gitRunner)
if err != nil {
return BuildResult{}, err
}

return BuildResult{
SystemPrompt: defaultSystemPrompt(),
Messages: trimMessages(input.Messages),
SystemPrompt: composeSystemPrompt(
defaultSystemPrompt(),
renderProjectRulesSection(rules),
renderSystemStateSection(systemState),
),
Messages: trimMessages(input.Messages),
}, nil
}
16 changes: 13 additions & 3 deletions internal/context/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package context
import (
stdcontext "context"
"fmt"
"strings"
"testing"

"neo-code/internal/provider"
Expand All @@ -16,7 +17,7 @@ func TestDefaultBuilderBuild(t *testing.T) {
Messages: []provider.Message{
{Role: "user", Content: "hello"},
},
Workdir: t.TempDir(),
Metadata: testMetadata(t.TempDir()),
}

got, err := builder.Build(stdcontext.Background(), input)
Expand All @@ -26,8 +27,17 @@ func TestDefaultBuilderBuild(t *testing.T) {
if got.SystemPrompt == "" {
t.Fatalf("expected non-empty system prompt")
}
if got.SystemPrompt != defaultSystemPrompt() {
t.Fatalf("expected default prompt to remain unchanged")
if !strings.Contains(got.SystemPrompt, defaultSystemPrompt()) {
t.Fatalf("expected default prompt to remain in composed prompt")
}
if !strings.Contains(got.SystemPrompt, "## System State") {
t.Fatalf("expected system state section in composed prompt")
}
if strings.Contains(got.SystemPrompt, "## Project Rules") {
t.Fatalf("did not expect project rules section without AGENTS.md")
}
if !strings.Contains(got.SystemPrompt, input.Metadata.Workdir) {
t.Fatalf("expected workdir in system state section")
}
if len(got.Messages) != 1 {
t.Fatalf("expected 1 message, got %d", len(got.Messages))
Expand Down
25 changes: 25 additions & 0 deletions internal/context/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package context

// Metadata contains the non-message runtime state needed by context sources.
type Metadata struct {
Workdir string
Shell string
Provider string
Model string
}

// GitState is the summarized git metadata exposed to the prompt builder.
type GitState struct {
Available bool
Branch string
Dirty bool
}

// SystemState is the summarized runtime metadata exposed to the prompt builder.
type SystemState struct {
Workdir string
Shell string
Provider string
Model string
Git GitState
}
14 changes: 14 additions & 0 deletions internal/context/prompt.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package context

import "strings"

func defaultSystemPrompt() string {
return `You are NeoCode, a local coding agent.

Expand All @@ -8,3 +10,15 @@ func defaultSystemPrompt() string {
When a tool fails, inspect the error and continue safely.
Stay within the workspace and avoid destructive behavior unless clearly requested.`
}

func composeSystemPrompt(parts ...string) string {
sections := make([]string, 0, len(parts))
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
sections = append(sections, part)
}
return strings.Join(sections, "\n\n")
}
Loading
Loading