Skip to content

Commit 3a93e33

Browse files
neversettle17-101claudegreptile-apps[bot]i-trytoohard
authored
refactor: move project manager to service layer (#68)
* refactor(project): manager talks to the sqlite store; drop the in-memory store The project Manager now runs only against the durable backend store: remove the process-local MemoryStore (and NewMemoryManager), and require a real Store. The daemon already wires the sqlite store; tests now build a real temp-dir sqlite store instead of the mock. - Move Row + the Store port to project/store.go. The Store interface stays because it is the dependency-inversion port that lets the manager reach the backend without an import cycle (storage imports project.Row), not an extra mock layer — there is no longer any in-memory implementation. - NewManager requires a non-nil Store (no in-memory fallback). - Add project/manager_test.go: List/Add/Get/Remove happy paths + PATH_REQUIRED/NOT_A_GIT_REPO/PATH_ALREADY_REGISTERED/ID_ALREADY_REGISTERED, PROJECT_NOT_FOUND/INVALID_PROJECT_ID, and UpdateConfig — all against a real sqlite store (the service-logic tests #47 lacked). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(project): trim routes, consolidate package, add code-first OpenAPI - Remove POST /reload, PATCH /{id}, POST /{id}/repair routes and their Manager methods (Reload, UpdateConfig, Repair) and DTOs (ReloadResult, UpdateConfigInput) — not needed at this stage - Merge Manager interface into manager.go; delete project.go (single-impl split served no purpose) - Remove dead notImplemented helper from errors.go - Port PR #59 code-first OpenAPI generation: controllers/dto.go named response types, specgen/build.go (4 routes), parity + drift tests, cmd/genspec, go generate wiring; regenerate openapi.yaml - Add swaggest deps; add YAML() method to apispec.Spec Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * fix(project): address PR review comments - t.Skipf → t.Fatalf in gitRepo helper: git failures now hard-fail instead of silently skipping manager tests on a misconfigured runner - FindProjectByPath: add AND archived_at IS NULL so archived paths don't permanently block re-registration (update queries/projects.sql and generated gen/projects.sql.go) - Add TestManager_ReaddAfterRemove to lock the fix Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * fixed lint and fmt * addressed greptile comments * Apply suggestions from code review Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * project tests fix * project_tests fix * fix: Linting and formatting fix * refactor: move project manager into service layer (#68) * refactor: split service package by resource (#68) * fix: ignore archived project id conflicts (#68) * refactor: move pr manager into service layer (#68) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: itrytoohard <ayetrytoohard@gmail.com>
1 parent 424e6e8 commit 3a93e33

40 files changed

Lines changed: 1987 additions & 1100 deletions

backend/cmd/genspec/main.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Command genspec writes the code-first OpenAPI document produced by
2+
// apispec.Build() to disk. It is invoked via `go generate` (see
3+
// internal/httpd/apispec/gen.go); the output openapi.yaml is committed and
4+
// embedded by the apispec package.
5+
package main
6+
7+
import (
8+
"flag"
9+
"log"
10+
"os"
11+
12+
"github.com/aoagents/agent-orchestrator/backend/internal/httpd/apispec/specgen"
13+
)
14+
15+
func main() {
16+
out := flag.String("out", "openapi.yaml", "output path for the generated OpenAPI document")
17+
flag.Parse()
18+
19+
doc, err := specgen.Build()
20+
if err != nil {
21+
log.Fatalf("genspec: build openapi: %v", err)
22+
}
23+
if err := os.WriteFile(*out, doc, 0o600); err != nil {
24+
log.Fatalf("genspec: write %s: %v", *out, err)
25+
}
26+
}

backend/go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ require (
88
github.com/go-chi/chi/v5 v5.1.0
99
github.com/pressly/goose/v3 v3.27.1
1010
github.com/spf13/cobra v1.10.1
11+
github.com/swaggest/jsonschema-go v0.3.79
12+
github.com/swaggest/openapi-go v0.2.61
1113
golang.org/x/sys v0.43.0
1214
gopkg.in/yaml.v3 v3.0.1
1315
modernc.org/sqlite v1.51.0
@@ -23,8 +25,10 @@ require (
2325
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
2426
github.com/sethvargo/go-retry v0.3.0 // indirect
2527
github.com/spf13/pflag v1.0.9 // indirect
28+
github.com/swaggest/refl v1.4.0 // indirect
2629
go.uber.org/multierr v1.11.0 // indirect
2730
golang.org/x/sync v0.20.0 // indirect
31+
gopkg.in/yaml.v2 v2.4.0 // indirect
2832
modernc.org/libc v1.72.3 // indirect
2933
modernc.org/mathutil v1.7.1 // indirect
3034
modernc.org/memory v1.11.0 // indirect

backend/go.sum

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
github.com/bool64/dev v0.2.43 h1:yQ7qiZVef6WtCl2vDYU0Y+qSq+0aBrQzY8KXkklk9cQ=
2+
github.com/bool64/dev v0.2.43/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
3+
github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E=
4+
github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs=
15
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
26
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
37
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
@@ -15,6 +19,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
1519
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
1620
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
1721
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
22+
github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc=
23+
github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE=
1824
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
1925
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
2026
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
@@ -30,6 +36,8 @@ github.com/pressly/goose/v3 v3.27.1/go.mod h1:maruOxsPnIG2yHHyo8UqKWXYKFcH7Q76cs
3036
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
3137
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
3238
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
39+
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
40+
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
3341
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
3442
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
3543
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
@@ -38,6 +46,18 @@ github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
3846
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
3947
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
4048
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
49+
github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ=
50+
github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU=
51+
github.com/swaggest/jsonschema-go v0.3.79 h1:0TOShCbAJ9Xjt1e2W83l+QtMQSG2pbun2EkiYTyafCs=
52+
github.com/swaggest/jsonschema-go v0.3.79/go.mod h1:GqVmJ+XNLeUHhFIhHNKc+C68euxfrl3a3aoZH4vTRl0=
53+
github.com/swaggest/openapi-go v0.2.61 h1:psc+LE7pWhEjmJpmkti9tUmBPkkobdUNflBf5Ps6JSc=
54+
github.com/swaggest/openapi-go v0.2.61/go.mod h1:786CwSwleh1IorB0nfwYGESWf83JgQh6fBc1PeJe4Iw=
55+
github.com/swaggest/refl v1.4.0 h1:CftOSdTqRqs100xpFOT/Rifss5xBV/CT0S/FN60Xe9k=
56+
github.com/swaggest/refl v1.4.0/go.mod h1:4uUVFVfPJ0NSX9FPwMPspeHos9wPFlCMGoPRllUbpvA=
57+
github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
58+
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
59+
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
60+
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
4161
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
4262
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
4363
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
@@ -50,6 +70,8 @@ golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
5070
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
5171
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
5272
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
73+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
74+
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
5375
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
5476
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
5577
modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY=

backend/internal/adapters/scm/github/doc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@
114114
//
115115
// - The poller loop and cadence selection (issue #35).
116116
// - Webhook ingestion (this package is polling-only).
117-
// - Persistence (PR Manager owns the row mapping; see internal/pr).
117+
// - Persistence (PR Manager owns the row mapping; see internal/service/pr).
118118
// - Linear / GitLab providers (separate PRs).
119119
// - Issue tracking (separate lane, see internal/adapters/tracker).
120120
// - Comment-injection-into-session-context (Messenger lane, not SCM).

backend/internal/cdc/cdc_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99

1010
"github.com/aoagents/agent-orchestrator/backend/internal/cdc"
1111
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
12-
"github.com/aoagents/agent-orchestrator/backend/internal/project"
1312
"github.com/aoagents/agent-orchestrator/backend/internal/storage/sqlite"
1413
)
1514

@@ -27,7 +26,7 @@ func seedSession(t *testing.T, s *sqlite.Store) domain.SessionRecord {
2726
t.Helper()
2827
ctx := context.Background()
2928
now := time.Now().UTC().Truncate(time.Second)
30-
if err := s.Upsert(ctx, project.Row{ID: "mer", Path: "/m", RegisteredAt: now}); err != nil {
29+
if err := s.UpsertProject(ctx, domain.ProjectRecord{ID: "mer", Path: "/m", RegisteredAt: now}); err != nil {
3130
t.Fatal(err)
3231
}
3332
r, err := s.CreateSession(ctx, domain.SessionRecord{

backend/internal/daemon/daemon.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import (
1414
"github.com/aoagents/agent-orchestrator/backend/internal/adapters/runtime/zellij"
1515
"github.com/aoagents/agent-orchestrator/backend/internal/config"
1616
"github.com/aoagents/agent-orchestrator/backend/internal/httpd"
17-
"github.com/aoagents/agent-orchestrator/backend/internal/project"
1817
"github.com/aoagents/agent-orchestrator/backend/internal/runfile"
18+
projectsvc "github.com/aoagents/agent-orchestrator/backend/internal/service/project"
1919
"github.com/aoagents/agent-orchestrator/backend/internal/storage/sqlite"
2020
"github.com/aoagents/agent-orchestrator/backend/internal/terminal"
2121
)
@@ -66,7 +66,7 @@ func Run() error {
6666
termMgr := terminal.NewManager(runtimeAdapter, cdcPipe.Broadcaster, log)
6767
defer termMgr.Close()
6868

69-
srv, err := httpd.NewWithDeps(cfg, log, termMgr, httpd.APIDeps{Projects: project.NewManager(store)})
69+
srv, err := httpd.NewWithDeps(cfg, log, termMgr, httpd.APIDeps{Projects: projectsvc.New(store)})
7070
if err != nil {
7171
stop()
7272
if cdcErr := cdcPipe.Stop(); cdcErr != nil {

backend/internal/daemon/wiring_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
1111
"github.com/aoagents/agent-orchestrator/backend/internal/lifecycle"
1212
"github.com/aoagents/agent-orchestrator/backend/internal/ports"
13-
"github.com/aoagents/agent-orchestrator/backend/internal/project"
1413
"github.com/aoagents/agent-orchestrator/backend/internal/storage/sqlite"
1514
)
1615

@@ -37,7 +36,7 @@ func TestWiring_WriteFlowsToBroadcaster(t *testing.T) {
3736
var got []cdc.Event
3837
bcast.Subscribe(func(e cdc.Event) { mu.Lock(); got = append(got, e); mu.Unlock() })
3938

40-
if err := store.Upsert(ctx, project.Row{ID: "mer", Path: "/repo/mer"}); err != nil {
39+
if err := store.UpsertProject(ctx, domain.ProjectRecord{ID: "mer", Path: "/repo/mer"}); err != nil {
4140
t.Fatal(err)
4241
}
4342
rec, err := store.CreateSession(ctx, domain.SessionRecord{

backend/internal/domain/project.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package domain
2+
3+
import "time"
4+
5+
// ProjectRecord is the durable project registry row used by storage and services.
6+
type ProjectRecord struct {
7+
ID string
8+
Path string
9+
RepoOriginURL string
10+
DisplayName string
11+
RegisteredAt time.Time
12+
ArchivedAt time.Time
13+
}

backend/internal/httpd/api.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ import (
1010
"github.com/aoagents/agent-orchestrator/backend/internal/httpd/apispec"
1111
"github.com/aoagents/agent-orchestrator/backend/internal/httpd/controllers"
1212
"github.com/aoagents/agent-orchestrator/backend/internal/httpd/envelope"
13-
"github.com/aoagents/agent-orchestrator/backend/internal/project"
13+
projectsvc "github.com/aoagents/agent-orchestrator/backend/internal/service/project"
1414
)
1515

1616
// APIDeps bundles every Manager the API layer's controllers depend on.
1717
// Controllers see only resource-level interfaces; they do not reach through to
1818
// lifecycle reducers, adapters, or storage. A nil dependency keeps its routes
1919
// registered but returns the OpenAPI-backed 501 response.
2020
type APIDeps struct {
21-
Projects project.Manager
21+
Projects projectsvc.Manager
2222
Sessions controllers.SessionService
2323
}
2424

backend/internal/httpd/apispec/apispec.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ var openapiYAML []byte
2727
// preserves the YAML shape verbatim so the JSON we emit on 501 responses
2828
// matches the on-disk source.
2929
type Spec struct {
30-
doc map[string]any
30+
doc map[string]any
31+
rawYAML []byte
3132
}
3233

3334
var (
@@ -61,7 +62,12 @@ func New(yamlBytes []byte) (*Spec, error) {
6162
if doc == nil {
6263
return nil, fmt.Errorf("parse openapi: empty document")
6364
}
64-
return &Spec{doc: doc}, nil
65+
return &Spec{doc: doc, rawYAML: yamlBytes}, nil
66+
}
67+
68+
// YAML returns the raw YAML bytes this spec was built from.
69+
func (s *Spec) YAML() []byte {
70+
return s.rawYAML
6571
}
6672

6773
// Operation returns the spec slice for a single (method, path) pair, ready

0 commit comments

Comments
 (0)