Skip to content

Commit 2a7436c

Browse files
fix: recover false-negative QA/tester failures and green CI format
Attach pre-test smoke evidence so honest tester passes are not rejected, promote the board when qa_gate is green, re-run smoke at review time, and hard-stop repeated tool loops. Also gofmt + Makefile version sync to 0.6.0. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 6122875 commit 2a7436c

20 files changed

Lines changed: 258 additions & 120 deletions

File tree

‎Makefile‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MODULE := github.com/UnicoLab/slmcode
22
BIN := slmcode
3-
VERSION ?= 0.5.17
3+
VERSION ?= 0.6.0
44
PREFIX ?= $(HOME)/.local
55
GIT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
66
BUILD_TIME := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)

‎pkg/augment/augment.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ Commit to one approach. Prefer tiny ws_edit/ws_patch. One atomic change → smok
117117
},
118118
{
119119
Topic: "Edit Recovery Loop", TokenCost: 100,
120-
Keywords: []string{"edit", "patch", "fix", "replace", "old_str", "failed", "error"},
120+
Keywords: []string{"edit", "patch", "fix", "replace", "old_str", "failed", "error"},
121121
RequiresTools: []string{"ws_read", "ws_edit"},
122122
Body: `When ws_edit/ws_patch fails: (1) ws_read the file, (2) copy exact numbered text into
123123
old_str/SEARCH, (3) retry. Never escalate to ws_write on an existing file.

‎pkg/config/config.go‎

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,8 @@ type Config struct {
160160
// PlanApproveTimeout for ask mode.
161161
PlanApproveTimeout time.Duration `yaml:"plan_approve_timeout" json:"plan_approve_timeout"`
162162

163-
DryRun bool `yaml:"dry_run" json:"dry_run"`
164-
Verbose bool `yaml:"verbose" json:"verbose"`
163+
DryRun bool `yaml:"dry_run" json:"dry_run"`
164+
Verbose bool `yaml:"verbose" json:"verbose"`
165165
// AutoApprove skips plan/shell/clarify HITL waits (forces recommended/allow).
166166
AutoApprove bool `yaml:"auto_approve" json:"auto_approve"`
167167

@@ -201,7 +201,7 @@ type Config struct {
201201
StaticQuality bool `yaml:"static_quality" json:"static_quality"` // reject stub/placeholder code
202202
ThinkingBudget bool `yaml:"thinking_budget" json:"thinking_budget"` // commit-to-implementation nudge
203203
// ThinkingBudgetTokens hard-abort threshold for over-long deliberation (0=4096).
204-
ThinkingBudgetTokens int `yaml:"thinking_budget_tokens" json:"thinking_budget_tokens"`
204+
ThinkingBudgetTokens int `yaml:"thinking_budget_tokens" json:"thinking_budget_tokens"`
205205
FinalizeWarn bool `yaml:"finalize_warn" json:"finalize_warn"` // warn before MaxIter exhaustion
206206
RequireSmoke bool `yaml:"require_smoke" json:"require_smoke"` // coding tasks need smoke for approve
207207
ClaimsGate bool `yaml:"claims_gate" json:"claims_gate"` // reject hallucinated files_changed
@@ -247,30 +247,30 @@ func Default(root string) *Config {
247247
root, _ = os.Getwd()
248248
}
249249
return &Config{
250-
Root: root,
251-
Provider: DefaultProvider,
252-
Endpoint: DefaultEndpoint,
253-
Model: DefaultModel,
254-
Backend: BackendSLMCode,
255-
Mode: ModeFull,
256-
Temperature: 0.2,
257-
MaxTokens: 4096,
258-
MaxRetries: DefaultMaxRetries,
259-
MaxParallel: DefaultMaxParallel,
260-
MaxContextKB: DefaultMaxContextKB,
261-
ThinkPasses: DefaultThinkPasses,
262-
TaskTimeout: DefaultTaskTimeout,
263-
QAGate: true,
264-
QAGateMaxRounds: DefaultQAGateRounds,
265-
PostWorkerSmoke: true,
266-
ClarifyMode: "auto",
267-
ClarifyTimeout: 2 * time.Minute,
268-
ScopeJudge: true,
269-
PlanApprove: "auto",
270-
PlanApproveTimeout: 2 * time.Minute,
271-
Listen: "127.0.0.1:7420",
272-
ClaudeCodeBin: "claude",
273-
Permission: "auto",
250+
Root: root,
251+
Provider: DefaultProvider,
252+
Endpoint: DefaultEndpoint,
253+
Model: DefaultModel,
254+
Backend: BackendSLMCode,
255+
Mode: ModeFull,
256+
Temperature: 0.2,
257+
MaxTokens: 4096,
258+
MaxRetries: DefaultMaxRetries,
259+
MaxParallel: DefaultMaxParallel,
260+
MaxContextKB: DefaultMaxContextKB,
261+
ThinkPasses: DefaultThinkPasses,
262+
TaskTimeout: DefaultTaskTimeout,
263+
QAGate: true,
264+
QAGateMaxRounds: DefaultQAGateRounds,
265+
PostWorkerSmoke: true,
266+
ClarifyMode: "auto",
267+
ClarifyTimeout: 2 * time.Minute,
268+
ScopeJudge: true,
269+
PlanApprove: "auto",
270+
PlanApproveTimeout: 2 * time.Minute,
271+
Listen: "127.0.0.1:7420",
272+
ClaudeCodeBin: "claude",
273+
Permission: "auto",
274274
ShellPermission: "allow",
275275
ShellWhitelist: true,
276276
ShellAskTimeout: 2 * time.Minute,

‎pkg/eval/harness.go‎

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,26 @@ import (
1717

1818
// Case is one coding eval scenario.
1919
type Case struct {
20-
ID string
21-
Query string
22-
SeedFiles map[string]string // rel path → content
23-
ExpectFiles []string // must exist after run
20+
ID string
21+
Query string
22+
SeedFiles map[string]string // rel path → content
23+
ExpectFiles []string // must exist after run
2424
ExpectSubstr map[string]string // path → substring that must appear
25-
Timeout time.Duration
25+
Timeout time.Duration
2626
}
2727

2828
// Result is the outcome of one case.
2929
type Result struct {
30-
ID string `json:"id"`
31-
OK bool `json:"ok"`
32-
Duration time.Duration `json:"duration"`
33-
TasksDone int `json:"tasks_done"`
34-
TasksTotal int `json:"tasks_total"`
35-
SmokeOK bool `json:"smoke_ok"`
36-
FilesOK bool `json:"files_ok"`
37-
Error string `json:"error,omitempty"`
38-
Summary string `json:"summary,omitempty"`
39-
Interventions int `json:"interventions"`
30+
ID string `json:"id"`
31+
OK bool `json:"ok"`
32+
Duration time.Duration `json:"duration"`
33+
TasksDone int `json:"tasks_done"`
34+
TasksTotal int `json:"tasks_total"`
35+
SmokeOK bool `json:"smoke_ok"`
36+
FilesOK bool `json:"files_ok"`
37+
Error string `json:"error,omitempty"`
38+
Summary string `json:"summary,omitempty"`
39+
Interventions int `json:"interventions"`
4040
}
4141

4242
// Report aggregates many results.
@@ -53,8 +53,8 @@ type Report struct {
5353
func DefaultCases() []Case {
5454
return []Case{
5555
{
56-
ID: "py-hello",
57-
Query: "Create hello.py that defines greet(name) returning 'Hello, {name}!'. Add a tiny pytest in test_hello.py.",
56+
ID: "py-hello",
57+
Query: "Create hello.py that defines greet(name) returning 'Hello, {name}!'. Add a tiny pytest in test_hello.py.",
5858
ExpectFiles: []string{"hello.py", "test_hello.py"},
5959
ExpectSubstr: map[string]string{
6060
"hello.py": "def greet",

‎pkg/hooks/hooks.go‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ type Hook struct {
2929

3030
// Runner executes Pre/Post tool hooks.
3131
type Runner struct {
32-
Root string
33-
Cfg Config
34-
Log func(string, ...interface{})
32+
Root string
33+
Cfg Config
34+
Log func(string, ...interface{})
3535
}
3636

3737
// Load reads .slmcode/hooks.json (or path). Missing file → empty config.

‎pkg/loop/runner.go‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,24 @@ func (r *Runner) reviewAndCorrect(ctx context.Context, board *plan.Board, t plan
626626
claimsFail := quality.ClaimsFailedInOutput(current.Output)
627627
smokeFiles := append([]string{}, current.Files...)
628628
smokeFiles = append(smokeFiles, parseFilesChanged(current.Output)...)
629+
// Review-time smoke insurance: if PostWorkerSmoke somehow didn't attach a
630+
// section (corrector overwrite, truncated finalize), run it now so
631+
// RequireSmoke cannot false-reject a green compile/test.
632+
if r.RequireSmoke && r.PostWorkerSmoke && quality.ShouldSmokeTask(current) &&
633+
!quality.SmokePassedInOutput(current.Output) && !smokeFail && !renameDisk &&
634+
quality.HasSmokeCommand(r.Root, smokeFiles) {
635+
sr := quality.RunPostWorkerSmoke(ctx, r.Root, current, r.Timeout)
636+
if sec := quality.FormatSmokeSection(sr); sec != "" {
637+
current.Output = strings.TrimSpace(current.Output) + sec
638+
board.UpdateTask(current)
639+
if sr.Ran && !sr.OK {
640+
smokeFail = true
641+
r.Log("%s review-time smoke FAILED: %s", current.ID, sr.Command)
642+
} else if sr.Ran {
643+
r.Log("%s review-time smoke PASSED: %s", current.ID, sr.Command)
644+
}
645+
}
646+
}
629647
smokeMissing := r.RequireSmoke && quality.HasSmokeCommand(r.Root, smokeFiles) &&
630648
!quality.SmokePassedInOutput(current.Output) && !smokeFail && !renameDisk
631649
// Rename on disk wins even when scope claims are noisy (weak tool log).

‎pkg/loop/think_multipass_test.go‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import (
1616

1717
// incompleteFirstExec returns incomplete worker JSON, then a complete corrector fix.
1818
type incompleteFirstExec struct {
19-
mu sync.Mutex
20-
workerCalls int
19+
mu sync.Mutex
20+
workerCalls int
2121
correctorCalls int
2222
}
2323

@@ -67,7 +67,7 @@ func TestThinkPassesForcesWorkerCritique(t *testing.T) {
6767
board := &plan.Board{Tasks: []plan.Task{{
6868
ID: "T1", Title: "Touch hello", Role: plan.RoleWorker,
6969
Acceptance: "file exists", Files: []string{"hello.py"},
70-
Column: plan.ColReadyToDev,
70+
Column: plan.ColReadyToDev,
7171
Description: "noop touch",
7272
}}}
7373
if err := r.RunBoard(context.Background(), board); err != nil {

‎pkg/mcp/client.go‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ import (
2020

2121
// ServerConfig describes one MCP server (stdio or HTTP).
2222
type ServerConfig struct {
23-
Name string `yaml:"name" json:"name"`
24-
Command string `yaml:"command,omitempty" json:"command,omitempty"`
25-
Args []string `yaml:"args,omitempty" json:"args,omitempty"`
26-
Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
27-
URL string `yaml:"url,omitempty" json:"url,omitempty"` // HTTP JSON-RPC
28-
ReadOnly bool `yaml:"read_only" json:"read_only"` // default true
23+
Name string `yaml:"name" json:"name"`
24+
Command string `yaml:"command,omitempty" json:"command,omitempty"`
25+
Args []string `yaml:"args,omitempty" json:"args,omitempty"`
26+
Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
27+
URL string `yaml:"url,omitempty" json:"url,omitempty"` // HTTP JSON-RPC
28+
ReadOnly bool `yaml:"read_only" json:"read_only"` // default true
2929
}
3030

3131
// ToolInfo is a discovered MCP tool.

‎pkg/orchestrator/orchestrator.go‎

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -56,20 +56,20 @@ type Result struct {
5656
}
5757

5858
type Orchestrator struct {
59-
cfg *config.Config
60-
store *contextstore.Store
61-
boardStore *plan.LiveStore
62-
packer *contextstore.Packer
63-
skills *skills.Loader
64-
llm *llm.ProviderManager
65-
tools *tools.ToolRegistry
66-
focus *workspace.FocusGuard
67-
factory *agents.Factory
68-
registry *ggagent.AgentRegistry
69-
executor loop.SubAgentRunner
70-
shared *ggagent.SharedState
71-
think *multipass.Runner
72-
claude *backends.ClaudeCodeRunner
59+
cfg *config.Config
60+
store *contextstore.Store
61+
boardStore *plan.LiveStore
62+
packer *contextstore.Packer
63+
skills *skills.Loader
64+
llm *llm.ProviderManager
65+
tools *tools.ToolRegistry
66+
focus *workspace.FocusGuard
67+
factory *agents.Factory
68+
registry *ggagent.AgentRegistry
69+
executor loop.SubAgentRunner
70+
shared *ggagent.SharedState
71+
think *multipass.Runner
72+
claude *backends.ClaudeCodeRunner
7373
onEvent EventHandler
7474
onAsk AskHandler
7575
onPlanApprove PlanApproveHandler
@@ -133,8 +133,8 @@ func New(cfg *config.Config) (*Orchestrator, error) {
133133
ShellPermission: cfg.ShellPermission,
134134
DryRun: cfg.DryRun, Permission: cfg.Permission, SlmDir: cfg.SlmDir(),
135135
Focus: focus, Hooks: hooksRunner,
136-
ShellAskTimeout: cfg.ShellAskTimeout,
137-
AutoApprove: cfg.AutoApprove,
136+
ShellAskTimeout: cfg.ShellAskTimeout,
137+
AutoApprove: cfg.AutoApprove,
138138
DisableWriteGuard: !cfg.WriteGuard,
139139
DisableReadBeforeEdit: !cfg.ReadBeforeEdit,
140140
DisableShellWriteGuard: !cfg.ShellWriteGuard,

0 commit comments

Comments
 (0)