orch-code 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +624 -0
- package/cmd/apply.go +111 -0
- package/cmd/auth.go +393 -0
- package/cmd/auth_test.go +100 -0
- package/cmd/diff.go +57 -0
- package/cmd/doctor.go +149 -0
- package/cmd/explain.go +192 -0
- package/cmd/explain_test.go +62 -0
- package/cmd/init.go +100 -0
- package/cmd/interactive.go +1372 -0
- package/cmd/interactive_input.go +45 -0
- package/cmd/interactive_input_test.go +55 -0
- package/cmd/logs.go +72 -0
- package/cmd/model.go +84 -0
- package/cmd/plan.go +149 -0
- package/cmd/provider.go +189 -0
- package/cmd/provider_model_doctor_test.go +91 -0
- package/cmd/root.go +67 -0
- package/cmd/run.go +123 -0
- package/cmd/run_engine.go +208 -0
- package/cmd/run_engine_test.go +30 -0
- package/cmd/session.go +589 -0
- package/cmd/session_helpers.go +54 -0
- package/cmd/session_integration_test.go +30 -0
- package/cmd/session_list_current_test.go +87 -0
- package/cmd/session_messages_test.go +163 -0
- package/cmd/session_runs_test.go +68 -0
- package/cmd/sprint1_integration_test.go +119 -0
- package/cmd/stats.go +173 -0
- package/cmd/stats_test.go +71 -0
- package/cmd/version.go +4 -0
- package/go.mod +45 -0
- package/go.sum +108 -0
- package/internal/agents/agent.go +31 -0
- package/internal/agents/coder.go +167 -0
- package/internal/agents/planner.go +155 -0
- package/internal/agents/reviewer.go +118 -0
- package/internal/agents/runtime.go +25 -0
- package/internal/agents/runtime_test.go +77 -0
- package/internal/auth/account.go +78 -0
- package/internal/auth/oauth.go +523 -0
- package/internal/auth/store.go +287 -0
- package/internal/confidence/policy.go +174 -0
- package/internal/confidence/policy_test.go +71 -0
- package/internal/confidence/scorer.go +253 -0
- package/internal/confidence/scorer_test.go +83 -0
- package/internal/config/config.go +331 -0
- package/internal/config/config_defaults_test.go +138 -0
- package/internal/execution/contract_builder.go +160 -0
- package/internal/execution/contract_builder_test.go +68 -0
- package/internal/execution/plan_compliance.go +161 -0
- package/internal/execution/plan_compliance_test.go +71 -0
- package/internal/execution/retry_directive.go +132 -0
- package/internal/execution/scope_guard.go +69 -0
- package/internal/logger/logger.go +120 -0
- package/internal/models/contracts_test.go +100 -0
- package/internal/models/models.go +269 -0
- package/internal/orchestrator/orchestrator.go +701 -0
- package/internal/orchestrator/orchestrator_retry_test.go +135 -0
- package/internal/orchestrator/review_engine_test.go +50 -0
- package/internal/orchestrator/state.go +42 -0
- package/internal/orchestrator/test_classifier_test.go +68 -0
- package/internal/patch/applier.go +131 -0
- package/internal/patch/applier_test.go +25 -0
- package/internal/patch/parser.go +89 -0
- package/internal/patch/patch.go +60 -0
- package/internal/patch/summary.go +30 -0
- package/internal/patch/validator.go +104 -0
- package/internal/planning/normalizer.go +416 -0
- package/internal/planning/normalizer_test.go +64 -0
- package/internal/providers/errors.go +35 -0
- package/internal/providers/openai/client.go +498 -0
- package/internal/providers/openai/client_test.go +187 -0
- package/internal/providers/provider.go +47 -0
- package/internal/providers/registry.go +32 -0
- package/internal/providers/registry_test.go +57 -0
- package/internal/providers/router.go +52 -0
- package/internal/providers/state.go +114 -0
- package/internal/providers/state_test.go +64 -0
- package/internal/repo/analyzer.go +188 -0
- package/internal/repo/context.go +83 -0
- package/internal/review/engine.go +267 -0
- package/internal/review/engine_test.go +103 -0
- package/internal/runstore/store.go +137 -0
- package/internal/runstore/store_test.go +59 -0
- package/internal/runtime/lock.go +150 -0
- package/internal/runtime/lock_test.go +57 -0
- package/internal/session/compaction.go +260 -0
- package/internal/session/compaction_test.go +36 -0
- package/internal/session/service.go +117 -0
- package/internal/session/service_test.go +113 -0
- package/internal/storage/storage.go +1498 -0
- package/internal/storage/storage_test.go +413 -0
- package/internal/testing/classifier.go +80 -0
- package/internal/testing/classifier_test.go +36 -0
- package/internal/tools/command.go +160 -0
- package/internal/tools/command_test.go +56 -0
- package/internal/tools/file.go +111 -0
- package/internal/tools/git.go +77 -0
- package/internal/tools/invalid_params_test.go +36 -0
- package/internal/tools/policy.go +98 -0
- package/internal/tools/policy_test.go +36 -0
- package/internal/tools/registry_test.go +52 -0
- package/internal/tools/result.go +30 -0
- package/internal/tools/search.go +86 -0
- package/internal/tools/tool.go +94 -0
- package/main.go +9 -0
- package/npm/orch.js +25 -0
- package/package.json +41 -0
- package/scripts/changelog.js +20 -0
- package/scripts/check-release-version.js +21 -0
- package/scripts/lib/release-utils.js +223 -0
- package/scripts/postinstall.js +157 -0
- package/scripts/release.js +52 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
package execution
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"strings"
|
|
5
|
+
|
|
6
|
+
"github.com/furkanbeydemir/orch/internal/models"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
type RetryDirectiveBuilder struct{}
|
|
10
|
+
|
|
11
|
+
func NewRetryDirectiveBuilder() *RetryDirectiveBuilder {
|
|
12
|
+
return &RetryDirectiveBuilder{}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
func (b *RetryDirectiveBuilder) FromValidation(state *models.RunState, attempt int) *models.RetryDirective {
|
|
16
|
+
if state == nil {
|
|
17
|
+
return nil
|
|
18
|
+
}
|
|
19
|
+
directive := &models.RetryDirective{
|
|
20
|
+
Stage: "validation",
|
|
21
|
+
Attempt: attempt,
|
|
22
|
+
Reasons: []string{},
|
|
23
|
+
FailedGates: []string{},
|
|
24
|
+
Instructions: []string{},
|
|
25
|
+
Avoid: []string{
|
|
26
|
+
"Do not retry with the same scope violation or forbidden change.",
|
|
27
|
+
"Do not expand scope unless the change is explicitly justified.",
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
for _, result := range state.ValidationResults {
|
|
31
|
+
if result.Status != models.ValidationFail {
|
|
32
|
+
continue
|
|
33
|
+
}
|
|
34
|
+
directive.FailedGates = append(directive.FailedGates, result.Name)
|
|
35
|
+
directive.Reasons = append(directive.Reasons, result.Summary)
|
|
36
|
+
directive.Instructions = append(directive.Instructions, result.ActionableItems...)
|
|
37
|
+
}
|
|
38
|
+
if len(directive.Instructions) == 0 {
|
|
39
|
+
directive.Instructions = append(directive.Instructions, "Regenerate the patch to satisfy all failed validation gates.")
|
|
40
|
+
}
|
|
41
|
+
return normalizeDirective(directive)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func (b *RetryDirectiveBuilder) FromTest(state *models.RunState, attempt int) *models.RetryDirective {
|
|
45
|
+
if state == nil {
|
|
46
|
+
return nil
|
|
47
|
+
}
|
|
48
|
+
reasons := []string{"The previous patch failed test execution."}
|
|
49
|
+
failedTests := []string{}
|
|
50
|
+
instructions := []string{"Fix the failing test behavior while keeping the patch inside the approved scope."}
|
|
51
|
+
for _, failure := range state.TestFailures {
|
|
52
|
+
failedTests = append(failedTests, failure.Code)
|
|
53
|
+
reasons = append(reasons, failure.Summary)
|
|
54
|
+
switch failure.Code {
|
|
55
|
+
case "test_timeout":
|
|
56
|
+
instructions = append(instructions, "Reduce the cause of timeout or make the code path deterministic enough to complete within the test budget.")
|
|
57
|
+
case "missing_required_tests":
|
|
58
|
+
instructions = append(instructions, "Add or restore the required tests instead of bypassing verification.")
|
|
59
|
+
case "test_assertion_failure":
|
|
60
|
+
instructions = append(instructions, "Fix the functional behavior that caused the assertion failure.")
|
|
61
|
+
case "flaky_test_suspected":
|
|
62
|
+
instructions = append(instructions, "Stabilize the code path instead of weakening the test.")
|
|
63
|
+
default:
|
|
64
|
+
instructions = append(instructions, "Resolve the setup or runtime issue causing the test command failure.")
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if len(failedTests) == 0 {
|
|
68
|
+
failedTests = splitNonEmptyLines(state.TestResults)
|
|
69
|
+
}
|
|
70
|
+
directive := &models.RetryDirective{
|
|
71
|
+
Stage: "test",
|
|
72
|
+
Attempt: attempt,
|
|
73
|
+
Reasons: reasons,
|
|
74
|
+
FailedTests: failedTests,
|
|
75
|
+
Instructions: instructions,
|
|
76
|
+
Avoid: []string{
|
|
77
|
+
"Do not remove or weaken tests to make them pass.",
|
|
78
|
+
"Do not introduce unrelated changes while fixing test failures.",
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
for _, result := range state.ValidationResults {
|
|
82
|
+
if result.Status == models.ValidationFail {
|
|
83
|
+
directive.FailedGates = append(directive.FailedGates, result.Name)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return normalizeDirective(directive)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
func (b *RetryDirectiveBuilder) FromReview(state *models.RunState, attempt int) *models.RetryDirective {
|
|
90
|
+
if state == nil || state.Review == nil {
|
|
91
|
+
return nil
|
|
92
|
+
}
|
|
93
|
+
directive := &models.RetryDirective{
|
|
94
|
+
Stage: "review",
|
|
95
|
+
Attempt: attempt,
|
|
96
|
+
Reasons: append([]string{}, state.Review.Comments...),
|
|
97
|
+
Instructions: append([]string{}, state.Review.Suggestions...),
|
|
98
|
+
Avoid: []string{
|
|
99
|
+
"Do not ignore reviewer findings.",
|
|
100
|
+
"Do not broaden the patch beyond what is needed to resolve review findings.",
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
if len(directive.Instructions) == 0 {
|
|
104
|
+
directive.Instructions = append(directive.Instructions, "Address the review findings and preserve all prior passing validation and test conditions.")
|
|
105
|
+
}
|
|
106
|
+
return normalizeDirective(directive)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
func normalizeDirective(d *models.RetryDirective) *models.RetryDirective {
|
|
110
|
+
if d == nil {
|
|
111
|
+
return nil
|
|
112
|
+
}
|
|
113
|
+
d.Reasons = uniqueNonEmpty(d.Reasons)
|
|
114
|
+
d.FailedGates = uniqueNonEmpty(d.FailedGates)
|
|
115
|
+
d.FailedTests = uniqueNonEmpty(d.FailedTests)
|
|
116
|
+
d.Instructions = uniqueNonEmpty(d.Instructions)
|
|
117
|
+
d.Avoid = uniqueNonEmpty(d.Avoid)
|
|
118
|
+
return d
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
func splitNonEmptyLines(text string) []string {
|
|
122
|
+
parts := strings.Split(strings.TrimSpace(text), "\n")
|
|
123
|
+
result := make([]string, 0, len(parts))
|
|
124
|
+
for _, part := range parts {
|
|
125
|
+
trimmed := strings.TrimSpace(part)
|
|
126
|
+
if trimmed == "" {
|
|
127
|
+
continue
|
|
128
|
+
}
|
|
129
|
+
result = append(result, trimmed)
|
|
130
|
+
}
|
|
131
|
+
return result
|
|
132
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
package execution
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"strings"
|
|
6
|
+
|
|
7
|
+
"github.com/furkanbeydemir/orch/internal/models"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
type ScopeGuard struct{}
|
|
11
|
+
|
|
12
|
+
func NewScopeGuard() *ScopeGuard {
|
|
13
|
+
return &ScopeGuard{}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
func (g *ScopeGuard) Validate(contract *models.ExecutionContract, patch *models.Patch) models.ValidationResult {
|
|
17
|
+
result := models.ValidationResult{
|
|
18
|
+
Name: "scope_compliance",
|
|
19
|
+
Stage: "validation",
|
|
20
|
+
Status: models.ValidationPass,
|
|
21
|
+
Severity: models.SeverityLow,
|
|
22
|
+
Summary: "patch stayed inside allowed scope",
|
|
23
|
+
Details: []string{},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if contract == nil {
|
|
27
|
+
result.Status = models.ValidationWarn
|
|
28
|
+
result.Severity = models.SeverityMedium
|
|
29
|
+
result.Summary = "execution contract missing; scope compliance could not be fully validated"
|
|
30
|
+
return result
|
|
31
|
+
}
|
|
32
|
+
if patch == nil {
|
|
33
|
+
result.Status = models.ValidationFail
|
|
34
|
+
result.Severity = models.SeverityHigh
|
|
35
|
+
result.Summary = "patch missing for scope validation"
|
|
36
|
+
return result
|
|
37
|
+
}
|
|
38
|
+
if len(contract.AllowedFiles) == 0 || len(patch.Files) == 0 {
|
|
39
|
+
return result
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
allowed := map[string]struct{}{}
|
|
43
|
+
for _, file := range contract.AllowedFiles {
|
|
44
|
+
allowed[strings.TrimSpace(file)] = struct{}{}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
violations := make([]string, 0)
|
|
48
|
+
for _, file := range patch.Files {
|
|
49
|
+
path := strings.TrimSpace(file.Path)
|
|
50
|
+
if path == "" {
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
if _, ok := allowed[path]; ok {
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
56
|
+
violations = append(violations, path)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if len(violations) == 0 {
|
|
60
|
+
return result
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
result.Status = models.ValidationFail
|
|
64
|
+
result.Severity = models.SeverityHigh
|
|
65
|
+
result.Summary = fmt.Sprintf("patch changed files outside allowed scope: %s", strings.Join(violations, ", "))
|
|
66
|
+
result.Details = violations
|
|
67
|
+
result.ActionableItems = []string{"Regenerate the patch using only allowed files or expand scope explicitly with justification."}
|
|
68
|
+
return result
|
|
69
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
package logger
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"fmt"
|
|
6
|
+
"os"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"sync"
|
|
9
|
+
"time"
|
|
10
|
+
|
|
11
|
+
"github.com/furkanbeydemir/orch/internal/models"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
type Logger struct {
|
|
15
|
+
runID string
|
|
16
|
+
repoRoot string
|
|
17
|
+
entries []models.LogEntry
|
|
18
|
+
mu sync.Mutex
|
|
19
|
+
verbose bool
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func New(runID, repoRoot string, verbose bool) *Logger {
|
|
23
|
+
return &Logger{
|
|
24
|
+
runID: runID,
|
|
25
|
+
repoRoot: repoRoot,
|
|
26
|
+
entries: make([]models.LogEntry, 0),
|
|
27
|
+
verbose: verbose,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
func (l *Logger) Log(actor, step, message string) {
|
|
32
|
+
entry := models.LogEntry{
|
|
33
|
+
Timestamp: time.Now(),
|
|
34
|
+
Actor: actor,
|
|
35
|
+
Step: step,
|
|
36
|
+
Message: message,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
l.mu.Lock()
|
|
40
|
+
l.entries = append(l.entries, entry)
|
|
41
|
+
l.mu.Unlock()
|
|
42
|
+
|
|
43
|
+
if l.verbose {
|
|
44
|
+
fmt.Printf("[%s] %s\n", actor, message)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
func (l *Logger) Verbose(actor, step, message string) {
|
|
49
|
+
if l.verbose {
|
|
50
|
+
l.Log(actor, step, message)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
func (l *Logger) Entries() []models.LogEntry {
|
|
55
|
+
l.mu.Lock()
|
|
56
|
+
defer l.mu.Unlock()
|
|
57
|
+
|
|
58
|
+
result := make([]models.LogEntry, len(l.entries))
|
|
59
|
+
copy(result, l.entries)
|
|
60
|
+
return result
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
func (l *Logger) Save() error {
|
|
64
|
+
l.mu.Lock()
|
|
65
|
+
entries := make([]models.LogEntry, len(l.entries))
|
|
66
|
+
copy(entries, l.entries)
|
|
67
|
+
l.mu.Unlock()
|
|
68
|
+
|
|
69
|
+
runsDir := filepath.Join(l.repoRoot, ".orch", "runs")
|
|
70
|
+
if err := os.MkdirAll(runsDir, 0o755); err != nil {
|
|
71
|
+
return fmt.Errorf("failed to create runs directory: %w", err)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
data, err := json.MarshalIndent(entries, "", " ")
|
|
75
|
+
if err != nil {
|
|
76
|
+
return fmt.Errorf("failed to serialize logs: %w", err)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
logPath := filepath.Join(runsDir, l.runID+".json")
|
|
80
|
+
if err := os.WriteFile(logPath, data, 0o644); err != nil {
|
|
81
|
+
return fmt.Errorf("failed to write log file: %w", err)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return nil
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
func LoadRunLog(repoRoot, runID string) ([]models.LogEntry, error) {
|
|
88
|
+
logPath := filepath.Join(repoRoot, ".orch", "runs", runID+".json")
|
|
89
|
+
|
|
90
|
+
data, err := os.ReadFile(logPath)
|
|
91
|
+
if err != nil {
|
|
92
|
+
return nil, fmt.Errorf("failed to read log file: %w", err)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
var entries []models.LogEntry
|
|
96
|
+
if err := json.Unmarshal(data, &entries); err != nil {
|
|
97
|
+
return nil, fmt.Errorf("failed to parse log file: %w", err)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return entries, nil
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
func ListRuns(repoRoot string) ([]string, error) {
|
|
104
|
+
runsDir := filepath.Join(repoRoot, ".orch", "runs")
|
|
105
|
+
|
|
106
|
+
entries, err := os.ReadDir(runsDir)
|
|
107
|
+
if err != nil {
|
|
108
|
+
return nil, fmt.Errorf("failed to read runs directory: %w", err)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
var runs []string
|
|
112
|
+
for _, entry := range entries {
|
|
113
|
+
if !entry.IsDir() && filepath.Ext(entry.Name()) == ".json" {
|
|
114
|
+
name := entry.Name()
|
|
115
|
+
runs = append(runs, name[:len(name)-len(".json")])
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return runs, nil
|
|
120
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
package models
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"testing"
|
|
6
|
+
"time"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
func TestRunStateStructuredArtifactsJSONRoundtrip(t *testing.T) {
|
|
10
|
+
now := time.Now().UTC()
|
|
11
|
+
state := RunState{
|
|
12
|
+
ID: "run-1",
|
|
13
|
+
Task: Task{ID: "task-1", Description: "fix auth bug", CreatedAt: now},
|
|
14
|
+
TaskBrief: &TaskBrief{
|
|
15
|
+
TaskID: "task-1",
|
|
16
|
+
UserRequest: "fix auth bug",
|
|
17
|
+
NormalizedGoal: "Fix auth bug while preserving existing behavior.",
|
|
18
|
+
TaskType: TaskTypeBugfix,
|
|
19
|
+
RiskLevel: RiskHigh,
|
|
20
|
+
},
|
|
21
|
+
Plan: &Plan{
|
|
22
|
+
TaskID: "task-1",
|
|
23
|
+
Summary: "Fix auth bug while preserving existing behavior.",
|
|
24
|
+
TaskType: TaskTypeBugfix,
|
|
25
|
+
RiskLevel: RiskHigh,
|
|
26
|
+
AcceptanceCriteria: []AcceptanceCriterion{{ID: "ac-1", Description: "Bug path no longer fails."}},
|
|
27
|
+
},
|
|
28
|
+
ExecutionContract: &ExecutionContract{
|
|
29
|
+
TaskID: "task-1",
|
|
30
|
+
AllowedFiles: []string{"internal/auth/service.go"},
|
|
31
|
+
PatchBudget: PatchBudget{MaxFiles: 2, MaxChangedLines: 80},
|
|
32
|
+
},
|
|
33
|
+
ValidationResults: []ValidationResult{{
|
|
34
|
+
Name: "scope_compliance",
|
|
35
|
+
Stage: "validation",
|
|
36
|
+
Status: ValidationPass,
|
|
37
|
+
Severity: SeverityLow,
|
|
38
|
+
Summary: "patch stayed inside allowed scope",
|
|
39
|
+
}},
|
|
40
|
+
RetryDirective: &RetryDirective{
|
|
41
|
+
Stage: "validation",
|
|
42
|
+
Attempt: 1,
|
|
43
|
+
FailedGates: []string{"scope_compliance"},
|
|
44
|
+
Instructions: []string{"Keep the patch inside allowed scope."},
|
|
45
|
+
},
|
|
46
|
+
ReviewScorecard: &ReviewScorecard{
|
|
47
|
+
RequirementCoverage: 9,
|
|
48
|
+
ScopeControl: 10,
|
|
49
|
+
RegressionRisk: 8,
|
|
50
|
+
Readability: 8,
|
|
51
|
+
Maintainability: 8,
|
|
52
|
+
TestAdequacy: 8,
|
|
53
|
+
Decision: ReviewAccept,
|
|
54
|
+
},
|
|
55
|
+
Confidence: &ConfidenceReport{
|
|
56
|
+
Score: 0.88,
|
|
57
|
+
Band: "high",
|
|
58
|
+
Reasons: []string{"all mandatory signals aligned"},
|
|
59
|
+
},
|
|
60
|
+
TestFailures: []TestFailure{{
|
|
61
|
+
Code: "test_assertion_failure",
|
|
62
|
+
Summary: "expected 200 got 500",
|
|
63
|
+
Details: []string{"expected 200 got 500"},
|
|
64
|
+
}},
|
|
65
|
+
Status: StatusPlanning,
|
|
66
|
+
StartedAt: now,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
encoded, err := json.Marshal(state)
|
|
70
|
+
if err != nil {
|
|
71
|
+
t.Fatalf("marshal: %v", err)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
var decoded RunState
|
|
75
|
+
if err := json.Unmarshal(encoded, &decoded); err != nil {
|
|
76
|
+
t.Fatalf("unmarshal: %v", err)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if decoded.TaskBrief == nil || decoded.TaskBrief.TaskType != TaskTypeBugfix {
|
|
80
|
+
t.Fatalf("task brief did not roundtrip")
|
|
81
|
+
}
|
|
82
|
+
if decoded.ExecutionContract == nil || len(decoded.ExecutionContract.AllowedFiles) != 1 {
|
|
83
|
+
t.Fatalf("execution contract did not roundtrip")
|
|
84
|
+
}
|
|
85
|
+
if len(decoded.ValidationResults) != 1 || decoded.ValidationResults[0].Name != "scope_compliance" {
|
|
86
|
+
t.Fatalf("validation results did not roundtrip")
|
|
87
|
+
}
|
|
88
|
+
if decoded.RetryDirective == nil || decoded.RetryDirective.Stage != "validation" {
|
|
89
|
+
t.Fatalf("retry directive did not roundtrip")
|
|
90
|
+
}
|
|
91
|
+
if decoded.ReviewScorecard == nil || decoded.ReviewScorecard.Decision != ReviewAccept {
|
|
92
|
+
t.Fatalf("review scorecard did not roundtrip")
|
|
93
|
+
}
|
|
94
|
+
if decoded.Confidence == nil || decoded.Confidence.Band != "high" {
|
|
95
|
+
t.Fatalf("confidence report did not roundtrip")
|
|
96
|
+
}
|
|
97
|
+
if len(decoded.TestFailures) != 1 || decoded.TestFailures[0].Code != "test_assertion_failure" {
|
|
98
|
+
t.Fatalf("test failures did not roundtrip")
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
package models
|
|
2
|
+
|
|
3
|
+
import "time"
|
|
4
|
+
|
|
5
|
+
// State machine: Created → Analyzing → Planning → Coding → Validating → Testing → Reviewing → Completed/Failed
|
|
6
|
+
type RunStatus string
|
|
7
|
+
|
|
8
|
+
const (
|
|
9
|
+
StatusCreated RunStatus = "created"
|
|
10
|
+
StatusAnalyzing RunStatus = "analyzing"
|
|
11
|
+
StatusPlanning RunStatus = "planning"
|
|
12
|
+
StatusCoding RunStatus = "coding"
|
|
13
|
+
StatusValidating RunStatus = "validating"
|
|
14
|
+
StatusTesting RunStatus = "testing"
|
|
15
|
+
StatusReviewing RunStatus = "reviewing"
|
|
16
|
+
StatusCompleted RunStatus = "completed"
|
|
17
|
+
StatusFailed RunStatus = "failed"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
type ReviewDecision string
|
|
21
|
+
|
|
22
|
+
const (
|
|
23
|
+
ReviewAccept ReviewDecision = "accept"
|
|
24
|
+
ReviewRevise ReviewDecision = "revise"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
type TaskType string
|
|
28
|
+
|
|
29
|
+
const (
|
|
30
|
+
TaskTypeUnknown TaskType = "unknown"
|
|
31
|
+
TaskTypeFeature TaskType = "feature"
|
|
32
|
+
TaskTypeBugfix TaskType = "bugfix"
|
|
33
|
+
TaskTypeTest TaskType = "test"
|
|
34
|
+
TaskTypeRefactor TaskType = "refactor"
|
|
35
|
+
TaskTypeDocs TaskType = "docs"
|
|
36
|
+
TaskTypeChore TaskType = "chore"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
type RiskLevel string
|
|
40
|
+
|
|
41
|
+
const (
|
|
42
|
+
RiskLow RiskLevel = "low"
|
|
43
|
+
RiskMedium RiskLevel = "medium"
|
|
44
|
+
RiskHigh RiskLevel = "high"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
type ValidationStatus string
|
|
48
|
+
|
|
49
|
+
const (
|
|
50
|
+
ValidationPass ValidationStatus = "pass"
|
|
51
|
+
ValidationWarn ValidationStatus = "warn"
|
|
52
|
+
ValidationFail ValidationStatus = "fail"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
type ValidationSeverity string
|
|
56
|
+
|
|
57
|
+
const (
|
|
58
|
+
SeverityLow ValidationSeverity = "low"
|
|
59
|
+
SeverityMedium ValidationSeverity = "medium"
|
|
60
|
+
SeverityHigh ValidationSeverity = "high"
|
|
61
|
+
SeverityCritical ValidationSeverity = "critical"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
type Task struct {
|
|
65
|
+
ID string `json:"id"`
|
|
66
|
+
Description string `json:"description"`
|
|
67
|
+
CreatedAt time.Time `json:"created_at"`
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type TaskBrief struct {
|
|
71
|
+
TaskID string `json:"task_id"`
|
|
72
|
+
UserRequest string `json:"user_request"`
|
|
73
|
+
NormalizedGoal string `json:"normalized_goal"`
|
|
74
|
+
TaskType TaskType `json:"task_type"`
|
|
75
|
+
RiskLevel RiskLevel `json:"risk_level"`
|
|
76
|
+
Constraints []string `json:"constraints,omitempty"`
|
|
77
|
+
Assumptions []string `json:"assumptions,omitempty"`
|
|
78
|
+
SuccessDefinition []string `json:"success_definition,omitempty"`
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
type AcceptanceCriterion struct {
|
|
82
|
+
ID string `json:"id"`
|
|
83
|
+
Description string `json:"description"`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
type Plan struct {
|
|
87
|
+
TaskID string `json:"task_id"`
|
|
88
|
+
Summary string `json:"summary,omitempty"`
|
|
89
|
+
TaskType TaskType `json:"task_type,omitempty"`
|
|
90
|
+
RiskLevel RiskLevel `json:"risk_level,omitempty"`
|
|
91
|
+
Steps []PlanStep `json:"steps"`
|
|
92
|
+
FilesToModify []string `json:"files_to_modify"`
|
|
93
|
+
FilesToInspect []string `json:"files_to_inspect"`
|
|
94
|
+
Risks []string `json:"risks"`
|
|
95
|
+
TestStrategy string `json:"test_strategy"`
|
|
96
|
+
TestRequirements []string `json:"test_requirements,omitempty"`
|
|
97
|
+
AcceptanceCriteria []AcceptanceCriterion `json:"acceptance_criteria,omitempty"`
|
|
98
|
+
Invariants []string `json:"invariants,omitempty"`
|
|
99
|
+
ForbiddenChanges []string `json:"forbidden_changes,omitempty"`
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
type PlanStep struct {
|
|
103
|
+
Order int `json:"order"`
|
|
104
|
+
Description string `json:"description"`
|
|
105
|
+
TargetFile string `json:"target_file,omitempty"`
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
type Patch struct {
|
|
109
|
+
TaskID string `json:"task_id"`
|
|
110
|
+
Files []PatchFile `json:"files"`
|
|
111
|
+
// RawDiff contains raw unified diff text.
|
|
112
|
+
RawDiff string `json:"raw_diff"`
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
type PatchFile struct {
|
|
116
|
+
Path string `json:"path"`
|
|
117
|
+
Status string `json:"status"`
|
|
118
|
+
Diff string `json:"diff"`
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
type RepoMap struct {
|
|
122
|
+
RootPath string `json:"root_path"`
|
|
123
|
+
// Language is the detected primary language.
|
|
124
|
+
Language string `json:"language"`
|
|
125
|
+
PackageManager string `json:"package_manager"`
|
|
126
|
+
TestFramework string `json:"test_framework"`
|
|
127
|
+
// Files is the repository file inventory.
|
|
128
|
+
Files []FileInfo `json:"files"`
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
type FileInfo struct {
|
|
132
|
+
Path string `json:"path"`
|
|
133
|
+
Language string `json:"language"`
|
|
134
|
+
Size int64 `json:"size"`
|
|
135
|
+
Imports []string `json:"imports,omitempty"`
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
type PatchBudget struct {
|
|
139
|
+
MaxFiles int `json:"max_files"`
|
|
140
|
+
MaxChangedLines int `json:"max_changed_lines"`
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
type ScopeExpansionPolicy struct {
|
|
144
|
+
Allowed bool `json:"allowed"`
|
|
145
|
+
RequiresReason bool `json:"requires_reason"`
|
|
146
|
+
MaxExtraFiles int `json:"max_extra_files"`
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
type ExecutionContract struct {
|
|
150
|
+
TaskID string `json:"task_id"`
|
|
151
|
+
PlanID string `json:"plan_id,omitempty"`
|
|
152
|
+
AllowedFiles []string `json:"allowed_files,omitempty"`
|
|
153
|
+
InspectFiles []string `json:"inspect_files,omitempty"`
|
|
154
|
+
RequiredEdits []string `json:"required_edits,omitempty"`
|
|
155
|
+
ProhibitedActions []string `json:"prohibited_actions,omitempty"`
|
|
156
|
+
AcceptanceCriteria []string `json:"acceptance_criteria,omitempty"`
|
|
157
|
+
Invariants []string `json:"invariants,omitempty"`
|
|
158
|
+
PatchBudget PatchBudget `json:"patch_budget"`
|
|
159
|
+
ScopeExpansionPolicy ScopeExpansionPolicy `json:"scope_expansion_policy"`
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
type ValidationResult struct {
|
|
163
|
+
Name string `json:"name"`
|
|
164
|
+
Stage string `json:"stage"`
|
|
165
|
+
Status ValidationStatus `json:"status"`
|
|
166
|
+
Severity ValidationSeverity `json:"severity"`
|
|
167
|
+
Summary string `json:"summary"`
|
|
168
|
+
Details []string `json:"details,omitempty"`
|
|
169
|
+
ActionableItems []string `json:"actionable_items,omitempty"`
|
|
170
|
+
Metadata map[string]string `json:"metadata,omitempty"`
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
type RetryDirective struct {
|
|
174
|
+
Stage string `json:"stage"`
|
|
175
|
+
Attempt int `json:"attempt"`
|
|
176
|
+
Reasons []string `json:"reasons,omitempty"`
|
|
177
|
+
FailedGates []string `json:"failed_gates,omitempty"`
|
|
178
|
+
FailedTests []string `json:"failed_tests,omitempty"`
|
|
179
|
+
Instructions []string `json:"instructions,omitempty"`
|
|
180
|
+
Avoid []string `json:"avoid,omitempty"`
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
type ReviewScorecard struct {
|
|
184
|
+
RequirementCoverage int `json:"requirement_coverage"`
|
|
185
|
+
ScopeControl int `json:"scope_control"`
|
|
186
|
+
RegressionRisk int `json:"regression_risk"`
|
|
187
|
+
Readability int `json:"readability"`
|
|
188
|
+
Maintainability int `json:"maintainability"`
|
|
189
|
+
TestAdequacy int `json:"test_adequacy"`
|
|
190
|
+
Decision ReviewDecision `json:"decision"`
|
|
191
|
+
Findings []string `json:"findings,omitempty"`
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
type ConfidenceReport struct {
|
|
195
|
+
Score float64 `json:"score"`
|
|
196
|
+
Band string `json:"band"`
|
|
197
|
+
Reasons []string `json:"reasons,omitempty"`
|
|
198
|
+
Warnings []string `json:"warnings,omitempty"`
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
type TestFailure struct {
|
|
202
|
+
Code string `json:"code"`
|
|
203
|
+
Summary string `json:"summary"`
|
|
204
|
+
Details []string `json:"details,omitempty"`
|
|
205
|
+
Flaky bool `json:"flaky,omitempty"`
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
type RunState struct {
|
|
209
|
+
ID string `json:"id"`
|
|
210
|
+
ProjectID string `json:"project_id,omitempty"`
|
|
211
|
+
SessionID string `json:"session_id,omitempty"`
|
|
212
|
+
Task Task `json:"task"`
|
|
213
|
+
TaskBrief *TaskBrief `json:"task_brief,omitempty"`
|
|
214
|
+
Status RunStatus `json:"status"`
|
|
215
|
+
Plan *Plan `json:"plan,omitempty"`
|
|
216
|
+
ExecutionContract *ExecutionContract `json:"execution_contract,omitempty"`
|
|
217
|
+
Patch *Patch `json:"patch,omitempty"`
|
|
218
|
+
Context *ContextResult `json:"context,omitempty"`
|
|
219
|
+
ValidationResults []ValidationResult `json:"validation_results,omitempty"`
|
|
220
|
+
RetryDirective *RetryDirective `json:"retry_directive,omitempty"`
|
|
221
|
+
ReviewScorecard *ReviewScorecard `json:"review_scorecard,omitempty"`
|
|
222
|
+
Confidence *ConfidenceReport `json:"confidence,omitempty"`
|
|
223
|
+
TestFailures []TestFailure `json:"test_failures,omitempty"`
|
|
224
|
+
// Review contains review output when available.
|
|
225
|
+
Review *ReviewResult `json:"review,omitempty"`
|
|
226
|
+
// TestResults stores summarized test execution output.
|
|
227
|
+
TestResults string `json:"test_results,omitempty"`
|
|
228
|
+
Retries RetryState `json:"retries"`
|
|
229
|
+
UnresolvedFailures []string `json:"unresolved_failures,omitempty"`
|
|
230
|
+
BestPatchSummary string `json:"best_patch_summary,omitempty"`
|
|
231
|
+
Logs []LogEntry `json:"logs"`
|
|
232
|
+
StartedAt time.Time `json:"started_at"`
|
|
233
|
+
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
|
234
|
+
Error string `json:"error,omitempty"`
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
type RetryState struct {
|
|
238
|
+
Validation int `json:"validation"`
|
|
239
|
+
Testing int `json:"testing"`
|
|
240
|
+
Review int `json:"review"`
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
type ReviewResult struct {
|
|
244
|
+
Decision ReviewDecision `json:"decision"`
|
|
245
|
+
Comments []string `json:"comments"`
|
|
246
|
+
Suggestions []string `json:"suggestions,omitempty"`
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
type LogEntry struct {
|
|
250
|
+
Timestamp time.Time `json:"timestamp"`
|
|
251
|
+
Actor string `json:"actor"`
|
|
252
|
+
Step string `json:"step"`
|
|
253
|
+
Message string `json:"message"`
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
type ContextResult struct {
|
|
257
|
+
SelectedFiles []string `json:"selected_files"`
|
|
258
|
+
RelatedTests []string `json:"related_tests"`
|
|
259
|
+
RelevantConfigs []string `json:"relevant_configs"`
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
type ToolResult struct {
|
|
263
|
+
ToolName string `json:"tool_name"`
|
|
264
|
+
Success bool `json:"success"`
|
|
265
|
+
Output string `json:"output"`
|
|
266
|
+
Error string `json:"error,omitempty"`
|
|
267
|
+
ErrorCode string `json:"error_code,omitempty"`
|
|
268
|
+
Metadata map[string]string `json:"metadata,omitempty"`
|
|
269
|
+
}
|