relay-flow 0.2.8-alpha → 0.2.9-alpha
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/README.md +62 -35
- package/cmd/relay-flow/commands_test.go +117 -8
- package/cmd/relay-flow/main.go +167 -60
- package/cmd/relay-flow/onboarding.go +344 -0
- package/cmd/relay-flow/onboarding_test.go +515 -0
- package/cmd/relay-flow/repo_registration.go +219 -0
- package/cmd/relay-flow/serve.go +3 -0
- package/go.mod +12 -8
- package/go.sum +24 -17
- package/internal/execution/goworkflows/engine_test.go +3 -3
- package/internal/harness/opencode/opencode_test.go +4 -4
- package/internal/harness/opencode/repo_setup.go +1 -1
- package/internal/harness/pi/prompt_test.go +3 -3
- package/internal/repo/service.go +34 -3
- package/internal/repo/service_test.go +22 -0
- package/internal/runner/herdr/herdr.go +103 -4
- package/internal/runner/herdr/herdr_test.go +72 -2
- package/internal/runner/herdr/herdrcli/contract.go +12 -3
- package/internal/runner/herdr/herdrcli/herdrcli_test.go +17 -2
- package/internal/runner/herdr/herdrcli/operations.go +16 -0
- package/internal/runner/herdr/herdrcli/testdata/strict-herdr.sh +8 -0
- package/internal/runner/herdr/herdrcli/testdata/workspace-create.json +1 -0
- package/internal/runner/orca/orca.go +68 -9
- package/internal/runner/orca/orca_test.go +134 -2
- package/internal/runner/orca/orcacli/orcacli.go +21 -1
- package/internal/runner/orca/orcacli/orcacli_test.go +22 -0
- package/internal/runner/orca/orcacli/testdata/strict-orca.sh +5 -0
- package/internal/runner/runner.go +9 -0
- package/internal/server/api_test.go +19 -0
- package/internal/server/client.go +10 -0
- package/internal/server/fixture_test.go +7 -0
- package/internal/server/server.go +41 -0
- package/internal/task/jira/testdata/jira_search_issues.json +4 -4
- package/internal/workflow/workflow.go +4 -1
- package/internal/workflow/workflow_test.go +4 -4
- package/package.json +1 -1
|
@@ -57,8 +57,22 @@ type Client interface {
|
|
|
57
57
|
// CLI is the production Client backed by the orca binary.
|
|
58
58
|
type CLI struct{}
|
|
59
59
|
|
|
60
|
+
// RepoRegistrar is the optional repository-provisioning seam used by the
|
|
61
|
+
// runner adapter. Keeping it separate from Client lets existing read-only
|
|
62
|
+
// test clients remain focused on the runtime operations they exercise.
|
|
63
|
+
type RepoRegistrar interface {
|
|
64
|
+
AddRepo(ctx context.Context, path string) error
|
|
65
|
+
}
|
|
66
|
+
|
|
60
67
|
func New() *CLI { return &CLI{} }
|
|
61
68
|
|
|
69
|
+
// AddRepo registers an existing local repository with Orca. Orca derives its
|
|
70
|
+
// display name from the path; relay-flow keeps the user-facing registration
|
|
71
|
+
// name separately.
|
|
72
|
+
func (CLI) AddRepo(ctx context.Context, path string) error {
|
|
73
|
+
return run(ctx, "repo", "add", "--path", path, "--json")
|
|
74
|
+
}
|
|
75
|
+
|
|
62
76
|
func (CLI) ListRepos(ctx context.Context) ([]Repo, error) {
|
|
63
77
|
var res struct {
|
|
64
78
|
Result struct {
|
|
@@ -194,7 +208,13 @@ func (CLI) CreateTerminal(ctx context.Context, ticketKey, title, command string)
|
|
|
194
208
|
}
|
|
195
209
|
|
|
196
210
|
func (CLI) CloseTerminal(ctx context.Context, handle string) error {
|
|
197
|
-
|
|
211
|
+
if err := run(ctx, "terminal", "close", "--terminal", handle, "--json"); err != nil {
|
|
212
|
+
if strings.Contains(err.Error(), "terminal_handle_stale") {
|
|
213
|
+
return ErrTerminalUnavailable
|
|
214
|
+
}
|
|
215
|
+
return err
|
|
216
|
+
}
|
|
217
|
+
return nil
|
|
198
218
|
}
|
|
199
219
|
|
|
200
220
|
func run(ctx context.Context, args ...string) error {
|
|
@@ -2,6 +2,7 @@ package orcacli
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"context"
|
|
5
|
+
"errors"
|
|
5
6
|
"os"
|
|
6
7
|
"os/exec"
|
|
7
8
|
"path/filepath"
|
|
@@ -30,6 +31,9 @@ func TestCLIContractsAgainstCapturedRealOutput(t *testing.T) {
|
|
|
30
31
|
t.Fatalf("ListWorktrees = %+v", worktrees)
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
if err := cli.AddRepo(ctx, "/work/new"); err != nil {
|
|
35
|
+
t.Fatal(err)
|
|
36
|
+
}
|
|
33
37
|
if err := cli.CreateWorktree(ctx, "PAY-101", "repo-1", "wt-main", "origin/alice/PAY-101"); err != nil {
|
|
34
38
|
t.Fatal(err)
|
|
35
39
|
}
|
|
@@ -80,6 +84,24 @@ func TestStrictFakeOrcaRejectsMissingFlags(t *testing.T) {
|
|
|
80
84
|
}
|
|
81
85
|
}
|
|
82
86
|
|
|
87
|
+
func TestCloseTerminalMapsStaleHandleToUnavailable(t *testing.T) {
|
|
88
|
+
installStrictFakeOrca(t)
|
|
89
|
+
cli := New()
|
|
90
|
+
|
|
91
|
+
err := cli.CloseTerminal(context.Background(), "term-stale")
|
|
92
|
+
if !errors.Is(err, ErrTerminalUnavailable) {
|
|
93
|
+
t.Fatalf("CloseTerminal(stale) = %v, want ErrTerminalUnavailable", err)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
err = cli.CloseTerminal(context.Background(), "term-unknown")
|
|
97
|
+
if err == nil {
|
|
98
|
+
t.Fatal("CloseTerminal(unknown) = nil, want the CLI failure")
|
|
99
|
+
}
|
|
100
|
+
if errors.Is(err, ErrTerminalUnavailable) {
|
|
101
|
+
t.Fatalf("CloseTerminal(unknown) = %v, incorrectly classified as stale", err)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
83
105
|
func TestFindExistingBranchLocalOnly(t *testing.T) {
|
|
84
106
|
repo := newGitRepo(t)
|
|
85
107
|
runGit(t, repo, "branch", "alice/PAY-101")
|
|
@@ -4,6 +4,8 @@ set -eu
|
|
|
4
4
|
fixture=
|
|
5
5
|
if [ "$#" -eq 3 ] && [ "$1" = repo ] && [ "$2" = list ] && [ "$3" = --json ]; then
|
|
6
6
|
fixture=repo-list.json
|
|
7
|
+
elif [ "$#" -eq 5 ] && [ "$1" = repo ] && [ "$2" = add ] && [ "$3" = --path ] && [ "$4" = /work/new ] && [ "$5" = --json ]; then
|
|
8
|
+
fixture=repo-list.json
|
|
7
9
|
elif [ "$#" -eq 3 ] && [ "$1" = worktree ] && [ "$2" = list ] && [ "$3" = --json ]; then
|
|
8
10
|
fixture=worktree-list.json
|
|
9
11
|
elif [ "$#" -eq 11 ] && [ "$1" = worktree ] && [ "$2" = create ] && [ "$3" = --name ] && [ "$4" = PAY-101 ] && [ "$5" = --repo ] && [ "$6" = id:repo-1 ] && [ "$7" = --parent-worktree ] && [ "$8" = worktree:wt-main ] && [ "$9" = --base-branch ] && [ "${10}" = origin/alice/PAY-101 ] && [ "${11}" = --json ]; then
|
|
@@ -20,6 +22,9 @@ elif [ "$#" -eq 8 ] && [ "$1" = terminal ] && [ "$2" = send ] && [ "$3" = --term
|
|
|
20
22
|
fixture=terminal-send.json
|
|
21
23
|
elif [ "$#" -eq 9 ] && [ "$1" = terminal ] && [ "$2" = create ] && [ "$3" = --worktree ] && [ "$4" = name:PAY-101 ] && [ "$5" = --title ] && [ "$6" = PAY-101:implement ] && [ "$7" = --command ] && [ "$8" = 'echo hello' ] && [ "$9" = --json ]; then
|
|
22
24
|
fixture=terminal-create.json
|
|
25
|
+
elif [ "$#" -eq 5 ] && [ "$1" = terminal ] && [ "$2" = close ] && [ "$3" = --terminal ] && [ "$4" = term-stale ] && [ "$5" = --json ]; then
|
|
26
|
+
printf '%s\n' '{"ok":false,"error":{"code":"terminal_handle_stale","message":"terminal_handle_stale"}}'
|
|
27
|
+
exit 1
|
|
23
28
|
elif [ "$#" -eq 5 ] && [ "$1" = terminal ] && [ "$2" = close ] && [ "$3" = --terminal ] && [ "$4" = term-1 ] && [ "$5" = --json ]; then
|
|
24
29
|
fixture=terminal-close.json
|
|
25
30
|
else
|
|
@@ -24,6 +24,15 @@ type RepoCandidate struct {
|
|
|
24
24
|
Path string `json:"path"`
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// RepoRegistrar is the optional runner capability used by repository
|
|
28
|
+
// registration. Runners that manage an external repository/workspace can
|
|
29
|
+
// make the resource available before relay-flow persists the repo entry.
|
|
30
|
+
// Runners without a provisioning primitive still use ValidateRepo through
|
|
31
|
+
// the repo service.
|
|
32
|
+
type RepoRegistrar interface {
|
|
33
|
+
EnsureRepo(context.Context, string, string) error
|
|
34
|
+
}
|
|
35
|
+
|
|
27
36
|
type Environment struct {
|
|
28
37
|
ID string `json:"id"`
|
|
29
38
|
Path string `json:"path"`
|
|
@@ -14,6 +14,7 @@ import (
|
|
|
14
14
|
|
|
15
15
|
"github.com/rajpopat27/relay-flow/internal/config"
|
|
16
16
|
"github.com/rajpopat27/relay-flow/internal/run"
|
|
17
|
+
"github.com/rajpopat27/relay-flow/internal/runner"
|
|
17
18
|
"github.com/rajpopat27/relay-flow/internal/server"
|
|
18
19
|
"github.com/rajpopat27/relay-flow/internal/task"
|
|
19
20
|
"github.com/rajpopat27/relay-flow/internal/workflow"
|
|
@@ -333,6 +334,24 @@ func TestClientRepoRegistrationFieldsUsesGetThenPost(t *testing.T) {
|
|
|
333
334
|
}
|
|
334
335
|
}
|
|
335
336
|
|
|
337
|
+
func TestRepoEnsureEndpoint(t *testing.T) {
|
|
338
|
+
fake := &fakeServices{}
|
|
339
|
+
c, cleanup := startHandler(t, fake)
|
|
340
|
+
defer cleanup()
|
|
341
|
+
code, env := do(t, c, http.MethodPost, "http://relay/repos/ensure", []byte(`{"name":"payments","path":"/srv/payments"}`))
|
|
342
|
+
if code != http.StatusOK || !env.OK {
|
|
343
|
+
t.Fatalf("POST /repos/ensure: code=%d env=%+v, want 200 ok", code, env)
|
|
344
|
+
}
|
|
345
|
+
if len(fake.ensureRepoCalls) != 1 || fake.ensureRepoCalls[0] != (runner.RepoCandidate{Name: "payments", Path: "/srv/payments"}) {
|
|
346
|
+
t.Fatalf("EnsureRepo calls = %+v", fake.ensureRepoCalls)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
code, env = do(t, c, http.MethodPost, "http://relay/repos/ensure", []byte(`{"name":"payments"}`))
|
|
350
|
+
if code != http.StatusBadRequest || env.OK {
|
|
351
|
+
t.Fatalf("invalid POST /repos/ensure: code=%d env=%+v, want 400", code, env)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
336
355
|
func TestRepoOperations(t *testing.T) {
|
|
337
356
|
c, cleanup := startHandler(t, &fakeServices{})
|
|
338
357
|
defer cleanup()
|
|
@@ -136,6 +136,16 @@ func (c *Client) DiscoverRepos(ctx context.Context) ([]runner.RepoCandidate, err
|
|
|
136
136
|
return out, nil
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
// EnsureRepo asks the configured runner to make a repository resource
|
|
140
|
+
// available without persisting a relay-flow repo entry.
|
|
141
|
+
func (c *Client) EnsureRepo(ctx context.Context, candidate runner.RepoCandidate) error {
|
|
142
|
+
payload, err := json.Marshal(candidate)
|
|
143
|
+
if err != nil {
|
|
144
|
+
return err
|
|
145
|
+
}
|
|
146
|
+
return c.call(ctx, http.MethodPost, "/repos/ensure", payload, nil)
|
|
147
|
+
}
|
|
148
|
+
|
|
139
149
|
// RepoTaskFields returns the documented initial required-key metadata.
|
|
140
150
|
func (c *Client) RepoTaskFields(ctx context.Context) ([]string, error) {
|
|
141
151
|
var out struct {
|
|
@@ -40,6 +40,8 @@ type fakeServices struct {
|
|
|
40
40
|
restartErr error
|
|
41
41
|
restarts []string
|
|
42
42
|
registrationValues []config.RawValues
|
|
43
|
+
ensureRepoCalls []runner.RepoCandidate
|
|
44
|
+
ensureRepoErr error
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
func (f *fakeServices) SubmitWorkflow(_ context.Context, yaml []byte) (*workflow.Workflow, error) {
|
|
@@ -155,6 +157,11 @@ func (f *fakeServices) DiscoverRepos(context.Context) ([]runner.RepoCandidate, e
|
|
|
155
157
|
return []runner.RepoCandidate{{Name: "payments", Path: "/srv/payments"}}, nil
|
|
156
158
|
}
|
|
157
159
|
|
|
160
|
+
func (f *fakeServices) EnsureRepo(_ context.Context, candidate runner.RepoCandidate) error {
|
|
161
|
+
f.ensureRepoCalls = append(f.ensureRepoCalls, candidate)
|
|
162
|
+
return f.ensureRepoErr
|
|
163
|
+
}
|
|
164
|
+
|
|
158
165
|
func (f *fakeServices) TaskRegistrationFields(_ context.Context, values config.RawValues) ([]task.RegistrationField, error) {
|
|
159
166
|
f.registrationValues = append(f.registrationValues, values)
|
|
160
167
|
return []task.RegistrationField{{Key: "project", Title: "Project"}, {Key: "component", Title: "Component", Derived: true}}, nil
|
|
@@ -85,6 +85,7 @@ func New(deps Deps) http.Handler {
|
|
|
85
85
|
mux.HandleFunc("/workflows", s.handleWorkflows)
|
|
86
86
|
mux.HandleFunc("/workflows/", s.handleWorkflowByName)
|
|
87
87
|
mux.HandleFunc("/repos/discover", s.handleReposDiscover)
|
|
88
|
+
mux.HandleFunc("/repos/ensure", s.handleRepoEnsure)
|
|
88
89
|
mux.HandleFunc("/repos/task-fields", s.handleRepoTaskFields)
|
|
89
90
|
mux.HandleFunc("/repos", s.handleRepos)
|
|
90
91
|
mux.HandleFunc("/repos/", s.handleRepoByName)
|
|
@@ -99,6 +100,14 @@ type server struct {
|
|
|
99
100
|
deps Deps
|
|
100
101
|
}
|
|
101
102
|
|
|
103
|
+
// repoEnsurer is implemented by the composition root when the selected
|
|
104
|
+
// runner can provision a repository resource. It is intentionally separate
|
|
105
|
+
// from Deps so existing task/run service seams do not need a runner-specific
|
|
106
|
+
// method.
|
|
107
|
+
type repoEnsurer interface {
|
|
108
|
+
EnsureRepo(context.Context, runner.RepoCandidate) error
|
|
109
|
+
}
|
|
110
|
+
|
|
102
111
|
// --- envelope helpers ---
|
|
103
112
|
|
|
104
113
|
func writeOK(w http.ResponseWriter, status int, data any) {
|
|
@@ -247,6 +256,38 @@ func (s *server) handleReposDiscover(w http.ResponseWriter, r *http.Request) {
|
|
|
247
256
|
writeOK(w, http.StatusOK, candidates)
|
|
248
257
|
}
|
|
249
258
|
|
|
259
|
+
func (s *server) handleRepoEnsure(w http.ResponseWriter, r *http.Request) {
|
|
260
|
+
if !methodOnly(w, r, http.MethodPost) {
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
ensurer, ok := s.deps.(repoEnsurer)
|
|
264
|
+
if !ok {
|
|
265
|
+
mapErr(w, fmt.Errorf("%w: runner repository provisioning is unavailable", ErrInvalid))
|
|
266
|
+
return
|
|
267
|
+
}
|
|
268
|
+
body, err := readBody(r)
|
|
269
|
+
if err != nil {
|
|
270
|
+
writeErr(w, http.StatusBadRequest, "invalid", err.Error())
|
|
271
|
+
return
|
|
272
|
+
}
|
|
273
|
+
var candidate runner.RepoCandidate
|
|
274
|
+
if err := decodeStrict(body, &candidate); err != nil {
|
|
275
|
+
writeErr(w, http.StatusBadRequest, "invalid", err.Error())
|
|
276
|
+
return
|
|
277
|
+
}
|
|
278
|
+
candidate.Name = strings.TrimSpace(candidate.Name)
|
|
279
|
+
candidate.Path = strings.TrimSpace(candidate.Path)
|
|
280
|
+
if candidate.Name == "" || candidate.Path == "" {
|
|
281
|
+
writeErr(w, http.StatusBadRequest, "invalid", "repository name and path are required")
|
|
282
|
+
return
|
|
283
|
+
}
|
|
284
|
+
if err := ensurer.EnsureRepo(r.Context(), candidate); err != nil {
|
|
285
|
+
mapErr(w, err)
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
writeOK(w, http.StatusOK, candidate)
|
|
289
|
+
}
|
|
290
|
+
|
|
250
291
|
func (s *server) handleRepoTaskFields(w http.ResponseWriter, r *http.Request) {
|
|
251
292
|
if r.Method == http.MethodGet {
|
|
252
293
|
fields, err := s.deps.TaskRegistrationFields(r.Context(), nil)
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"48x48": "https://secure.gravatar.com/avatar/09110b9326675e772c94aa61fc89195b?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FRP-6.png"
|
|
16
16
|
},
|
|
17
17
|
"displayName": "Raj Popat",
|
|
18
|
-
"emailAddress": "
|
|
18
|
+
"emailAddress": "test.user@example.com",
|
|
19
19
|
"self": "https://jira-prod-us-26-3.prod.atl-paas.net/rest/api/3/user?accountId=712020%3Adad5df85-a7bd-4337-b858-0faf9012a009",
|
|
20
20
|
"timeZone": "America/New_York"
|
|
21
21
|
},
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"fieldsToInclude": null,
|
|
50
50
|
"id": "157320",
|
|
51
|
-
"key": "
|
|
51
|
+
"key": "DEMO-41242",
|
|
52
52
|
"names": null,
|
|
53
53
|
"operations": null,
|
|
54
54
|
"properties": null,
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"48x48": "https://secure.gravatar.com/avatar/09110b9326675e772c94aa61fc89195b?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FRP-6.png"
|
|
75
75
|
},
|
|
76
76
|
"displayName": "Raj Popat",
|
|
77
|
-
"emailAddress": "
|
|
77
|
+
"emailAddress": "test.user@example.com",
|
|
78
78
|
"self": "https://jira-prod-us-26-3.prod.atl-paas.net/rest/api/3/user?accountId=712020%3Adad5df85-a7bd-4337-b858-0faf9012a009",
|
|
79
79
|
"timeZone": "America/New_York"
|
|
80
80
|
},
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
},
|
|
108
108
|
"fieldsToInclude": null,
|
|
109
109
|
"id": "155110",
|
|
110
|
-
"key": "
|
|
110
|
+
"key": "DEMO-41175",
|
|
111
111
|
"names": null,
|
|
112
112
|
"operations": null,
|
|
113
113
|
"properties": null,
|
|
@@ -62,6 +62,7 @@ type NudgeTemplateData struct {
|
|
|
62
62
|
Workflow string
|
|
63
63
|
Repo string
|
|
64
64
|
Node string
|
|
65
|
+
Mailbox string
|
|
65
66
|
NextSteps string
|
|
66
67
|
}
|
|
67
68
|
|
|
@@ -70,7 +71,7 @@ var namePattern = regexp.MustCompile(`^[a-z][a-zA-Z0-9]*$`)
|
|
|
70
71
|
var nudgeVarPattern = regexp.MustCompile(`\{\{([^{}]*)\}\}`)
|
|
71
72
|
|
|
72
73
|
var knownNudgeVars = map[string]bool{
|
|
73
|
-
"taskSystem": true, "ticket": true, "workflow": true, "repo": true, "node": true, "nextSteps": true,
|
|
74
|
+
"taskSystem": true, "ticket": true, "workflow": true, "repo": true, "node": true, "mailbox": true, "nextSteps": true,
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
// Parse strictly decodes a workflow YAML document. Unknown fields and
|
|
@@ -326,6 +327,8 @@ func (w *Workflow) RenderNudge(node string, data NudgeTemplateData) (string, err
|
|
|
326
327
|
return data.Repo
|
|
327
328
|
case "node":
|
|
328
329
|
return data.Node
|
|
330
|
+
case "mailbox":
|
|
331
|
+
return data.Mailbox
|
|
329
332
|
case "nextSteps":
|
|
330
333
|
return data.NextSteps
|
|
331
334
|
}
|
|
@@ -350,7 +350,7 @@ func TestValidateGraphReachability(t *testing.T) {
|
|
|
350
350
|
|
|
351
351
|
func TestValidateNudgeTemplate(t *testing.T) {
|
|
352
352
|
t.Run("supported variables", func(t *testing.T) {
|
|
353
|
-
yaml := strings.Replace(minimalValid, " description: Do the coding work.", " description: Do the coding work.\n nudgePrompt: \"{{taskSystem}} {{ticket}} {{workflow}} {{repo}} {{node}} {{nextSteps}}\"", 1)
|
|
353
|
+
yaml := strings.Replace(minimalValid, " description: Do the coding work.", " description: Do the coding work.\n nudgePrompt: \"{{taskSystem}} {{ticket}} {{workflow}} {{repo}} {{node}} {{mailbox}} {{nextSteps}}\"", 1)
|
|
354
354
|
wf := parse(t, "basicFlow", yaml)
|
|
355
355
|
if err := wf.Validate(); err != nil {
|
|
356
356
|
t.Fatalf("supported nudge variables rejected: %v", err)
|
|
@@ -389,16 +389,16 @@ func TestCleanupRunnerOnEndDefaultsFalse(t *testing.T) {
|
|
|
389
389
|
}
|
|
390
390
|
|
|
391
391
|
func TestRenderNudge(t *testing.T) {
|
|
392
|
-
yaml := strings.Replace(minimalValid, " description: Do the coding work.", " description: Do the coding work.\n nudgePrompt: \"task={{taskSystem}} ticket={{ticket}} wf={{workflow}} repo={{repo}} node={{node}} steps={{nextSteps}}\"", 1)
|
|
392
|
+
yaml := strings.Replace(minimalValid, " description: Do the coding work.", " description: Do the coding work.\n nudgePrompt: \"task={{taskSystem}} ticket={{ticket}} wf={{workflow}} repo={{repo}} node={{node}} mailbox={{mailbox}} steps={{nextSteps}}\"", 1)
|
|
393
393
|
wf := parse(t, "basicFlow", yaml)
|
|
394
394
|
for _, taskSystem := range []string{"jira", "linear"} {
|
|
395
395
|
out, err := wf.RenderNudge("coding", workflow.NudgeTemplateData{
|
|
396
|
-
TaskSystem: taskSystem, Ticket: "PAY-101", Workflow: "basicFlow", Repo: "payments", Node: "coding", NextSteps: "end",
|
|
396
|
+
TaskSystem: taskSystem, Ticket: "PAY-101", Workflow: "basicFlow", Repo: "payments", Node: "coding", Mailbox: "PAY-101.1", NextSteps: "end",
|
|
397
397
|
})
|
|
398
398
|
if err != nil {
|
|
399
399
|
t.Fatalf("RenderNudge(%s) failed: %v", taskSystem, err)
|
|
400
400
|
}
|
|
401
|
-
want := "task=" + taskSystem + " ticket=PAY-101 wf=basicFlow repo=payments node=coding steps=end"
|
|
401
|
+
want := "task=" + taskSystem + " ticket=PAY-101 wf=basicFlow repo=payments node=coding mailbox=PAY-101.1 steps=end"
|
|
402
402
|
if out != want {
|
|
403
403
|
t.Fatalf("RenderNudge(%s) = %q, want %q", taskSystem, out, want)
|
|
404
404
|
}
|