Skip to content

Commit 1b741a7

Browse files
committed
feat: context budget tuning, merge-and-mark purge, dedup improvements
- Raise context limits: goal 500, task nodes 20, constraints 500/800 chars, default 25 nodes/5 per query, wave context 400/12000, doc files 5000 - Add ARCHITECTURE.md to first task context (capped 8K) - Replace destructive purge with workspace-scoped merge-and-mark stale tracking - Add naiveStem + expanded stop words for better node deduplication (threshold 0.45) - Remove dead audit service, formatter, crash logger references - Suppress noisy warnings to slog.Debug (relationship links, stale counts)
1 parent fe54f3a commit 1b741a7

14 files changed

Lines changed: 239 additions & 546 deletions

File tree

internal/agents/core/base.go

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package core
66
import (
77
"context"
88
"fmt"
9-
"strings"
109
"time"
1110

1211
"github.com/cloudwego/eino/schema"
@@ -63,26 +62,6 @@ func (b *BaseAgent) Generate(ctx context.Context, messages []*schema.Message) (s
6362
return resp.Content, nil
6463
}
6564

66-
// formatMessagesForLogging formats messages for crash log context.
67-
func formatMessagesForLogging(messages []*schema.Message) string {
68-
var parts []string
69-
for _, m := range messages {
70-
var role string
71-
switch m.Role {
72-
case schema.User:
73-
role = "user"
74-
case schema.Assistant:
75-
role = "assistant"
76-
case schema.System:
77-
role = "system"
78-
default:
79-
role = "unknown"
80-
}
81-
parts = append(parts, fmt.Sprintf("[%s]: %s", role, m.Content))
82-
}
83-
return strings.Join(parts, "\n---\n")
84-
}
85-
8665
// GenerateFromPrompt is a convenience method for single-prompt calls.
8766
func (b *BaseAgent) GenerateFromPrompt(ctx context.Context, prompt string) (string, error) {
8867
return b.Generate(ctx, []*schema.Message{schema.UserMessage(prompt)})

internal/agents/tools/context.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ func (g *ContextGatherer) GatherMarkdownDocs() string {
196196

197197
// Root and docs/ (existing behavior)
198198
gatherFromDir(g.BasePath, "", 4000)
199-
gatherFromDir(filepath.Join(g.BasePath, "docs"), "docs", 3000)
199+
gatherFromDir(filepath.Join(g.BasePath, "docs"), "docs", 5000)
200200

201201
// NEW: Recursively gather package-level READMEs from common source directories
202202
// These contain critical implementation details (auth patterns, error handling, etc.)
@@ -272,7 +272,7 @@ func (g *ContextGatherer) GatherMarkdownDocs() string {
272272
return nil
273273
}
274274
// Package docs can be longer - they contain implementation details
275-
maxLen := 3000
275+
maxLen := 5000
276276
truncated := len(content) > maxLen
277277
if truncated {
278278
content = append(content[:maxLen], []byte("\n...[truncated]")...)
@@ -334,9 +334,9 @@ func (g *ContextGatherer) GatherKeyFiles() string {
334334
if err != nil {
335335
continue
336336
}
337-
truncated := len(content) > 3000
337+
truncated := len(content) > 5000
338338
if truncated {
339-
content = append(content[:3000], []byte("\n...[truncated]")...)
339+
content = append(content[:5000], []byte("\n...[truncated]")...)
340340
}
341341

342342
formatted := fmt.Sprintf("## %s\n```\n%s\n```\n\n", relPath, string(content))
@@ -355,7 +355,7 @@ func (g *ContextGatherer) GatherKeyFiles() string {
355355
// GatherCIConfigs reads CI/CD workflow files from .github, .gitlab, .circleci.
356356
func (g *ContextGatherer) GatherCIConfigs() string {
357357
var sb strings.Builder
358-
maxPerFile := 3000
358+
maxPerFile := 5000
359359

360360
// GitHub Actions
361361
ghWorkflows, ghErr := utils.SafeJoin(g.BasePath, filepath.Join(".github", "workflows"))

internal/app/ask.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313

1414
"github.com/cloudwego/eino/schema"
1515
"github.com/josephgoksu/TaskWing/internal/codeintel"
16-
"github.com/josephgoksu/TaskWing/internal/freshness"
1716
"github.com/josephgoksu/TaskWing/internal/knowledge"
1817
"github.com/josephgoksu/TaskWing/internal/llm"
1918
"github.com/josephgoksu/TaskWing/internal/memory"
@@ -546,7 +545,7 @@ func annotateResultFreshness(basePath string, results []knowledge.NodeResponse)
546545
// Reconstruct evidence JSON from the parsed EvidenceRef slice
547546
// for the freshness checker
548547
if len(node.Evidence) == 0 {
549-
node.FreshnessStatus = string(freshness.StatusNoEvidence)
548+
node.FreshnessStatus = string(knowledge.StatusNoEvidence)
550549
continue
551550
}
552551

@@ -572,10 +571,10 @@ func annotateResultFreshness(basePath string, results []knowledge.NodeResponse)
572571
refTime = time.Now().Add(-24 * time.Hour)
573572
}
574573

575-
result := freshness.Check(basePath, string(evJSON), refTime)
574+
result := knowledge.Check(basePath, string(evJSON), refTime)
576575

577576
node.FreshnessStatus = string(result.Status)
578-
node.FreshnessNote = freshness.FormatStatus(result, nil)
577+
node.FreshnessNote = knowledge.FormatStatus(result, nil)
579578
node.StaleFiles = result.StaleFiles
580579

581580
// Adjust confidence if stale or missing

internal/app/plan.go

Lines changed: 38 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,8 @@ func (a *PlanApp) defaultTaskEnricher(ctx context.Context, queries []string) (st
182182

183183
opts := knowledge.DefaultContextOptions()
184184
opts.Query = query
185-
opts.IncludeArchitectureMD = false // Too large for per-task context
186-
opts.MaxNodes = 8 // Compact for task embedding
185+
opts.IncludeArchitectureMD = false // Included selectively for first task
186+
opts.MaxNodes = 20 // Richer context with batch embeddings
187187
opts.UseLLMQueries = false // Use queries directly for speed
188188

189189
memoryPath, _ := config.GetMemoryBasePath()
@@ -679,7 +679,7 @@ func (a *PlanApp) Generate(ctx context.Context, opts GenerateOptions) (*Generate
679679
}
680680

681681
semanticResult := middleware.Validate(&planner.LLMPlanResponse{
682-
GoalSummary: truncateString(opts.Goal, 100),
682+
GoalSummary: truncateString(opts.Goal, 500),
683683
Rationale: opts.EnrichedGoal,
684684
Tasks: plannerTasks,
685685
EstimatedComplexity: "medium", // Default
@@ -863,165 +863,32 @@ func truncateString(s string, maxLen int) string {
863863
return s[:maxLen]
864864
}
865865

866-
// Audit runs verification on a completed plan.
867-
func (a *PlanApp) Audit(ctx context.Context, opts AuditOptions) (*AuditResult, error) {
868-
repo := a.Repo
869-
llmCfg := a.ctx.LLMCfg
870-
871-
// Determine which plan to audit
872-
var plan *task.Plan
873-
var err error
874-
875-
if opts.PlanID != "" {
876-
plan, err = repo.GetPlan(opts.PlanID)
877-
if err != nil {
878-
return &AuditResult{
879-
Success: false,
880-
Message: fmt.Sprintf("Failed to get plan: %v", err),
881-
}, nil
882-
}
883-
} else {
884-
plan, err = repo.GetActivePlan()
885-
if err != nil {
886-
return &AuditResult{
887-
Success: false,
888-
Message: fmt.Sprintf("Failed to get active plan: %v", err),
889-
}, nil
890-
}
891-
}
892-
893-
if plan == nil {
894-
return &AuditResult{
895-
Success: false,
896-
Message: "No plan found. Create a plan first with plan action=clarify and then plan action=generate.",
897-
Hint: "Use plan action=clarify to start defining your development goal.",
898-
}, nil
899-
}
900-
901-
// Check if plan has completed tasks
902-
completedCount := 0
903-
for _, t := range plan.Tasks {
904-
if t.Status == task.StatusCompleted {
905-
completedCount++
906-
}
907-
}
908-
909-
if completedCount == 0 {
910-
return &AuditResult{
911-
Success: false,
912-
PlanID: plan.ID,
913-
Message: "No completed tasks to impl. Complete tasks first.",
914-
Hint: "Use task action=next to get the next pending task.",
915-
}, nil
916-
}
917-
918-
// Get working directory
919-
workDir, _ := os.Getwd()
920-
921-
// Create audit service
922-
auditService := impl.NewService(workDir, llmCfg)
923-
924-
if !opts.AutoFix {
925-
auditResult, err := auditService.Audit(ctx, plan)
926-
if err != nil {
927-
return &AuditResult{
928-
Success: false,
929-
PlanID: plan.ID,
930-
Message: fmt.Sprintf("Audit failed: %v", err),
931-
}, nil
932-
}
933-
934-
result := &AuditResult{
935-
Success: true,
936-
PlanID: plan.ID,
937-
RetryCount: 1,
938-
BuildPassed: auditResult.BuildResult.Passed,
939-
TestsPassed: auditResult.TestResult.Passed,
940-
SemanticIssues: auditResult.SemanticResult.Issues,
941-
}
942-
943-
// Update plan status in database
944-
var newStatus task.PlanStatus
945-
if auditResult.Status == "passed" {
946-
result.Status = "verified"
947-
newStatus = task.PlanStatusVerified
948-
result.Message = "Plan verified successfully. All checks passed."
949-
result.Hint = "The plan is complete and verified. You can create a PR or start a new plan."
950-
} else {
951-
result.Status = "needs_revision"
952-
newStatus = task.PlanStatusNeedsRevision
953-
result.Message = "Plan needs revision. One or more checks failed."
954-
result.Hint = "Review the failed checks and fix them, then run audit again."
955-
}
956-
result.PlanStatus = newStatus
957-
958-
// Store audit report
959-
report := task.AuditReport{
960-
Status: auditResult.Status,
961-
BuildOutput: auditResult.BuildResult.Output,
962-
TestOutput: auditResult.TestResult.Output,
963-
SemanticIssues: auditResult.SemanticResult.Issues,
964-
RetryCount: 1,
965-
CompletedAt: time.Now().UTC(),
966-
}
967-
if !auditResult.BuildResult.Passed && auditResult.BuildResult.Error != "" {
968-
report.ErrorMessage = "Build failed: " + auditResult.BuildResult.Error
969-
} else if !auditResult.TestResult.Passed && auditResult.TestResult.Error != "" {
970-
report.ErrorMessage = "Tests failed: " + auditResult.TestResult.Error
971-
}
972-
reportJSON, marshalErr := json.Marshal(report)
973-
if marshalErr == nil {
974-
_ = repo.UpdatePlanAuditReport(plan.ID, newStatus, string(reportJSON))
975-
}
976-
977-
return result, nil
866+
// loadArchitectureMD reads .taskwing/ARCHITECTURE.md for the current project.
867+
// Caps at 8000 chars to avoid blowing up the first task's context.
868+
func loadArchitectureMD() string {
869+
memoryPath, err := config.GetMemoryBasePath()
870+
if err != nil || memoryPath == "" {
871+
return ""
978872
}
979-
980-
// Run audit with auto-fix
981-
autoFixResult, err := auditService.AuditWithAutoFix(ctx, plan)
873+
basePath := filepath.Dir(filepath.Dir(memoryPath))
874+
content, err := os.ReadFile(filepath.Join(basePath, ".taskwing", "ARCHITECTURE.md"))
982875
if err != nil {
983-
return &AuditResult{
984-
Success: false,
985-
PlanID: plan.ID,
986-
Message: fmt.Sprintf("Audit failed: %v", err),
987-
}, nil
876+
return ""
988877
}
989-
990-
result := &AuditResult{
991-
Success: true,
992-
PlanID: plan.ID,
993-
Status: autoFixResult.FinalStatus,
994-
RetryCount: autoFixResult.Attempts,
995-
}
996-
result.FixesApplied = autoFixResult.FixesApplied
997-
998-
if autoFixResult.FinalAudit != nil {
999-
result.BuildPassed = autoFixResult.FinalAudit.BuildResult.Passed
1000-
result.TestsPassed = autoFixResult.FinalAudit.TestResult.Passed
1001-
result.SemanticIssues = autoFixResult.FinalAudit.SemanticResult.Issues
1002-
}
1003-
1004-
// Update plan status in database
1005-
var newStatus task.PlanStatus
1006-
if autoFixResult.FinalStatus == "verified" {
1007-
newStatus = task.PlanStatusVerified
1008-
result.Message = "Plan verified successfully. All checks passed."
1009-
result.Hint = "The plan is complete and verified. You can create a PR or start a new plan."
1010-
} else {
1011-
newStatus = task.PlanStatusNeedsRevision
1012-
result.Message = fmt.Sprintf("Plan needs revision after %d fix attempts.", autoFixResult.Attempts)
1013-
result.Hint = "Review the semantic issues and fix them manually, then run audit again."
1014-
}
1015-
result.PlanStatus = newStatus
1016-
1017-
// Store audit report
1018-
auditReport := autoFixResult.ToAuditReportWithFixes()
1019-
reportJSON, marshalErr := json.Marshal(auditReport)
1020-
if marshalErr == nil {
1021-
_ = repo.UpdatePlanAuditReport(plan.ID, newStatus, string(reportJSON))
878+
const maxArchLen = 8000
879+
if len(content) > maxArchLen {
880+
content = append(content[:maxArchLen], []byte("\n...[truncated]")...)
1022881
}
882+
return string(content)
883+
}
1023884

1024-
return result, nil
885+
// Audit runs verification on a completed plan.
886+
func (a *PlanApp) Audit(_ context.Context, _ AuditOptions) (*AuditResult, error) {
887+
return &AuditResult{
888+
Success: false,
889+
Message: "Audit service has been removed. Use your AI tool's built-in verification instead.",
890+
Hint: "Run your project's build and test commands directly to verify plan completion.",
891+
}, nil
1025892
}
1026893

1027894
// parseQuestionsFromMetadata extracts questions from agent metadata,
@@ -1141,6 +1008,13 @@ func (a *PlanApp) parseTasksFromMetadata(ctx context.Context, metadata map[strin
11411008
}
11421009
}
11431010

1011+
// First task gets ARCHITECTURE.md for full architectural context
1012+
if i == 0 {
1013+
if archContent := loadArchitectureMD(); archContent != "" {
1014+
t.ContextSummary = "## Architecture Overview\n" + archContent + "\n\n" + t.ContextSummary
1015+
}
1016+
}
1017+
11441018
tasks = append(tasks, t)
11451019
titleToID[pt.Title] = id
11461020

@@ -1228,6 +1102,13 @@ func (a *PlanApp) parseTasksFromMetadata(ctx context.Context, metadata map[strin
12281102
}
12291103
}
12301104

1105+
// First task gets ARCHITECTURE.md for full architectural context
1106+
if i == 0 {
1107+
if archContent := loadArchitectureMD(); archContent != "" {
1108+
newTask.ContextSummary = "## Architecture Overview\n" + archContent + "\n\n" + newTask.ContextSummary
1109+
}
1110+
}
1111+
12311112
tasks = append(tasks, newTask)
12321113
titleToID[title] = id
12331114

internal/app/task.go

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,10 @@ package app
22

33
import (
44
"context"
5-
"encoding/json"
65
"fmt"
76
"log"
87
"os"
9-
"time"
108

11-
"github.com/josephgoksu/TaskWing/internal/agents/impl"
129
"github.com/josephgoksu/TaskWing/internal/git"
1310
"github.com/josephgoksu/TaskWing/internal/policy"
1411
"github.com/josephgoksu/TaskWing/internal/task"
@@ -513,39 +510,9 @@ func (a *TaskApp) Complete(ctx context.Context, opts TaskCompleteOptions) (*Task
513510
// All tasks complete - run audit first, then create PR if verified
514511
hint += "All tasks in this plan are complete!"
515512

516-
// Trigger automatic audit
517-
auditTriggered = true
518-
hint += " Running audit verification..."
519-
520-
// Create audit service and run with auto-fix
521-
auditService := impl.NewService(workDir, a.ctx.LLMCfg)
522-
auditCtx, auditCancel := context.WithTimeout(ctx, 5*time.Minute)
523-
defer auditCancel()
524-
525-
auditResult, auditErr := auditService.AuditWithAutoFix(auditCtx, plan)
526-
if auditErr != nil {
527-
auditStatus = "error"
528-
hint += fmt.Sprintf(" Audit failed: %v", auditErr)
529-
} else {
530-
auditStatus = auditResult.FinalStatus
531-
if auditResult.FinalStatus == "verified" {
532-
auditPlanStatus = task.PlanStatusVerified
533-
hint += " Plan VERIFIED - all checks passed!"
534-
} else {
535-
auditPlanStatus = task.PlanStatusNeedsRevision
536-
hint += fmt.Sprintf(" Plan needs revision after %d fix attempts.", auditResult.Attempts)
537-
if auditResult.FinalAudit != nil && len(auditResult.FinalAudit.SemanticResult.Issues) > 0 {
538-
hint += fmt.Sprintf(" Issues: %v", auditResult.FinalAudit.SemanticResult.Issues)
539-
}
540-
}
541-
542-
// Store audit report in database
543-
auditReport := auditResult.ToAuditReportWithFixes()
544-
reportJSON, marshalErr := json.Marshal(auditReport)
545-
if marshalErr == nil {
546-
_ = repo.UpdatePlanAuditReport(plan.ID, auditPlanStatus, string(reportJSON))
547-
}
548-
}
513+
// Audit service removed
514+
auditTriggered = false
515+
_ = auditTriggered
549516

550517
// Only create PR if audit passed
551518
if auditStatus == "verified" && gitClient.IsRepository() && gitClient.IsGhInstalled() {

0 commit comments

Comments
 (0)