Skip to content

Commit 60da682

Browse files
author
SIN CI
committed
fix: gosec version pin, swebench dataset path, gitignore build/, pyproject license
- .github/workflows/lint.yml: pin securego/gosec@master → @v2.22.2 - .github/workflows/swebench-nightly.yml: fix default dataset path (swebench-lite.json → swe-bench-example.json) - .gitignore: add build/ (setuptools build artifact) - pyproject.toml: fix deprecated license table form → string form - Uncommitted subagent improvements across 21 Go files (formatting, test hardening, error handling improvements) - slugify_helper.go: preserve subagent's structural refactor of / case
1 parent ebe82c8 commit 60da682

25 files changed

Lines changed: 406 additions & 286 deletions

‎.github/workflows/lint.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ jobs:
5353
steps:
5454
- uses: actions/checkout@v4
5555
- name: Run gosec
56-
uses: securego/gosec@master
56+
uses: securego/gosec@v2.22.2
5757
with:
5858
args: "-no-fail -fmt sarif -out gosec.sarif ./..."
5959
- name: Upload SARIF

‎.github/workflows/swebench-nightly.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ on:
1010
dataset:
1111
description: 'SWE-bench dataset path'
1212
required: false
13-
default: 'evals/swebench-lite.json'
13+
default: 'evals/swe-bench-example.json'
1414
max_instances:
1515
description: 'Max instances to evaluate (0 = all)'
1616
required: false
@@ -30,7 +30,7 @@ jobs:
3030
echo "::error::N8N_CI_WEBHOOK_URL secret is not configured"
3131
exit 1
3232
fi
33-
DATASET="${{ inputs.dataset || 'evals/swebench-lite.json' }}"
33+
DATASET="${{ inputs.dataset || 'evals/swe-bench-example.json' }}"
3434
MAX_INSTANCES="${{ inputs.max_instances || '10' }}"
3535
curl -fsS -X POST \
3636
-H "Content-Type: application/json" \

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ coverage.json
4848
coverage.map.out
4949
coverage.out
5050
dist/
51+
build/
5152
htmlcov/
5253
node_modules
5354
PRD.md

‎cmd/sin-code/autodev_issue_cmd.go‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,13 @@ func newGoalAddFromIssueCmd() *cobra.Command {
6868
if num <= 0 {
6969
return fmt.Errorf("issue number must be a positive integer")
7070
}
71-
if repo == "" {
72-
var err error
73-
repo, err = detectRepo()
74-
if err != nil {
75-
return err
71+
if repo == "" {
72+
var err error
73+
repo, err = detectRepo()
74+
if err != nil {
75+
return err
76+
}
7677
}
77-
}
7878
ctx := cmd.Context()
7979
issue, err := fetchIssue(ctx, repo, num)
8080
if err != nil {

‎cmd/sin-code/internal/agentloop/budget_test.go‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -263,10 +263,10 @@ func TestPerTurnBudget_Error_MatchByErrorsIs(b *testing.T) {
263263

264264
func TestPerTurnBudget_LazyConstruct(b *testing.T) {
265265
cases := []struct {
266-
name string
267-
perTurn, perTurnThinking int
268-
wantTracker bool
269-
wantCapThinking, wantCapTokens int
266+
name string
267+
perTurn, perTurnThinking int
268+
wantTracker bool
269+
wantCapThinking, wantCapTokens int
270270
}{
271271
{"both_zero_no_tracker", 0, 0, false, -1, -1},
272272
{"tokens_only", 100, 0, true, -1, 100},

‎cmd/sin-code/internal/agentloop/compaction_helpers.go‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"strings"
77
)
88

9-
func toLowerTrim(s string) string { return strings.ToLower(strings.TrimSpace(s)) }
9+
func toLowerTrim(s string) string { return strings.ToLower(strings.TrimSpace(s)) }
1010
func errUnknownMode(s string) error { return fmt.Errorf("agentloop: unknown compaction mode %q", s) }
11-
func errUnknownTrigger(s string) error { return fmt.Errorf("agentloop: unknown compaction trigger %q", s) }
11+
func errUnknownTrigger(s string) error {
12+
return fmt.Errorf("agentloop: unknown compaction trigger %q", s)
13+
}

‎cmd/sin-code/internal/agentloop/compaction_test.go‎

Lines changed: 63 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -368,65 +368,110 @@ func TestCompact2_Off_NoOp(t *testing.T) {
368368
c := NewCompactor(stubSummarizerCM("LLM"))
369369
msgs := []session.Message{{Role: "system", Content: "sys"}, {Role: "user", Content: "u1"}, {Role: "assistant", Content: "a1"}}
370370
r, _ := c.Compact2(context.Background(), CompactInput{Messages: msgs, Mode: ContextCompactionOff, MaxTokens: 100})
371-
if len(r.Kept) != len(msgs) { t.Errorf("off no-op; got %d want %d", len(r.Kept), len(msgs)) }
371+
if len(r.Kept) != len(msgs) {
372+
t.Errorf("off no-op; got %d want %d", len(r.Kept), len(msgs))
373+
}
372374
}
373375

374376
func TestCompact2_Deterministic_EvidencePreserved(t *testing.T) {
375377
c := NewCompactor(stubSummarizerCM("LLM"))
376378
c.Configure(CompactorConfig{Mode: ContextCompactionDeterministic, Trigger: CompactionTriggerTokens, Threshold: 0.8, MaxTokens: 8000, PreserveEvidence: true, RecentTurns: 4})
377379
msgs := make([]session.Message, 30)
378-
for i := range msgs { msgs[i] = session.Message{Role: "user", Content: "noise"}; if i%2 == 1 { msgs[i].Role = "assistant" } }
380+
for i := range msgs {
381+
msgs[i] = session.Message{Role: "user", Content: "noise"}
382+
if i%2 == 1 {
383+
msgs[i].Role = "assistant"
384+
}
385+
}
379386
msgs[0] = session.Message{Role: "system", Content: "SYSTEM"}
380387
msgs[15] = session.Message{Role: "assistant", Content: "VERIFY: VERIFICATION PASSED"}
381388
r, _ := c.Compact2(context.Background(), CompactInput{Messages: msgs, Mode: ContextCompactionDeterministic, MaxTokens: 8000})
382-
if len(r.Kept) >= len(msgs) { t.Errorf("deterministic reduce; got %d from %d", len(r.Kept), len(msgs)) }
383-
found := false; for _, m := range r.Kept { if m.Content == "VERIFY: VERIFICATION PASSED" { found = true } }
384-
if !found { t.Error("missing VERIFICATION PASSED") }
385-
if r.Summary != "" { t.Errorf("deterministic summary non-empty: %q", r.Summary) }
389+
if len(r.Kept) >= len(msgs) {
390+
t.Errorf("deterministic reduce; got %d from %d", len(r.Kept), len(msgs))
391+
}
392+
found := false
393+
for _, m := range r.Kept {
394+
if m.Content == "VERIFY: VERIFICATION PASSED" {
395+
found = true
396+
}
397+
}
398+
if !found {
399+
t.Error("missing VERIFICATION PASSED")
400+
}
401+
if r.Summary != "" {
402+
t.Errorf("deterministic summary non-empty: %q", r.Summary)
403+
}
386404
}
387405

388406
func TestCompact2_LLM_SingleSummary(t *testing.T) {
389407
c := NewCompactor(stubSummarizerCM("LLM-SUMMARY"))
390408
c.Configure(CompactorConfig{Mode: ContextCompactionLLM, MaxTokens: 8000, PreserveEvidence: true, RecentTurns: 4})
391409
msgs := []session.Message{{Role: "system", Content: "S"}, {Role: "user", Content: "g"}, {Role: "assistant", Content: "p"}}
392410
r, _ := c.Compact2(context.Background(), CompactInput{Messages: msgs, Mode: ContextCompactionLLM, MaxTokens: 8000})
393-
if len(r.Kept) != 1 || r.Kept[0].Role != "system" { t.Error("llm single system message expected") }
394-
if !strings.Contains(r.Summary, "LLM-SUMMARY") { t.Errorf("summary missing tag: %q", r.Summary) }
395-
if !r.Mode.IsLossy() { t.Error("llm should be lossy") }
411+
if len(r.Kept) != 1 || r.Kept[0].Role != "system" {
412+
t.Error("llm single system message expected")
413+
}
414+
if !strings.Contains(r.Summary, "LLM-SUMMARY") {
415+
t.Errorf("summary missing tag: %q", r.Summary)
416+
}
417+
if !r.Mode.IsLossy() {
418+
t.Error("llm should be lossy")
419+
}
396420
}
397421

398422
func TestCompact2_Hybrid_EvidencePlusSummary(t *testing.T) {
399423
c := NewCompactor(stubSummarizerCM("LLM-HYBRID"))
400424
c.Configure(CompactorConfig{Mode: ContextCompactionHybrid, RecentTurns: 2, PreserveEvidence: true, MaxTokens: 8000})
401425
msgs := make([]session.Message, 20)
402-
for i := range msgs { msgs[i] = session.Message{Role: "user", Content: "m" + strconv.Itoa(i)}; if i%2 == 1 { msgs[i].Role = "assistant" } }
426+
for i := range msgs {
427+
msgs[i] = session.Message{Role: "user", Content: "m" + strconv.Itoa(i)}
428+
if i%2 == 1 {
429+
msgs[i].Role = "assistant"
430+
}
431+
}
403432
msgs[0].Role = "system"
404433
msgs[15].Content = "VERIFICATION PASSED"
405434
r, _ := c.Compact2(context.Background(), CompactInput{Messages: msgs, Mode: ContextCompactionHybrid, MaxTokens: 8000})
406-
if !strings.Contains(r.Summary, "LLM-HYBRID") { t.Errorf("hybrid summary missing tag: %q", r.Summary) }
407-
if len(r.Kept) == 0 || r.Kept[0].Role != "system" { t.Error("hybrid Kept[0] should be system") }
435+
if !strings.Contains(r.Summary, "LLM-HYBRID") {
436+
t.Errorf("hybrid summary missing tag: %q", r.Summary)
437+
}
438+
if len(r.Kept) == 0 || r.Kept[0].Role != "system" {
439+
t.Error("hybrid Kept[0] should be system")
440+
}
408441
}
409442

410443
func TestCompact2_LegacyStrategy_Unchanged(t *testing.T) {
411444
c := NewCompactor(stubSummarizerCM("LLM"))
412445
msgs := makeTestMessages(20)
413446
r, _ := c.Compact2(context.Background(), CompactInput{Messages: msgs, Strategy: CompactionTruncate, MaxTokens: 3000})
414-
if len(r.Kept) >= len(msgs) { t.Error("legacy truncate should reduce") }
447+
if len(r.Kept) >= len(msgs) {
448+
t.Error("legacy truncate should reduce")
449+
}
415450
}
416451

417452
func TestCompact2_ByteStableDeterministic(t *testing.T) {
418453
cfg := CompactorConfig{Mode: ContextCompactionDeterministic, RecentTurns: 4, PreserveEvidence: true, MaxTokens: 8000}
419454
msgs := []session.Message{{Role: "system", Content: "S"}, {Role: "user", Content: "U1"}, {Role: "assistant", Content: "A1"}, {Role: "tool", Content: "T1", ToolCallID: "tc1"}, {Role: "assistant", Content: "VERIFICATION PASSED"}}
420-
c1 := NewCompactor(stubSummarizerCM("FIXED")); c1.Configure(cfg)
421-
c2 := NewCompactor(stubSummarizerCM("FIXED")); c2.Configure(cfg)
455+
c1 := NewCompactor(stubSummarizerCM("FIXED"))
456+
c1.Configure(cfg)
457+
c2 := NewCompactor(stubSummarizerCM("FIXED"))
458+
c2.Configure(cfg)
422459
r1, _ := c1.Compact2(context.Background(), CompactInput{Messages: msgs, Mode: cfg.Mode, MaxTokens: cfg.MaxTokens})
423460
r2, _ := c2.Compact2(context.Background(), CompactInput{Messages: msgs, Mode: cfg.Mode, MaxTokens: cfg.MaxTokens})
424-
if len(r1.Kept) != len(r2.Kept) { t.Errorf("byte-stable r1=%d r2=%d", len(r1.Kept), len(r2.Kept)) }
425-
for i := range r1.Kept { if r1.Kept[i].Role != r2.Kept[i].Role || r1.Kept[i].Content != r2.Kept[i].Content { t.Errorf("Kept[%d] differs", i) } }
461+
if len(r1.Kept) != len(r2.Kept) {
462+
t.Errorf("byte-stable r1=%d r2=%d", len(r1.Kept), len(r2.Kept))
463+
}
464+
for i := range r1.Kept {
465+
if r1.Kept[i].Role != r2.Kept[i].Role || r1.Kept[i].Content != r2.Kept[i].Content {
466+
t.Errorf("Kept[%d] differs", i)
467+
}
468+
}
426469
}
427470

428471
func TestIdentifyEvidence_FindsMarkers(t *testing.T) {
429472
msgs := []session.Message{{Role: "system", Content: "s"}, {Role: "user", Content: "u"}, {Role: "assistant", Content: "VERIFICATION PASSED"}, {Role: "user", Content: "m"}, {Role: "assistant", Content: "Open acceptance criteria: yes"}}
430473
idx := evidenceIndices(msgs)
431-
if len(idx) != 2 { t.Errorf("evidenceIndices size: got %d want 2 (idx=%v)", len(idx), idx) }
474+
if len(idx) != 2 {
475+
t.Errorf("evidenceIndices size: got %d want 2 (idx=%v)", len(idx), idx)
476+
}
432477
}

‎cmd/sin-code/internal/agentloop/loop_detector_test.go‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func TestNewLoopDetector_Defaults(t *testing.T) {
1818
}
1919

2020
func TestLoopDetector_SameCallRepeated(t *testing.T) {
21-
d := NewSimpleLoopDetector(3, 2)
21+
d := NewSimpleLoopDetector(3, 2)
2222
if d.Record("sin_read") {
2323
t.Fatal("single call should not loop")
2424
}
@@ -31,7 +31,7 @@ func TestLoopDetector_SameCallRepeated(t *testing.T) {
3131
}
3232

3333
func TestLoopDetector_NoLoop(t *testing.T) {
34-
d := NewSimpleLoopDetector(3, 2)
34+
d := NewSimpleLoopDetector(3, 2)
3535
for _, c := range []string{"sin_read", "sin_edit", "sin_read", "sin_edit", "sin_test"} {
3636
if d.Record(c) {
3737
t.Fatalf("Record(%q) unexpectedly detected a loop", c)
@@ -43,7 +43,7 @@ func TestLoopDetector_NoLoop(t *testing.T) {
4343
}
4444

4545
func TestLoopDetector_SequenceRepeated(t *testing.T) {
46-
d := NewSimpleLoopDetector(3, 2)
46+
d := NewSimpleLoopDetector(3, 2)
4747
// A,B repeated 3 times => loop on the 6th call.
4848
seq := []string{"sin_read", "sin_edit", "sin_read", "sin_edit", "sin_read", "sin_edit"}
4949
for i, c := range seq {
@@ -59,7 +59,7 @@ func TestLoopDetector_SequenceRepeated(t *testing.T) {
5959
}
6060

6161
func TestLoopDetector_AlternatingPattern(t *testing.T) {
62-
d := NewSimpleLoopDetector(2, 2)
62+
d := NewSimpleLoopDetector(2, 2)
6363
// A,B,A,B => windowSize=2, maxRepeats=2 => need=4.
6464
if d.Record("sin_read") {
6565
t.Fatal("1 should not loop")
@@ -76,7 +76,7 @@ func TestLoopDetector_AlternatingPattern(t *testing.T) {
7676
}
7777

7878
func TestLoopDetector_Reset(t *testing.T) {
79-
d := NewSimpleLoopDetector(2, 1)
79+
d := NewSimpleLoopDetector(2, 1)
8080
d.Record("sin_read")
8181
d.Record("sin_read")
8282
if !d.IsLooping() {
@@ -92,7 +92,7 @@ func TestLoopDetector_Reset(t *testing.T) {
9292
}
9393

9494
func TestLoopDetector_IsLoopingIdempotent(t *testing.T) {
95-
d := NewSimpleLoopDetector(2, 1)
95+
d := NewSimpleLoopDetector(2, 1)
9696
d.Record("sin_read")
9797
d.Record("sin_read")
9898
if !d.IsLooping() {
@@ -104,7 +104,7 @@ func TestLoopDetector_IsLoopingIdempotent(t *testing.T) {
104104
}
105105

106106
func TestLoopDetector_Concurrent(t *testing.T) {
107-
d := NewSimpleLoopDetector(100, 2)
107+
d := NewSimpleLoopDetector(100, 2)
108108
var wg sync.WaitGroup
109109
for g := 0; g < 20; g++ {
110110
wg.Add(1)

‎cmd/sin-code/internal/agentloop/session_context.go‎

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ func (c *ContextInjector) Enabled() bool {
176176
// - lessons sorted by occurrences DESC then last_seen DESC
177177
// - memory sorted by Prime's internal order (already score-ranked)
178178
// - goals sorted by priority DESC then id ASC for ties
179+
//
179180
// Build is a convenience wrapper around Invoke that uses an empty
180181
// prompt. It is the no-arg-prompt entry point used by the agent loop's
181182
// SessionContext field (issue #379).
@@ -475,48 +476,80 @@ func (b *SessionContextBuilder) Build(ctx context.Context) (string, error) {
475476
}
476477
var lessons, memories, goals, todos, sessionSummary, autoMemory []string
477478
if b.config.IncludeLessons && b.lessonsStore != nil {
478-
if err := ctx.Err(); err != nil { return "", err }
479+
if err := ctx.Err(); err != nil {
480+
return "", err
481+
}
479482
ls, err := b.lessonsStore.Recent(b.config.RecentLessons)
480-
if err != nil { return "", err }
483+
if err != nil {
484+
return "", err
485+
}
481486
lessons = ls
482487
}
483488
if b.config.IncludeMemories && b.memoryStore != nil {
484-
if err := ctx.Err(); err != nil { return "", err }
489+
if err := ctx.Err(); err != nil {
490+
return "", err
491+
}
485492
ms, err := b.memoryStore.Query(b.config.MemoryQuery, b.config.TopKMemories)
486-
if err != nil { return "", err }
493+
if err != nil {
494+
return "", err
495+
}
487496
memories = ms
488497
}
489498
if b.config.IncludeGoals && b.goalStore != nil {
490-
if err := ctx.Err(); err != nil { return "", err }
499+
if err := ctx.Err(); err != nil {
500+
return "", err
501+
}
491502
gs, err := b.goalStore.Active()
492-
if err != nil { return "", err }
503+
if err != nil {
504+
return "", err
505+
}
493506
goals = gs
494507
}
495508
if b.config.IncludeTodos && b.todoStore != nil {
496-
if err := ctx.Err(); err != nil { return "", err }
509+
if err := ctx.Err(); err != nil {
510+
return "", err
511+
}
497512
ts, err := b.todoStore.Open(false)
498-
if err != nil { return "", err }
513+
if err != nil {
514+
return "", err
515+
}
499516
todos = ts
500517
}
501518
if b.config.IncludeSession && b.sessionStore != nil {
502-
if err := ctx.Err(); err != nil { return "", err }
519+
if err := ctx.Err(); err != nil {
520+
return "", err
521+
}
503522
ss, err := b.sessionStore.Summary("")
504-
if err != nil { return "", err }
505-
if ss != "" { sessionSummary = []string{ss} }
523+
if err != nil {
524+
return "", err
525+
}
526+
if ss != "" {
527+
sessionSummary = []string{ss}
528+
}
506529
}
507530
if b.config.IncludeAutoMemory && b.autoMemoryStore != nil {
508-
if err := ctx.Err(); err != nil { return "", err }
531+
if err := ctx.Err(); err != nil {
532+
return "", err
533+
}
509534
am, err := b.autoMemoryStore.IndexBytes()
510-
if err != nil { return "", err }
511-
if len(am) > 0 { autoMemory = []string{string(am)} }
535+
if err != nil {
536+
return "", err
537+
}
538+
if len(am) > 0 {
539+
autoMemory = []string{string(am)}
540+
}
512541
}
513542
preamble := b.Format(lessons, memories, goals, todos, sessionSummary, autoMemory)
514543
maxChars := b.config.MaxPreambleChars
515-
if maxChars <= 0 { maxChars = defaultMaxPreambleChars }
544+
if maxChars <= 0 {
545+
maxChars = defaultMaxPreambleChars
546+
}
516547
marker := "\n[...truncated]"
517548
if len(preamble) > maxChars {
518549
cutAt := maxChars - len(marker)
519-
if cutAt < 0 { cutAt = 0 }
550+
if cutAt < 0 {
551+
cutAt = 0
552+
}
520553
preamble = preamble[:cutAt] + marker
521554
}
522555
return preamble, nil
@@ -525,7 +558,9 @@ func (b *SessionContextBuilder) Build(ctx context.Context) (string, error) {
525558
func (b *SessionContextBuilder) Format(lessons, memories, goals, todos, sessionSummary, autoMemory []string) string {
526559
var sb strings.Builder
527560
writeSection := func(heading string, items []string) {
528-
if len(items) == 0 { return }
561+
if len(items) == 0 {
562+
return
563+
}
529564
sb.WriteString("## ")
530565
sb.WriteString(heading)
531566
sb.WriteByte('\n')

0 commit comments

Comments
 (0)