-
Notifications
You must be signed in to change notification settings - Fork 0
fix: make review workbenches location-independent and resume-safe #507
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b8fedc7
4356d3a
e6e6cce
a09fcdb
c6e9c93
2cbb49e
1978338
83af757
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,15 +13,16 @@ data lifecycle commands. | |
|
|
||
| The current Go code is a Cobra command tree in `internal/cmd/*` with a thin | ||
| `cmd/cr` entrypoint, shared exit-code mapping in `internal/cmd/exitcode`, and | ||
| version plumbing in `internal/version`. Command-independent helpers used to | ||
| compose review and response application runtimes live in `internal/appruntime`; | ||
| command packages keep CLI composition, rendering, config-path selection, and | ||
| error mapping. Review orchestration is split across `internal/reviewruntime`, | ||
| version plumbing in `internal/version`. Production review and response runtime | ||
| assembly lives in `internal/app`; `internal/appruntime` contains only small | ||
| command-independent helpers such as retention conversion and invocation-root | ||
| resolution. Command packages keep CLI composition, rendering, config-path | ||
| selection, and error mapping. Review orchestration is split across | ||
| `internal/pipeline`, `internal/reviewrun`, | ||
| `internal/threadrespond`, `internal/reviewplan`, `internal/outbox`, | ||
| `internal/gate`, and `internal/gateio`. Command packages construct typed | ||
| runtime requests, while `internal/reviewruntime` owns the reusable provider, | ||
| ledger, adapter, pipeline, live-run, and response-run assembly. | ||
| runtime requests, while `internal/app` owns the reusable provider, ledger, | ||
| adapter, pipeline, live-run, and response-run assembly. | ||
|
|
||
| Architecture guardrails for LLM execution, model resolution, Git provider | ||
| writes, command and review harness boundaries, inline thread lifecycle, and | ||
|
|
@@ -43,6 +44,12 @@ Within `internal/pipeline`, the public entry points are `DryRun`, `Live`, and | |
| and no ledger or posting side effects so benchmark tooling can reuse the real | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. File-level note: docs/development.md The repo-local development map should point contributors at the current runtime composition boundary, but this section still says review/response runtime assembly lives in Reply inline to this comment.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed - development.md now identifies internal/app as the production composition root and limits internal/appruntime to small runtime helpers. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed. Updating Summary: |
||
| selector implementation. | ||
|
|
||
| Review workbench preparation is location-independent. Runtime composition | ||
| constructs the authenticated Git command adapter from the repository/read | ||
| credential and injects it into the pipeline; workbench code derives remotes | ||
| from the validated PR identity and must not resolve or inspect the invocation | ||
| checkout. | ||
|
|
||
| Structured LLM calls in the review pipeline are durable per-task units. See | ||
| `docs/llm-task-artifacts.md` for the artifact schema, status taxonomy, and | ||
| resume invariants. | ||
|
|
@@ -98,12 +105,12 @@ make clean # remove build artifacts | |
| - Current distribution status: GitHub release archives plus standard package | ||
| channels declared in `packaging/identity.yml`. | ||
| - Application package layering follows the active codereview implementation: | ||
| command glue in `internal/cmd/*`, review/response app runtime helpers in | ||
| `internal/appruntime`, presentation in `internal/view`, | ||
| command glue in `internal/cmd/*`, production review/response composition in | ||
| `internal/app`, small runtime helpers in `internal/appruntime`, presentation in `internal/view`, | ||
| state/config adapters in `internal/config`, `internal/ledger`, and | ||
| `internal/statepaths`, provider/LLM adapters in their owning packages, and | ||
| review runtime assembly in `internal/reviewruntime`, review posting/gating in | ||
| `internal/outbox`, `internal/gate`, and `internal/gateio`, and response-only | ||
| review posting/gating in `internal/outbox`, `internal/gate`, and | ||
| `internal/gateio`, and response-only | ||
| inline discussion handling in | ||
| `internal/threadrespond`. | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,9 +14,11 @@ import ( | |
| "github.com/open-cli-collective/cli-common/credstore" | ||
|
|
||
| "github.com/open-cli-collective/codereview-cli/internal/approvaloverride" | ||
| "github.com/open-cli-collective/codereview-cli/internal/appruntime" | ||
| "github.com/open-cli-collective/codereview-cli/internal/config" | ||
| "github.com/open-cli-collective/codereview-cli/internal/credentials" | ||
| "github.com/open-cli-collective/codereview-cli/internal/datalifecycle" | ||
| "github.com/open-cli-collective/codereview-cli/internal/gitexec" | ||
| "github.com/open-cli-collective/codereview-cli/internal/gitprovider" | ||
| githubprovider "github.com/open-cli-collective/codereview-cli/internal/gitprovider/github" | ||
| "github.com/open-cli-collective/codereview-cli/internal/hooks" | ||
|
|
@@ -73,8 +75,6 @@ type OpenRequest struct { | |
| MaxConcurrency int | ||
| Retention datalifecycle.RetentionPolicy | ||
| RetentionManualOnly bool | ||
| ResolveRepoRoot func(context.Context) (string, error) | ||
| GitCommand func(context.Context, string, ...string) ([]byte, error) | ||
| Dependencies Dependencies | ||
| } | ||
|
|
||
|
|
@@ -103,6 +103,8 @@ type AdapterFactory func(config.LLMConfig, credentials.Reader) (llm.Adapter, err | |
| // defaults. | ||
| type Dependencies struct { | ||
| NewGitProvider GitProviderFactory | ||
| NewGitCommand func(string, gitexec.TokenSource) (func(context.Context, string, ...string) ([]byte, error), error) | ||
| ResolveRepoRoot func(context.Context) (string, error) | ||
| ResolvePostingIdentity PostingIdentityResolver | ||
| NewAdapter AdapterFactory | ||
| RuntimeLayout func() (statepaths.Layout, error) | ||
|
|
@@ -119,6 +121,18 @@ func (d Dependencies) withDefaults() Dependencies { | |
| if d.ResolvePostingIdentity == nil { | ||
| d.ResolvePostingIdentity = resolvePostingIdentity | ||
| } | ||
| if d.NewGitCommand == nil { | ||
| d.NewGitCommand = func(host string, tokens gitexec.TokenSource) (func(context.Context, string, ...string) ([]byte, error), error) { | ||
| client, err := gitexec.New(host, tokens) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return client.Run, nil | ||
| } | ||
| } | ||
| if d.ResolveRepoRoot == nil { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Reply inline to this comment.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed - app now defaults the seam through appruntime.ResolveRepoRoot. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed. Routing the default seam through Summary: |
||
| d.ResolveRepoRoot = appruntime.ResolveRepoRoot | ||
| } | ||
| if d.NewAdapter == nil { | ||
| d.NewAdapter = newAdapter | ||
| } | ||
|
|
@@ -150,7 +164,12 @@ func Open(ctx context.Context, req OpenRequest) (Runtime, error) { | |
| cleanup() | ||
| return Runtime{}, err | ||
| } | ||
| repoProvider, _, err := deps.NewGitProvider(profile.Git, repoProviderStore, gitProviderOptions(profile, profile.Git, req.PRRef)) | ||
| repoProvider, repoCredential, err := deps.NewGitProvider(profile.Git, repoProviderStore, gitProviderOptions(profile, profile.Git, req.PRRef)) | ||
| if err != nil { | ||
| cleanup() | ||
| return Runtime{}, err | ||
| } | ||
| gitCommand, err := deps.NewGitCommand(profile.Git.Host, repositoryTokenSource(repoProvider, repoCredential)) | ||
| if err != nil { | ||
| cleanup() | ||
| return Runtime{}, err | ||
|
|
@@ -215,7 +234,7 @@ func Open(ctx context.Context, req OpenRequest) (Runtime, error) { | |
| } | ||
| return withProgressAdapter(req.Progress, command, adapter, string(profile.LLM.Provider), string(profile.LLM.Adapter)), nil | ||
| }) | ||
| runner := buildReviewRunner(ledgerStore, repoProvider, postingProvider, adapter, profile, limiter, layout, req.Warnings, req.Progress, dispatcher, req, command) | ||
| runner := buildReviewRunner(ledgerStore, repoProvider, postingProvider, adapter, profile, limiter, layout, req.Warnings, req.Progress, dispatcher, req, command, gitCommand, deps.ResolveRepoRoot) | ||
| return Runtime{ | ||
| Runner: runner, | ||
| Responder: runner, | ||
|
|
@@ -372,7 +391,7 @@ func runtimeLayout() (statepaths.Layout, error) { | |
| return layout, nil | ||
| } | ||
|
|
||
| func buildReviewRunner(ledgerStore *ledger.Store, repoProvider gitprovider.GitProvider, postingProvider gitprovider.GitProvider, adapter llm.Adapter, profile config.Profile, limiter outbox.Limiter, layout statepaths.Layout, warnings io.Writer, logger *progress.Logger, dispatcher *hookDispatcher, req OpenRequest, command string) reviewRunner { | ||
| func buildReviewRunner(ledgerStore *ledger.Store, repoProvider gitprovider.GitProvider, postingProvider gitprovider.GitProvider, adapter llm.Adapter, profile config.Profile, limiter outbox.Limiter, layout statepaths.Layout, warnings io.Writer, logger *progress.Logger, dispatcher *hookDispatcher, req OpenRequest, command string, gitCommand func(context.Context, string, ...string) ([]byte, error), resolveRepoRoot func(context.Context) (string, error)) reviewRunner { | ||
| taskProgress := newPipelineTaskProgress(logger, command, dispatcher) | ||
| liveProvider := runtimeProvider{read: repoProvider, write: postingProvider} | ||
| pipelineOpts := pipeline.Options{ | ||
|
|
@@ -387,8 +406,8 @@ func buildReviewRunner(ledgerStore *ledger.Store, repoProvider gitprovider.GitPr | |
| MaxConcurrency: req.MaxConcurrency, | ||
| Retention: req.Retention, | ||
| RetentionManualOnly: req.RetentionManualOnly, | ||
| ResolveRepoRoot: req.ResolveRepoRoot, | ||
| GitCommand: req.GitCommand, | ||
| ResolveRepoRoot: resolveRepoRoot, | ||
| GitCommand: gitCommand, | ||
| } | ||
| return reviewRunner{ | ||
| pipeline: pipelineOpts, | ||
|
|
@@ -403,7 +422,7 @@ func buildReviewRunner(ledgerStore *ledger.Store, repoProvider gitprovider.GitPr | |
| ApprovalOverride: withProgressApprovalOverrideClassifier(logger, buildApprovalOverrideClassifier(profile, adapter, warnings)), | ||
| Retention: req.Retention, | ||
| RetentionManualOnly: req.RetentionManualOnly, | ||
| ResolveRepoRoot: req.ResolveRepoRoot, | ||
| ResolveRepoRoot: resolveRepoRoot, | ||
| }, | ||
| respond: threadrespond.Options{ | ||
| Store: ledgerStore, | ||
|
|
@@ -645,6 +664,13 @@ func resolvePostingIdentity(ctx context.Context, provider gitprovider.GitProvide | |
| return provider.WhoAmI(ctx, credential) | ||
| } | ||
|
|
||
| func repositoryTokenSource(provider gitprovider.GitProvider, credential gitprovider.Credential) gitexec.TokenSource { | ||
| if source, ok := provider.(gitexec.TokenSource); ok { | ||
| return source | ||
| } | ||
| return gitexec.TokenSourceFunc(func(context.Context) (string, error) { return credential.Token, nil }) | ||
| } | ||
|
|
||
| type adapterConstructor func(config.LLMConfig, credentials.Reader, config.LLMRuntimeSpec) (llm.Adapter, error) | ||
|
|
||
| var adapterConstructors = map[config.LLMAdapter]adapterConstructor{ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
File-level note: docs/architecture.md
This file is the repo-local architecture source of truth, but after adding the new Git transport guidance it still says review runtime assembly belongs in
internal/reviewruntimeand thatcr review/cr respondshould constructreviewruntime.OpenRequestvalues. In the current tree the production composition root isinternal/app/app.OpenRequest, andinternal/architecture/git_transport_test.gonow codifiesinternal/appas the only place allowed to constructgitexec. Leaving the old package names here gives contributors the wrong layering rule for future runtime work. Update the guardrail text to point atinternal/appfor production runtime assembly, while keepinginternal/appruntimescoped to small command-independent helpers like retention and repo-root resolution.Reply inline to this comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed - the architecture guardrail now names internal/app and app.OpenRequest as the production composition boundary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. That update aligns the architecture guardrail with the current production composition boundary in
internal/appandapp.OpenRequest.Summary:
Updated
docs/architecture.mdto nameinternal/appandapp.OpenRequestas the production runtime composition boundary, replacing the outdatedinternal/reviewruntimeguidance.