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.
Files changed (116) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +624 -0
  4. package/cmd/apply.go +111 -0
  5. package/cmd/auth.go +393 -0
  6. package/cmd/auth_test.go +100 -0
  7. package/cmd/diff.go +57 -0
  8. package/cmd/doctor.go +149 -0
  9. package/cmd/explain.go +192 -0
  10. package/cmd/explain_test.go +62 -0
  11. package/cmd/init.go +100 -0
  12. package/cmd/interactive.go +1372 -0
  13. package/cmd/interactive_input.go +45 -0
  14. package/cmd/interactive_input_test.go +55 -0
  15. package/cmd/logs.go +72 -0
  16. package/cmd/model.go +84 -0
  17. package/cmd/plan.go +149 -0
  18. package/cmd/provider.go +189 -0
  19. package/cmd/provider_model_doctor_test.go +91 -0
  20. package/cmd/root.go +67 -0
  21. package/cmd/run.go +123 -0
  22. package/cmd/run_engine.go +208 -0
  23. package/cmd/run_engine_test.go +30 -0
  24. package/cmd/session.go +589 -0
  25. package/cmd/session_helpers.go +54 -0
  26. package/cmd/session_integration_test.go +30 -0
  27. package/cmd/session_list_current_test.go +87 -0
  28. package/cmd/session_messages_test.go +163 -0
  29. package/cmd/session_runs_test.go +68 -0
  30. package/cmd/sprint1_integration_test.go +119 -0
  31. package/cmd/stats.go +173 -0
  32. package/cmd/stats_test.go +71 -0
  33. package/cmd/version.go +4 -0
  34. package/go.mod +45 -0
  35. package/go.sum +108 -0
  36. package/internal/agents/agent.go +31 -0
  37. package/internal/agents/coder.go +167 -0
  38. package/internal/agents/planner.go +155 -0
  39. package/internal/agents/reviewer.go +118 -0
  40. package/internal/agents/runtime.go +25 -0
  41. package/internal/agents/runtime_test.go +77 -0
  42. package/internal/auth/account.go +78 -0
  43. package/internal/auth/oauth.go +523 -0
  44. package/internal/auth/store.go +287 -0
  45. package/internal/confidence/policy.go +174 -0
  46. package/internal/confidence/policy_test.go +71 -0
  47. package/internal/confidence/scorer.go +253 -0
  48. package/internal/confidence/scorer_test.go +83 -0
  49. package/internal/config/config.go +331 -0
  50. package/internal/config/config_defaults_test.go +138 -0
  51. package/internal/execution/contract_builder.go +160 -0
  52. package/internal/execution/contract_builder_test.go +68 -0
  53. package/internal/execution/plan_compliance.go +161 -0
  54. package/internal/execution/plan_compliance_test.go +71 -0
  55. package/internal/execution/retry_directive.go +132 -0
  56. package/internal/execution/scope_guard.go +69 -0
  57. package/internal/logger/logger.go +120 -0
  58. package/internal/models/contracts_test.go +100 -0
  59. package/internal/models/models.go +269 -0
  60. package/internal/orchestrator/orchestrator.go +701 -0
  61. package/internal/orchestrator/orchestrator_retry_test.go +135 -0
  62. package/internal/orchestrator/review_engine_test.go +50 -0
  63. package/internal/orchestrator/state.go +42 -0
  64. package/internal/orchestrator/test_classifier_test.go +68 -0
  65. package/internal/patch/applier.go +131 -0
  66. package/internal/patch/applier_test.go +25 -0
  67. package/internal/patch/parser.go +89 -0
  68. package/internal/patch/patch.go +60 -0
  69. package/internal/patch/summary.go +30 -0
  70. package/internal/patch/validator.go +104 -0
  71. package/internal/planning/normalizer.go +416 -0
  72. package/internal/planning/normalizer_test.go +64 -0
  73. package/internal/providers/errors.go +35 -0
  74. package/internal/providers/openai/client.go +498 -0
  75. package/internal/providers/openai/client_test.go +187 -0
  76. package/internal/providers/provider.go +47 -0
  77. package/internal/providers/registry.go +32 -0
  78. package/internal/providers/registry_test.go +57 -0
  79. package/internal/providers/router.go +52 -0
  80. package/internal/providers/state.go +114 -0
  81. package/internal/providers/state_test.go +64 -0
  82. package/internal/repo/analyzer.go +188 -0
  83. package/internal/repo/context.go +83 -0
  84. package/internal/review/engine.go +267 -0
  85. package/internal/review/engine_test.go +103 -0
  86. package/internal/runstore/store.go +137 -0
  87. package/internal/runstore/store_test.go +59 -0
  88. package/internal/runtime/lock.go +150 -0
  89. package/internal/runtime/lock_test.go +57 -0
  90. package/internal/session/compaction.go +260 -0
  91. package/internal/session/compaction_test.go +36 -0
  92. package/internal/session/service.go +117 -0
  93. package/internal/session/service_test.go +113 -0
  94. package/internal/storage/storage.go +1498 -0
  95. package/internal/storage/storage_test.go +413 -0
  96. package/internal/testing/classifier.go +80 -0
  97. package/internal/testing/classifier_test.go +36 -0
  98. package/internal/tools/command.go +160 -0
  99. package/internal/tools/command_test.go +56 -0
  100. package/internal/tools/file.go +111 -0
  101. package/internal/tools/git.go +77 -0
  102. package/internal/tools/invalid_params_test.go +36 -0
  103. package/internal/tools/policy.go +98 -0
  104. package/internal/tools/policy_test.go +36 -0
  105. package/internal/tools/registry_test.go +52 -0
  106. package/internal/tools/result.go +30 -0
  107. package/internal/tools/search.go +86 -0
  108. package/internal/tools/tool.go +94 -0
  109. package/main.go +9 -0
  110. package/npm/orch.js +25 -0
  111. package/package.json +41 -0
  112. package/scripts/changelog.js +20 -0
  113. package/scripts/check-release-version.js +21 -0
  114. package/scripts/lib/release-utils.js +223 -0
  115. package/scripts/postinstall.js +157 -0
  116. package/scripts/release.js +52 -0
package/cmd/doctor.go ADDED
@@ -0,0 +1,149 @@
1
+ package cmd
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "os"
7
+ "strings"
8
+ "time"
9
+
10
+ "github.com/furkanbeydemir/orch/internal/auth"
11
+ "github.com/furkanbeydemir/orch/internal/config"
12
+ "github.com/furkanbeydemir/orch/internal/providers/openai"
13
+ "github.com/spf13/cobra"
14
+ )
15
+
16
+ var doctorCmd = &cobra.Command{
17
+ Use: "doctor",
18
+ Short: "Validate Orch runtime readiness",
19
+ RunE: runDoctor,
20
+ }
21
+
22
+ func init() {
23
+ rootCmd.AddCommand(doctorCmd)
24
+ }
25
+
26
+ func runDoctor(cmd *cobra.Command, args []string) error {
27
+ cwd, err := os.Getwd()
28
+ if err != nil {
29
+ return fmt.Errorf("failed to get working directory: %w", err)
30
+ }
31
+
32
+ cfg, err := config.Load(cwd)
33
+ if err != nil {
34
+ return fmt.Errorf("failed to load configuration: %w", err)
35
+ }
36
+
37
+ type check struct {
38
+ name string
39
+ ok bool
40
+ detail string
41
+ }
42
+
43
+ checks := make([]check, 0)
44
+
45
+ defaultProvider := strings.TrimSpace(cfg.Provider.Default)
46
+ checks = append(checks, check{
47
+ name: "provider.default",
48
+ ok: defaultProvider == "openai",
49
+ detail: fmt.Sprintf("value=%q", defaultProvider),
50
+ })
51
+
52
+ checks = append(checks, check{
53
+ name: "provider.flags.openaiEnabled",
54
+ ok: cfg.Provider.Flags.OpenAIEnabled,
55
+ detail: fmt.Sprintf("value=%t", cfg.Provider.Flags.OpenAIEnabled),
56
+ })
57
+
58
+ authMode := strings.ToLower(strings.TrimSpace(cfg.Provider.OpenAI.AuthMode))
59
+ if authMode == "" {
60
+ authMode = "api_key"
61
+ }
62
+ checks = append(checks, check{name: "openai.auth_mode", ok: authMode == "api_key" || authMode == "account", detail: authMode})
63
+
64
+ key := strings.TrimSpace(os.Getenv(cfg.Provider.OpenAI.APIKeyEnv))
65
+ accountToken := strings.TrimSpace(os.Getenv(cfg.Provider.OpenAI.AccountTokenEnv))
66
+
67
+ storedCred, credErr := auth.Get(cwd, "openai")
68
+ storedAPIKey := credErr == nil && storedCred != nil && storedCred.Type == "api" && strings.TrimSpace(storedCred.Key) != ""
69
+ storedAccount := credErr == nil && storedCred != nil && storedCred.Type == "oauth" && strings.TrimSpace(storedCred.AccessToken) != ""
70
+ storedRefresh := credErr == nil && storedCred != nil && storedCred.Type == "oauth" && strings.TrimSpace(storedCred.RefreshToken) != ""
71
+ if credErr != nil {
72
+ checks = append(checks, check{name: "openai.stored_credential", ok: false, detail: credErr.Error()})
73
+ }
74
+
75
+ checks = append(checks, check{
76
+ name: "openai.api_key",
77
+ ok: key != "" || storedAPIKey || authMode == "account",
78
+ detail: fmt.Sprintf("env=%s", cfg.Provider.OpenAI.APIKeyEnv),
79
+ })
80
+
81
+ checks = append(checks, check{
82
+ name: "openai.account_token",
83
+ ok: accountToken != "" || storedAccount || authMode == "api_key",
84
+ detail: fmt.Sprintf("env=%s stored=%t refresh=%t", cfg.Provider.OpenAI.AccountTokenEnv, storedAccount, storedRefresh),
85
+ })
86
+
87
+ checks = append(checks, check{
88
+ name: "openai.account_refresh",
89
+ ok: authMode != "account" || accountToken != "" || !storedAccount || storedRefresh || (storedCred != nil && storedCred.ExpiresAt.IsZero()) || time.Now().UTC().Before(storedCred.ExpiresAt),
90
+ detail: fmt.Sprintf("required_when_expired=%t", authMode == "account" && accountToken == ""),
91
+ })
92
+
93
+ checks = append(checks, check{name: "openai.model.planner", ok: strings.TrimSpace(cfg.Provider.OpenAI.Models.Planner) != "", detail: cfg.Provider.OpenAI.Models.Planner})
94
+ checks = append(checks, check{name: "openai.model.coder", ok: strings.TrimSpace(cfg.Provider.OpenAI.Models.Coder) != "", detail: cfg.Provider.OpenAI.Models.Coder})
95
+ checks = append(checks, check{name: "openai.model.reviewer", ok: strings.TrimSpace(cfg.Provider.OpenAI.Models.Reviewer) != "", detail: cfg.Provider.OpenAI.Models.Reviewer})
96
+
97
+ if cfg.Provider.Flags.OpenAIEnabled && defaultProvider == "openai" {
98
+ client := openai.New(cfg.Provider.OpenAI)
99
+ client.SetTokenResolver(func(ctx context.Context) (string, error) {
100
+ _ = ctx
101
+ if authMode == "api_key" {
102
+ if storedCred != nil && strings.TrimSpace(storedCred.Key) != "" {
103
+ return strings.TrimSpace(storedCred.Key), nil
104
+ }
105
+ return "", nil
106
+ }
107
+ resolved, resolveErr := auth.ResolveAccountAccessToken(cwd, "openai")
108
+ if resolveErr != nil {
109
+ return "", resolveErr
110
+ }
111
+ return resolved, nil
112
+ })
113
+
114
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
115
+ defer cancel()
116
+ validateErr := client.Validate(ctx)
117
+ checks = append(checks, check{
118
+ name: "openai.auth",
119
+ ok: validateErr == nil,
120
+ detail: errDetail(validateErr, "ok"),
121
+ })
122
+ }
123
+
124
+ failed := 0
125
+ fmt.Println("Orch Doctor")
126
+ fmt.Println("-----------")
127
+ for _, c := range checks {
128
+ status := "PASS"
129
+ if !c.ok {
130
+ status = "FAIL"
131
+ failed++
132
+ }
133
+ fmt.Printf("%-6s %-32s %s\n", status, c.name, c.detail)
134
+ }
135
+
136
+ if failed > 0 {
137
+ return fmt.Errorf("doctor failed: %d checks failed", failed)
138
+ }
139
+
140
+ fmt.Println("All checks passed.")
141
+ return nil
142
+ }
143
+
144
+ func errDetail(err error, fallback string) string {
145
+ if err == nil {
146
+ return fallback
147
+ }
148
+ return err.Error()
149
+ }
package/cmd/explain.go ADDED
@@ -0,0 +1,192 @@
1
+ package cmd
2
+
3
+ import (
4
+ "fmt"
5
+ "os"
6
+ "sort"
7
+ "strings"
8
+
9
+ "github.com/furkanbeydemir/orch/internal/models"
10
+ "github.com/spf13/cobra"
11
+ )
12
+
13
+ var explainCmd = &cobra.Command{
14
+ Use: "explain [run-id]",
15
+ Short: "Explain why a run passed, failed, or was downgraded",
16
+ Long: `Shows the structured reasoning artifacts for a run, including plan, validation, test, review, and confidence signals.`,
17
+ Args: cobra.MaximumNArgs(1),
18
+ RunE: runExplain,
19
+ }
20
+
21
+ func init() {
22
+ rootCmd.AddCommand(explainCmd)
23
+ }
24
+
25
+ func runExplain(cmd *cobra.Command, args []string) error {
26
+ cwd, err := os.Getwd()
27
+ if err != nil {
28
+ return fmt.Errorf("failed to get working directory: %w", err)
29
+ }
30
+
31
+ ctx, err := loadSessionContext(cwd)
32
+ if err != nil {
33
+ return err
34
+ }
35
+ defer ctx.Store.Close()
36
+
37
+ var state *models.RunState
38
+ if len(args) == 0 {
39
+ state, err = ctx.Store.GetLatestRunStateBySession(ctx.Session.ID)
40
+ if err != nil {
41
+ return fmt.Errorf("failed to load latest run state: %w", err)
42
+ }
43
+ } else {
44
+ state, err = ctx.Store.GetRunState(ctx.ProjectID, args[0])
45
+ if err != nil {
46
+ return fmt.Errorf("failed to load run state %s: %w", args[0], err)
47
+ }
48
+ }
49
+
50
+ printExplain(state)
51
+ return nil
52
+ }
53
+
54
+ func printExplain(state *models.RunState) {
55
+ if state == nil {
56
+ fmt.Println("No run state available.")
57
+ return
58
+ }
59
+
60
+ fmt.Println("═══════════════════════════════════════")
61
+ fmt.Println("šŸ”Ž RUN EXPLANATION")
62
+ fmt.Println("═══════════════════════════════════════")
63
+ fmt.Printf("Run ID: %s\n", state.ID)
64
+ fmt.Printf("Status: %s\n", state.Status)
65
+ fmt.Printf("Task: %s\n", state.Task.Description)
66
+ if state.Error != "" {
67
+ fmt.Printf("Error: %s\n", state.Error)
68
+ }
69
+
70
+ if state.TaskBrief != nil {
71
+ fmt.Println("\n🧭 Task Brief")
72
+ fmt.Printf(" Type: %s\n", state.TaskBrief.TaskType)
73
+ fmt.Printf(" Risk: %s\n", state.TaskBrief.RiskLevel)
74
+ fmt.Printf(" Goal: %s\n", state.TaskBrief.NormalizedGoal)
75
+ }
76
+
77
+ if state.Plan != nil {
78
+ fmt.Println("\nšŸ“‹ Plan")
79
+ if state.Plan.Summary != "" {
80
+ fmt.Printf(" Summary: %s\n", state.Plan.Summary)
81
+ }
82
+ if len(state.Plan.FilesToModify) > 0 {
83
+ fmt.Printf(" Files To Modify: %s\n", strings.Join(state.Plan.FilesToModify, ", "))
84
+ }
85
+ if len(state.Plan.AcceptanceCriteria) > 0 {
86
+ fmt.Println(" Acceptance Criteria:")
87
+ for _, criterion := range state.Plan.AcceptanceCriteria {
88
+ fmt.Printf(" - %s\n", criterion.Description)
89
+ }
90
+ }
91
+ }
92
+
93
+ if state.ExecutionContract != nil {
94
+ fmt.Println("\n🧩 Execution Contract")
95
+ if len(state.ExecutionContract.AllowedFiles) > 0 {
96
+ fmt.Printf(" Allowed Files: %s\n", strings.Join(state.ExecutionContract.AllowedFiles, ", "))
97
+ }
98
+ if len(state.ExecutionContract.RequiredEdits) > 0 {
99
+ fmt.Printf(" Required Edits: %s\n", strings.Join(state.ExecutionContract.RequiredEdits, " | "))
100
+ }
101
+ fmt.Printf(" Patch Budget: max_files=%d max_changed_lines=%d\n", state.ExecutionContract.PatchBudget.MaxFiles, state.ExecutionContract.PatchBudget.MaxChangedLines)
102
+ }
103
+
104
+ if len(state.ValidationResults) > 0 {
105
+ fmt.Println("\nšŸ›”ļø Validation + Review/Test Gates")
106
+ results := append([]models.ValidationResult(nil), state.ValidationResults...)
107
+ sort.SliceStable(results, func(i, j int) bool {
108
+ if results[i].Stage == results[j].Stage {
109
+ return results[i].Name < results[j].Name
110
+ }
111
+ return results[i].Stage < results[j].Stage
112
+ })
113
+ for _, result := range results {
114
+ fmt.Printf(" - [%s] %s = %s (%s)\n", result.Stage, result.Name, result.Status, result.Summary)
115
+ }
116
+ }
117
+
118
+ if len(state.TestFailures) > 0 {
119
+ fmt.Println("\n🧪 Test Failure Classification")
120
+ for _, failure := range state.TestFailures {
121
+ line := fmt.Sprintf(" - %s: %s", failure.Code, failure.Summary)
122
+ if failure.Flaky {
123
+ line += " [flaky-suspected]"
124
+ }
125
+ fmt.Println(line)
126
+ }
127
+ } else if strings.TrimSpace(state.TestResults) != "" {
128
+ fmt.Println("\n🧪 Test Output")
129
+ fmt.Printf(" %s\n", strings.ReplaceAll(strings.TrimSpace(state.TestResults), "\n", "\n "))
130
+ }
131
+
132
+ if state.ReviewScorecard != nil {
133
+ fmt.Println("\n🧠 Review Scorecard")
134
+ fmt.Printf(" Requirement Coverage: %d\n", state.ReviewScorecard.RequirementCoverage)
135
+ fmt.Printf(" Scope Control: %d\n", state.ReviewScorecard.ScopeControl)
136
+ fmt.Printf(" Regression Risk: %d\n", state.ReviewScorecard.RegressionRisk)
137
+ fmt.Printf(" Readability: %d\n", state.ReviewScorecard.Readability)
138
+ fmt.Printf(" Maintainability: %d\n", state.ReviewScorecard.Maintainability)
139
+ fmt.Printf(" Test Adequacy: %d\n", state.ReviewScorecard.TestAdequacy)
140
+ fmt.Printf(" Decision: %s\n", state.ReviewScorecard.Decision)
141
+ if len(state.ReviewScorecard.Findings) > 0 {
142
+ fmt.Println(" Findings:")
143
+ for _, finding := range state.ReviewScorecard.Findings {
144
+ fmt.Printf(" - %s\n", finding)
145
+ }
146
+ }
147
+ }
148
+
149
+ if state.Confidence != nil {
150
+ fmt.Println("\nšŸŽÆ Confidence")
151
+ fmt.Printf(" Score: %.2f\n", state.Confidence.Score)
152
+ fmt.Printf(" Band: %s\n", state.Confidence.Band)
153
+ if len(state.Confidence.Reasons) > 0 {
154
+ fmt.Println(" Reasons:")
155
+ for _, reason := range state.Confidence.Reasons {
156
+ fmt.Printf(" - %s\n", reason)
157
+ }
158
+ }
159
+ if len(state.Confidence.Warnings) > 0 {
160
+ fmt.Println(" Warnings:")
161
+ for _, warning := range state.Confidence.Warnings {
162
+ fmt.Printf(" - %s\n", warning)
163
+ }
164
+ }
165
+ }
166
+
167
+ if state.Review != nil {
168
+ fmt.Println("\nšŸ’¬ Final Review")
169
+ fmt.Printf(" Decision: %s\n", state.Review.Decision)
170
+ for _, comment := range state.Review.Comments {
171
+ fmt.Printf(" - %s\n", comment)
172
+ }
173
+ if len(state.Review.Suggestions) > 0 {
174
+ fmt.Println(" Suggestions:")
175
+ for _, suggestion := range state.Review.Suggestions {
176
+ fmt.Printf(" - %s\n", suggestion)
177
+ }
178
+ }
179
+ }
180
+
181
+ if state.RetryDirective != nil {
182
+ fmt.Println("\nšŸ” Last Retry Directive")
183
+ fmt.Printf(" Stage: %s\n", state.RetryDirective.Stage)
184
+ fmt.Printf(" Attempt: %d\n", state.RetryDirective.Attempt)
185
+ if len(state.RetryDirective.Reasons) > 0 {
186
+ fmt.Printf(" Reasons: %s\n", strings.Join(state.RetryDirective.Reasons, " | "))
187
+ }
188
+ if len(state.RetryDirective.Instructions) > 0 {
189
+ fmt.Printf(" Instructions: %s\n", strings.Join(state.RetryDirective.Instructions, " | "))
190
+ }
191
+ }
192
+ }
@@ -0,0 +1,62 @@
1
+ package cmd
2
+
3
+ import (
4
+ "bytes"
5
+ "io"
6
+ "os"
7
+ "strings"
8
+ "testing"
9
+ "time"
10
+
11
+ "github.com/furkanbeydemir/orch/internal/models"
12
+ )
13
+
14
+ func TestPrintExplainIncludesConfidenceAndReview(t *testing.T) {
15
+ state := &models.RunState{
16
+ ID: "run-explain-1",
17
+ Task: models.Task{ID: "task-1", Description: "demo", CreatedAt: time.Now()},
18
+ Status: models.StatusCompleted,
19
+ StartedAt: time.Now(),
20
+ Review: &models.ReviewResult{Decision: models.ReviewAccept, Comments: []string{"looks good"}},
21
+ Confidence: &models.ConfidenceReport{
22
+ Score: 0.91,
23
+ Band: "high",
24
+ Reasons: []string{"tests and review aligned"},
25
+ },
26
+ }
27
+
28
+ output := captureStdout(t, func() {
29
+ printExplain(state)
30
+ })
31
+
32
+ if !strings.Contains(output, "RUN EXPLANATION") {
33
+ t.Fatalf("expected explanation header, got: %s", output)
34
+ }
35
+ if !strings.Contains(output, "Score: 0.91") {
36
+ t.Fatalf("expected confidence score in output, got: %s", output)
37
+ }
38
+ if !strings.Contains(output, "Decision: accept") {
39
+ t.Fatalf("expected review decision in output, got: %s", output)
40
+ }
41
+ }
42
+
43
+ func captureStdout(t *testing.T, fn func()) string {
44
+ t.Helper()
45
+ original := os.Stdout
46
+ r, w, err := os.Pipe()
47
+ if err != nil {
48
+ t.Fatalf("pipe: %v", err)
49
+ }
50
+ os.Stdout = w
51
+ defer func() { os.Stdout = original }()
52
+
53
+ fn()
54
+
55
+ _ = w.Close()
56
+ var buf bytes.Buffer
57
+ if _, err := io.Copy(&buf, r); err != nil {
58
+ t.Fatalf("copy stdout: %v", err)
59
+ }
60
+ _ = r.Close()
61
+ return buf.String()
62
+ }
package/cmd/init.go ADDED
@@ -0,0 +1,100 @@
1
+ // Package cmd implements the init command.
2
+ package cmd
3
+
4
+ import (
5
+ "fmt"
6
+ "os"
7
+ "path/filepath"
8
+
9
+ "github.com/furkanbeydemir/orch/internal/config"
10
+ "github.com/furkanbeydemir/orch/internal/repo"
11
+ "github.com/furkanbeydemir/orch/internal/storage"
12
+ "github.com/spf13/cobra"
13
+ )
14
+
15
+ // initCmd represents the `orch init` command.
16
+ var initCmd = &cobra.Command{
17
+ Use: "init",
18
+ Short: "Analyze repository and create configuration",
19
+ Long: `Analyzes repository and creates .orch/ and configuration files.`,
20
+ RunE: runInit,
21
+ }
22
+
23
+ func init() {
24
+ rootCmd.AddCommand(initCmd)
25
+ }
26
+
27
+ func runInit(cmd *cobra.Command, args []string) error {
28
+ cwd, err := os.Getwd()
29
+ if err != nil {
30
+ return fmt.Errorf("failed to get working directory: %w", err)
31
+ }
32
+
33
+ return performInit(cwd, false)
34
+ }
35
+
36
+ func AutoInitIfNeeded(cwd string) error {
37
+ configPath := filepath.Join(cwd, config.OrchDir, config.ConfigFile)
38
+ if _, err := os.Stat(configPath); err == nil {
39
+ // Already initialized
40
+ return nil
41
+ }
42
+
43
+ // Print a notice since it's happening automatically
44
+ fmt.Println("✨ First run detected. Initializing Orch automatically...")
45
+ return performInit(cwd, true)
46
+ }
47
+
48
+ func performInit(cwd string, quiet bool) error {
49
+ if !quiet {
50
+ fmt.Println("šŸ” Analyzing repository...")
51
+ }
52
+
53
+ if err := config.EnsureOrchDir(cwd); err != nil {
54
+ return err
55
+ }
56
+
57
+ cfg := config.DefaultConfig()
58
+ if err := config.Save(cwd, cfg); err != nil {
59
+ return err
60
+ }
61
+ if !quiet {
62
+ fmt.Println("āœ… .orch/config.json created")
63
+ }
64
+
65
+ // Repository analysis
66
+ analyzer := repo.NewAnalyzer(cwd)
67
+ repoMap, err := analyzer.Analyze()
68
+ if err != nil {
69
+ return fmt.Errorf("repository analysis failed: %w", err)
70
+ }
71
+
72
+ if !quiet {
73
+ fmt.Printf("āœ… .orch/repo-map.json created (%d files scanned)\n", len(repoMap.Files))
74
+ fmt.Printf("šŸ“‹ Language: %s | Package Manager: %s | Test: %s\n",
75
+ repoMap.Language, repoMap.PackageManager, repoMap.TestFramework)
76
+ }
77
+
78
+ store, err := storage.Open(cwd)
79
+ if err != nil {
80
+ return fmt.Errorf("failed to initialize sqlite storage: %w", err)
81
+ }
82
+ defer store.Close()
83
+
84
+ projectID, err := store.GetOrCreateProject()
85
+ if err != nil {
86
+ return fmt.Errorf("failed to resolve project: %w", err)
87
+ }
88
+
89
+ session, err := store.EnsureDefaultSession(projectID)
90
+ if err != nil {
91
+ return fmt.Errorf("failed to resolve default session: %w", err)
92
+ }
93
+
94
+ if !quiet {
95
+ fmt.Printf("āœ… SQLite storage ready (.orch/orch.db), active session: %s (%s)\n", session.Name, session.ID)
96
+ fmt.Println("\nšŸŽÆ Orch configured successfully!")
97
+ }
98
+
99
+ return nil
100
+ }