git: a creation gate that never writes history, a git tab with Paperclip's worktree scan, and a warning line in every pane

This commit is contained in:
Sam
2026-11-12 20:38:44 +11:00
parent f7b77f370f
commit c419de5b48
18 changed files with 2223 additions and 82 deletions

View File

@@ -221,6 +221,23 @@ type Project struct {
Name string `json:"name"`
URLKey string `json:"urlKey"`
Status string `json:"status"`
// The worktree policy is fixed when the project is created, so the screen
// reports it rather than offering to change it.
ExecutionWorkspacePolicy *struct {
Enabled bool `json:"enabled"`
DefaultMode string `json:"defaultMode"`
Strategy struct {
Type string `json:"type"`
Base string `json:"baseRef"`
} `json:"workspaceStrategy"`
} `json:"executionWorkspacePolicy"`
Workspaces []struct {
SourceType string `json:"sourceType"`
Cwd string `json:"cwd"`
DefaultRef string `json:"defaultRef"`
} `json:"workspaces"`
}
// Comment is one entry on a task's thread.
@@ -615,3 +632,76 @@ func (c *Client) ImportSkill(source string) (string, error) {
}
return "", fmt.Errorf("import returned no skill")
}
// ExecutionWorkspace is one task's own directory: a git worktree, or a shared
// folder when the project does not use worktrees. Paperclip makes these, not us.
type ExecutionWorkspace struct {
ID string `json:"id"`
Name string `json:"name"`
ProjectID string `json:"projectId"`
SourceIssueID string `json:"sourceIssueId"`
BranchName string `json:"branchName"`
BaseRef string `json:"baseRef"`
ProviderType string `json:"providerType"`
ProviderRef string `json:"providerRef"`
Mode string `json:"mode"`
Status string `json:"status"`
DeliveryState string `json:"deliveryState"`
Cwd string `json:"cwd"`
LastUsedAt string `json:"lastUsedAt"`
}
// CloseReadiness is Paperclip's own git scan of one execution workspace, plus
// what it would do if the workspace were closed. The git half is the only place
// in Paperclip that reports branch, dirtiness and how far behind a worktree is.
type CloseReadiness struct {
State string `json:"state"`
DeliveryState string `json:"deliveryState"`
BlockingReasons []string `json:"blockingReasons"`
Warnings []string `json:"warnings"`
Git struct {
RepoRoot string `json:"repoRoot"`
WorkspacePath string `json:"workspacePath"`
BranchName string `json:"branchName"`
BaseRef string `json:"baseRef"`
HasDirtyTrackedFiles bool `json:"hasDirtyTrackedFiles"`
HasUntrackedFiles bool `json:"hasUntrackedFiles"`
DirtyEntryCount int `json:"dirtyEntryCount"`
UntrackedEntryCount int `json:"untrackedEntryCount"`
AheadCount int `json:"aheadCount"`
BehindCount int `json:"behindCount"`
IsMergedIntoBase bool `json:"isMergedIntoBase"`
CreatedByRuntime bool `json:"createdByRuntime"`
} `json:"git"`
}
// ExecutionWorkspaces lists every task workspace in the company. They are
// filtered per project here rather than by the API, which has no such filter.
func (c *Client) ExecutionWorkspaces() ([]ExecutionWorkspace, error) {
var raw json.RawMessage
if err := c.do("GET", "/api/companies/"+c.Company+"/execution-workspaces", nil, &raw); err != nil {
return nil, err
}
var list []ExecutionWorkspace
if err := json.Unmarshal(raw, &list); err == nil {
return list, nil
}
var wrapper struct {
Workspaces []ExecutionWorkspace `json:"workspaces"`
Data []ExecutionWorkspace `json:"data"`
}
if err := json.Unmarshal(raw, &wrapper); err != nil {
return nil, err
}
if len(wrapper.Workspaces) > 0 {
return wrapper.Workspaces, nil
}
return wrapper.Data, nil
}
// CloseReadinessFor asks Paperclip to scan one execution workspace's git state.
func (c *Client) CloseReadinessFor(id string) (CloseReadiness, error) {
var out CloseReadiness
err := c.do("GET", "/api/execution-workspaces/"+id+"/close-readiness", nil, &out)
return out, err
}