relay-flow 0.2.0-alpha → 0.2.2-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 +171 -26
- package/cmd/relay-flow/beads_composition_test.go +451 -0
- package/cmd/relay-flow/commands_test.go +593 -15
- package/cmd/relay-flow/main.go +356 -119
- package/cmd/relay-flow/scenario_test.go +246 -35
- package/cmd/relay-flow/serve.go +5 -2
- package/examples/beads-workflow.yaml +74 -0
- package/examples/default-story-workflow.yaml +88 -0
- package/go.mod +2 -1
- package/go.sum +2 -0
- package/internal/config/config.go +13 -2
- package/internal/config/merge_test.go +18 -0
- package/internal/execution/goworkflows/activities.go +136 -87
- package/internal/execution/goworkflows/end_feedback_test.go +124 -0
- package/internal/execution/goworkflows/engine.go +41 -8
- package/internal/execution/goworkflows/engine_test.go +100 -22
- package/internal/execution/goworkflows/fakes_test.go +63 -25
- package/internal/execution/goworkflows/interpreter.go +45 -30
- package/internal/execution/goworkflows/mailbox_test.go +85 -0
- package/internal/execution/goworkflows/node_runtime_integration_test.go +12 -6
- package/internal/execution/goworkflows/node_runtime_test.go +72 -23
- package/internal/execution/goworkflows/recovery_test.go +7 -7
- package/internal/execution/goworkflows/report_contract_fixture_test.go +31 -0
- package/internal/execution/goworkflows/retry_log_test.go +11 -11
- package/internal/harness/contract_test.go +15 -0
- package/internal/harness/factory.go +25 -3
- package/internal/harness/harness.go +30 -4
- package/internal/harness/opencode/opencode.go +125 -10
- package/internal/harness/opencode/opencode_test.go +183 -0
- package/internal/harness/opencode/repo_setup.go +361 -0
- package/internal/harness/plugin_selection_test.go +5 -5
- package/internal/paths/paths.go +18 -16
- package/internal/recover/recover.go +11 -6
- package/internal/repo/repo.go +13 -0
- package/internal/repo/service.go +10 -0
- package/internal/repo/service_test.go +55 -6
- package/internal/router/router.go +3 -2
- package/internal/router/router_test.go +87 -0
- package/internal/run/manager.go +14 -1
- package/internal/run/run.go +6 -4
- package/internal/run/run_manager_test.go +21 -1
- package/internal/runner/contract_test.go +64 -26
- package/internal/runner/orca/orca.go +30 -54
- package/internal/runner/orca/orca_test.go +143 -4
- package/internal/runner/orca/orcacli/orcacli.go +5 -0
- package/internal/runner/orca/orcacli/orcacli_test.go +3 -0
- package/internal/runner/orca/orcacli/testdata/strict-orca.sh +2 -0
- package/internal/runner/runner.go +15 -8
- package/internal/task/auth_test.go +48 -0
- package/internal/task/beads/bdcli/bdcli.go +323 -0
- package/internal/task/beads/bdcli/bdcli_test.go +297 -0
- package/internal/task/beads/bdcli/testdata/array.json +1 -0
- package/internal/task/beads/bdcli/testdata/children.json +1 -0
- package/internal/task/beads/bdcli/testdata/claimed.json +1 -0
- package/internal/task/beads/bdcli/testdata/commented.json +1 -0
- package/internal/task/beads/bdcli/testdata/comments.json +1 -0
- package/internal/task/beads/bdcli/testdata/created.json +1 -0
- package/internal/task/beads/bdcli/testdata/object.json +1 -0
- package/internal/task/beads/bdcli/testdata/ready.json +1 -0
- package/internal/task/beads/bdcli/testdata/show.json +1 -0
- package/internal/task/beads/bdcli/testdata/strict-bd.sh +149 -0
- package/internal/task/beads/bdcli/testdata/updated.json +1 -0
- package/internal/task/beads/beads.go +840 -0
- package/internal/task/beads/beads_test.go +609 -0
- package/internal/task/beads/comments_test.go +242 -0
- package/internal/task/beads/config_compatibility_test.go +163 -0
- package/internal/task/beads/lifecycle_inheritance_test.go +168 -0
- package/internal/task/beads/repo_composition_test.go +232 -0
- package/internal/task/beads/runtime_config_test.go +81 -0
- package/internal/task/beads/status_compatibility_test.go +233 -0
- package/internal/task/beads/status_test.go +257 -0
- package/internal/task/beads/testdata/strict-bd-repo.sh +27 -0
- package/internal/task/beads/validation_test.go +110 -0
- package/internal/task/contract_test.go +12 -0
- package/internal/task/factory.go +52 -3
- package/internal/task/jira/auth.go +209 -0
- package/internal/task/jira/auth_test.go +160 -0
- package/internal/task/jira/effects_test.go +39 -0
- package/internal/task/jira/filters_test.go +96 -16
- package/internal/task/jira/helpers_test.go +29 -19
- package/internal/task/jira/jira.go +263 -90
- package/internal/task/jira/lifecycle_inheritance_test.go +172 -0
- package/internal/task/jira/normalize.go +32 -14
- package/internal/task/jira/rest/adf.go +165 -0
- package/internal/task/jira/rest/adf_test.go +60 -0
- package/internal/task/jira/rest/client.go +573 -0
- package/internal/task/jira/rest/client_test.go +381 -0
- package/internal/task/jira/templates_test.go +118 -0
- package/internal/task/jira/transition_defaults_test.go +22 -18
- package/internal/task/jira/validation_test.go +1 -1
- package/internal/task/task.go +32 -0
- package/internal/workflow/report_test.go +45 -0
- package/internal/workflow/workflow.go +9 -6
- package/internal/workflow/workflow_test.go +14 -12
- package/package.json +2 -1
- package/internal/task/jira/acli/acli.go +0 -306
- package/internal/task/jira/acli/acli_test.go +0 -208
- package/internal/task/jira/acli/testdata/acli_comments.json +0 -55
- package/internal/task/jira/acli/testdata/search_invalid_assignee.txt +0 -1
- package/internal/task/jira/acli/testdata/search_invalid_status.txt +0 -1
- /package/internal/task/{jira/acli/testdata/search_success.json → beads/bdcli/testdata/empty.json} +0 -0
- /package/internal/task/jira/testdata/{acli_search.json → jira_search_issues.json} +0 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
"testing"
|
|
9
|
+
"time"
|
|
10
|
+
|
|
11
|
+
"github.com/rajpopat27/relay-flow/internal/config"
|
|
12
|
+
"github.com/rajpopat27/relay-flow/internal/execution/goworkflows"
|
|
13
|
+
"github.com/rajpopat27/relay-flow/internal/repo"
|
|
14
|
+
runsvc "github.com/rajpopat27/relay-flow/internal/run"
|
|
15
|
+
"github.com/rajpopat27/relay-flow/internal/server"
|
|
16
|
+
"github.com/rajpopat27/relay-flow/internal/workflow"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
// TestBeadsCompositionUsesRealRepoServiceAndDurableRun exercises the real
|
|
20
|
+
// composition path with the production Beads factory and bd subprocess seam.
|
|
21
|
+
// The fake bd intentionally blocks mailbox discovery until the test observes
|
|
22
|
+
// the durable run, making claim -> run creation -> mailbox creation ordering
|
|
23
|
+
// explicit rather than inferred from timing.
|
|
24
|
+
func TestBeadsCompositionUsesRealRepoServiceAndDurableRun(t *testing.T) {
|
|
25
|
+
base := t.TempDir()
|
|
26
|
+
relayHome := filepath.Join(base, "relay-flow")
|
|
27
|
+
codePath := filepath.Join(base, "code", "payments")
|
|
28
|
+
beadsDir := filepath.Join(base, "beads", "payments", ".beads")
|
|
29
|
+
for _, dir := range []string{relayHome, codePath, beadsDir} {
|
|
30
|
+
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
31
|
+
t.Fatal(err)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
t.Setenv("RELAY_FLOW_HOME", relayHome)
|
|
35
|
+
|
|
36
|
+
logPath, releasePath, stateDir := installCompositionFakeBD(t, codePath, beadsDir)
|
|
37
|
+
|
|
38
|
+
p, err := home()
|
|
39
|
+
if err != nil {
|
|
40
|
+
t.Fatal(err)
|
|
41
|
+
}
|
|
42
|
+
if err := config.SaveMachine(p.Config, &config.Machine{
|
|
43
|
+
PollIntervalSeconds: 1,
|
|
44
|
+
CompletedRunRetentionDays: 1,
|
|
45
|
+
TaskPlugin: "beads",
|
|
46
|
+
RunnerPlugin: scenarioRunnerPlugin,
|
|
47
|
+
HarnessPlugin: scenarioHarnessPlugin,
|
|
48
|
+
Repos: map[string]config.Repo{},
|
|
49
|
+
}); err != nil {
|
|
50
|
+
t.Fatal(err)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
log := newScenarioLog()
|
|
54
|
+
rnr := newScenarioRunner(log)
|
|
55
|
+
hrn := newScenarioHarness(log)
|
|
56
|
+
// The scenario runner and harness are the existing replaceable seams used
|
|
57
|
+
// by the composition tests; only the task system is the real Beads adapter.
|
|
58
|
+
setScenarioFactoryAdapters(nil, rnr, hrn)
|
|
59
|
+
repoService := repo.NewService(repo.ServiceConfig{
|
|
60
|
+
ConfigPath: p.Config,
|
|
61
|
+
TaskPlugin: "beads",
|
|
62
|
+
Runner: rnr,
|
|
63
|
+
Harness: hrn,
|
|
64
|
+
Active: compositionNoActiveRuns{},
|
|
65
|
+
Workflows: compositionNoWorkflowRefs{},
|
|
66
|
+
})
|
|
67
|
+
if _, err := repoService.Register(context.Background(), repo.RegisterInput{
|
|
68
|
+
Name: "payments",
|
|
69
|
+
Path: codePath,
|
|
70
|
+
TaskConfig: config.RawValues{"beadsDir": beadsDir},
|
|
71
|
+
}); err != nil {
|
|
72
|
+
t.Fatalf("register Beads repo: %v", err)
|
|
73
|
+
}
|
|
74
|
+
// Registration probes the real adapter once. Start the composition
|
|
75
|
+
// assertions with a clean command trace and clean fake state.
|
|
76
|
+
if err := os.WriteFile(logPath, nil, 0o600); err != nil {
|
|
77
|
+
t.Fatal(err)
|
|
78
|
+
}
|
|
79
|
+
if err := os.RemoveAll(stateDir); err != nil {
|
|
80
|
+
t.Fatal(err)
|
|
81
|
+
}
|
|
82
|
+
if err := os.MkdirAll(stateDir, 0o700); err != nil {
|
|
83
|
+
t.Fatal(err)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if err := os.MkdirAll(p.Workflows, 0o700); err != nil {
|
|
87
|
+
t.Fatal(err)
|
|
88
|
+
}
|
|
89
|
+
store := &workflow.Store{Dir: p.Workflows}
|
|
90
|
+
if err := store.Put("beadsComposition", []byte(beadsCompositionWorkflowYAML)); err != nil {
|
|
91
|
+
t.Fatal(err)
|
|
92
|
+
}
|
|
93
|
+
if err := goworkflows.InitDatabase(p.Database); err != nil {
|
|
94
|
+
t.Fatal(err)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
ctx, cancel := context.WithCancel(context.Background())
|
|
98
|
+
done := make(chan error, 1)
|
|
99
|
+
go func() { done <- serveRoot(ctx, p, false) }()
|
|
100
|
+
|
|
101
|
+
released := false
|
|
102
|
+
cleanup := func() {
|
|
103
|
+
if !released {
|
|
104
|
+
_ = os.WriteFile(releasePath, []byte("release\n"), 0o600)
|
|
105
|
+
released = true
|
|
106
|
+
}
|
|
107
|
+
cancel()
|
|
108
|
+
select {
|
|
109
|
+
case <-done:
|
|
110
|
+
case <-time.After(15 * time.Second):
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
t.Cleanup(cleanup)
|
|
114
|
+
|
|
115
|
+
client := server.NewClient(p.Socket)
|
|
116
|
+
waitForServer(t, client)
|
|
117
|
+
|
|
118
|
+
var active runsvc.Run
|
|
119
|
+
waitScenario(t, 15*time.Second, func() bool {
|
|
120
|
+
var getErr error
|
|
121
|
+
active, getErr = client.GetRunByTicket(context.Background(), "demo-parent")
|
|
122
|
+
return getErr == nil
|
|
123
|
+
})
|
|
124
|
+
if active.ID == "" {
|
|
125
|
+
t.Fatal("durable run was not created after the Beads claim")
|
|
126
|
+
}
|
|
127
|
+
if err := appendCompositionLog(logPath, "test|run-observed\n"); err != nil {
|
|
128
|
+
t.Fatal(err)
|
|
129
|
+
}
|
|
130
|
+
lines := readCompositionLog(t, logPath)
|
|
131
|
+
readyIndex := compositionLogIndex(lines, "list --ready --no-parent --limit 0 --json")
|
|
132
|
+
claimIndex := compositionLogIndex(lines, "update demo-parent --add-label wf:beadsComposition --json")
|
|
133
|
+
runObservedIndex := compositionLogIndex(lines, "test|run-observed")
|
|
134
|
+
if readyIndex < 0 || claimIndex < 0 || runObservedIndex < 0 {
|
|
135
|
+
t.Fatalf("composition trace missing poll/claim/run events: %v", lines)
|
|
136
|
+
}
|
|
137
|
+
if !(readyIndex < claimIndex && claimIndex < runObservedIndex) {
|
|
138
|
+
t.Fatalf("poll -> claim -> run ordering = ready:%d claim:%d run:%d; trace=%v", readyIndex, claimIndex, runObservedIndex, lines)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if err := os.WriteFile(releasePath, []byte("release\n"), 0o600); err != nil {
|
|
142
|
+
t.Fatal(err)
|
|
143
|
+
}
|
|
144
|
+
released = true
|
|
145
|
+
waitScenario(t, 15*time.Second, func() bool {
|
|
146
|
+
current, getErr := client.GetRunByTicket(context.Background(), "demo-parent")
|
|
147
|
+
return getErr == nil && current.State == runsvc.StateWaiting && current.CurrentNode == "implement"
|
|
148
|
+
})
|
|
149
|
+
lines = readCompositionLog(t, logPath)
|
|
150
|
+
createImplementIndex := compositionLogIndex(lines, "create demo-parent:implement")
|
|
151
|
+
if createImplementIndex <= runObservedIndex {
|
|
152
|
+
t.Fatalf("mailbox creation did not follow run creation: run:%d create:%d; trace=%v", runObservedIndex, createImplementIndex, lines)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
current, err := client.GetRunByTicket(context.Background(), "demo-parent")
|
|
156
|
+
if err != nil {
|
|
157
|
+
t.Fatal(err)
|
|
158
|
+
}
|
|
159
|
+
if ack, err := client.SubmitReport(context.Background(), runsvc.ReportRequest{
|
|
160
|
+
RunID: current.ID,
|
|
161
|
+
Node: current.CurrentNode,
|
|
162
|
+
ReportID: string(current.CurrentNodeVisitID) + ":composition-implement",
|
|
163
|
+
Report: scenarioReport(workflow.OutcomeSuccess, "verify"),
|
|
164
|
+
}); err != nil || !ack.Accepted {
|
|
165
|
+
t.Fatalf("submit implement report: ack=%+v err=%v", ack, err)
|
|
166
|
+
}
|
|
167
|
+
waitScenario(t, 15*time.Second, func() bool {
|
|
168
|
+
current, getErr := client.GetRunByTicket(context.Background(), "demo-parent")
|
|
169
|
+
return getErr == nil && current.State == runsvc.StateWaiting && current.CurrentNode == "verify"
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
current, err = client.GetRunByTicket(context.Background(), "demo-parent")
|
|
173
|
+
if err != nil {
|
|
174
|
+
t.Fatal(err)
|
|
175
|
+
}
|
|
176
|
+
if ack, err := client.SubmitReport(context.Background(), runsvc.ReportRequest{
|
|
177
|
+
RunID: current.ID,
|
|
178
|
+
Node: current.CurrentNode,
|
|
179
|
+
ReportID: string(current.CurrentNodeVisitID) + ":composition-verify",
|
|
180
|
+
Report: scenarioReport(workflow.OutcomeSuccess, "end"),
|
|
181
|
+
}); err != nil || !ack.Accepted {
|
|
182
|
+
t.Fatalf("submit verify report: ack=%+v err=%v", ack, err)
|
|
183
|
+
}
|
|
184
|
+
waitScenario(t, 15*time.Second, func() bool {
|
|
185
|
+
finished, getErr := client.GetRunByTicket(context.Background(), "demo-parent")
|
|
186
|
+
return getErr == nil && finished.State == runsvc.StateCompleted
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
lines = readCompositionLog(t, logPath)
|
|
190
|
+
if compositionLogIndex(lines, "create demo-parent:verify") < 0 {
|
|
191
|
+
t.Fatalf("verify mailbox was not created: %v", lines)
|
|
192
|
+
}
|
|
193
|
+
comments := readCompositionState(t, stateDir, "comments.log")
|
|
194
|
+
for _, want := range []string{
|
|
195
|
+
"comment demo-parent.1",
|
|
196
|
+
"comment demo-parent.2",
|
|
197
|
+
"Summary for implement",
|
|
198
|
+
"Feedback from implement to verify mailbox demo-parent.2",
|
|
199
|
+
"Summary for verify",
|
|
200
|
+
} {
|
|
201
|
+
if !strings.Contains(comments, want) {
|
|
202
|
+
t.Fatalf("comments missing %q: %q", want, comments)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if strings.Contains(comments, "comment demo-parent ") {
|
|
206
|
+
t.Fatalf("summary/feedback was written to the parent: %q", comments)
|
|
207
|
+
}
|
|
208
|
+
if log.count("runner-repo-validated") == 0 || log.count("harness-agent-validated:implementer") == 0 {
|
|
209
|
+
t.Fatalf("existing runner/harness seams were not used: %v", log.all())
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if err := client.Stop(context.Background()); err != nil {
|
|
213
|
+
t.Fatal(err)
|
|
214
|
+
}
|
|
215
|
+
select {
|
|
216
|
+
case serveErr := <-done:
|
|
217
|
+
if serveErr != nil {
|
|
218
|
+
t.Fatalf("serveRoot: %v", serveErr)
|
|
219
|
+
}
|
|
220
|
+
case <-time.After(15 * time.Second):
|
|
221
|
+
t.Fatal("serveRoot did not stop")
|
|
222
|
+
}
|
|
223
|
+
released = true
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
func installCompositionFakeBD(t *testing.T, repoPath, beadsDir string) (logPath, releasePath, stateDir string) {
|
|
227
|
+
t.Helper()
|
|
228
|
+
binDir := t.TempDir()
|
|
229
|
+
logPath = filepath.Join(t.TempDir(), "bd.log")
|
|
230
|
+
releasePath = filepath.Join(t.TempDir(), "release")
|
|
231
|
+
stateDir = filepath.Join(t.TempDir(), "state")
|
|
232
|
+
if err := os.WriteFile(logPath, nil, 0o600); err != nil {
|
|
233
|
+
t.Fatal(err)
|
|
234
|
+
}
|
|
235
|
+
if err := os.MkdirAll(stateDir, 0o700); err != nil {
|
|
236
|
+
t.Fatal(err)
|
|
237
|
+
}
|
|
238
|
+
path := filepath.Join(binDir, "bd")
|
|
239
|
+
if err := os.WriteFile(path, []byte(compositionFakeBDScript), 0o755); err != nil {
|
|
240
|
+
t.Fatal(err)
|
|
241
|
+
}
|
|
242
|
+
t.Setenv("PATH", binDir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
|
243
|
+
t.Setenv("BD_LOG", logPath)
|
|
244
|
+
t.Setenv("BD_STATE", stateDir)
|
|
245
|
+
t.Setenv("BD_GATE_RELEASE", releasePath)
|
|
246
|
+
t.Setenv("BD_EXPECT_CWD", repoPath)
|
|
247
|
+
t.Setenv("BD_EXPECT_BEADS_DIR", beadsDir)
|
|
248
|
+
// These ambient selectors must be removed by bdcli.commandEnvironment.
|
|
249
|
+
t.Setenv("BEADS_DIR", "/ambient/workspace")
|
|
250
|
+
t.Setenv("BEADS_DB", "/ambient/beads.db")
|
|
251
|
+
t.Setenv("BD_DB", "/ambient/legacy.db")
|
|
252
|
+
return logPath, releasePath, stateDir
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
func appendCompositionLog(path, text string) error {
|
|
256
|
+
f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0o600)
|
|
257
|
+
if err != nil {
|
|
258
|
+
return err
|
|
259
|
+
}
|
|
260
|
+
defer f.Close()
|
|
261
|
+
_, err = f.WriteString(text)
|
|
262
|
+
return err
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
func readCompositionLog(t *testing.T, path string) []string {
|
|
266
|
+
t.Helper()
|
|
267
|
+
raw, err := os.ReadFile(path)
|
|
268
|
+
if err != nil {
|
|
269
|
+
t.Fatal(err)
|
|
270
|
+
}
|
|
271
|
+
return strings.Split(strings.TrimSpace(string(raw)), "\n")
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
func compositionLogIndex(lines []string, want string) int {
|
|
275
|
+
for i, line := range lines {
|
|
276
|
+
if strings.Contains(line, want) {
|
|
277
|
+
return i
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return -1
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
func readCompositionState(t *testing.T, dir, name string) string {
|
|
284
|
+
t.Helper()
|
|
285
|
+
raw, err := os.ReadFile(filepath.Join(dir, name))
|
|
286
|
+
if err != nil {
|
|
287
|
+
t.Fatal(err)
|
|
288
|
+
}
|
|
289
|
+
return string(raw)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
type compositionNoActiveRuns struct{}
|
|
293
|
+
|
|
294
|
+
func (compositionNoActiveRuns) HasActiveRepo(context.Context, string) (bool, error) {
|
|
295
|
+
return false, nil
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
type compositionNoWorkflowRefs struct{}
|
|
299
|
+
|
|
300
|
+
func (compositionNoWorkflowRefs) ReferencesRepo(string) bool { return false }
|
|
301
|
+
|
|
302
|
+
const beadsCompositionWorkflowYAML = `name: beadsComposition
|
|
303
|
+
repos: [payments]
|
|
304
|
+
cleanupRunnerOnEnd: true
|
|
305
|
+
nodes:
|
|
306
|
+
start:
|
|
307
|
+
onSuccess: [{target: implement}]
|
|
308
|
+
implement:
|
|
309
|
+
type: agent
|
|
310
|
+
agent: implementer
|
|
311
|
+
description: Implement the requested change.
|
|
312
|
+
onSuccess: [{target: verify}]
|
|
313
|
+
onFailure: [{target: implement, when: implementation cannot proceed}]
|
|
314
|
+
verify:
|
|
315
|
+
type: agent
|
|
316
|
+
agent: verifier
|
|
317
|
+
description: Verify the implementation.
|
|
318
|
+
onSuccess: [{target: end}]
|
|
319
|
+
onFailure: [{target: implement, when: verification fails}]
|
|
320
|
+
end: {}
|
|
321
|
+
`
|
|
322
|
+
|
|
323
|
+
const compositionFakeBDScript = `#!/bin/sh
|
|
324
|
+
set -eu
|
|
325
|
+
|
|
326
|
+
fail() {
|
|
327
|
+
printf 'unsupported or malformed composition bd invocation: %s\n' "$*" >&2
|
|
328
|
+
exit 64
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
[ -n "${BD_LOG-}" ] || fail "BD_LOG is missing"
|
|
332
|
+
[ -n "${BD_STATE-}" ] || fail "BD_STATE is missing"
|
|
333
|
+
[ -n "${BD_GATE_RELEASE-}" ] || fail "BD_GATE_RELEASE is missing"
|
|
334
|
+
[ "$PWD" = "${BD_EXPECT_CWD-}" ] || fail "wrong cwd: $PWD"
|
|
335
|
+
[ "${BEADS_DIR-}" = "${BD_EXPECT_BEADS_DIR-}" ] || fail "wrong BEADS_DIR: ${BEADS_DIR-}"
|
|
336
|
+
[ "${BEADS_DB+x}" != x ] || fail "ambient BEADS_DB leaked"
|
|
337
|
+
[ "${BD_DB+x}" != x ] || fail "ambient BD_DB leaked"
|
|
338
|
+
printf 'bd|%s\n' "$*" >> "$BD_LOG"
|
|
339
|
+
|
|
340
|
+
status_file() {
|
|
341
|
+
case "$1" in
|
|
342
|
+
demo-parent) printf '%s/parent.status' "$BD_STATE" ;;
|
|
343
|
+
demo-parent.1) printf '%s/implement.status' "$BD_STATE" ;;
|
|
344
|
+
demo-parent.2) printf '%s/verify.status' "$BD_STATE" ;;
|
|
345
|
+
*) fail "unknown issue $1" ;;
|
|
346
|
+
esac
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
issue_status() {
|
|
350
|
+
file=$(status_file "$1")
|
|
351
|
+
if [ -f "$file" ]; then
|
|
352
|
+
tr -d '\n' < "$file"
|
|
353
|
+
else
|
|
354
|
+
printf 'open'
|
|
355
|
+
fi
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
issue_json() {
|
|
359
|
+
issue=$1
|
|
360
|
+
status=$(issue_status "$issue")
|
|
361
|
+
case "$issue" in
|
|
362
|
+
demo-parent)
|
|
363
|
+
if [ -f "$BD_STATE/claimed" ]; then
|
|
364
|
+
labels='["wf:beadsComposition"]'
|
|
365
|
+
else
|
|
366
|
+
labels='[]'
|
|
367
|
+
fi
|
|
368
|
+
printf '[{"id":"demo-parent","title":"Composition parent","status":"%s","issue_type":"epic","priority":1,"labels":%s}]\n' "$status" "$labels"
|
|
369
|
+
;;
|
|
370
|
+
demo-parent.1)
|
|
371
|
+
printf '[{"id":"demo-parent.1","title":"demo-parent:implement","status":"%s","issue_type":"task","priority":2,"labels":["wf:beadsComposition"],"parent":"demo-parent"}]\n' "$status"
|
|
372
|
+
;;
|
|
373
|
+
demo-parent.2)
|
|
374
|
+
printf '[{"id":"demo-parent.2","title":"demo-parent:verify","status":"%s","issue_type":"task","priority":2,"labels":["wf:beadsComposition"],"parent":"demo-parent"}]\n' "$status"
|
|
375
|
+
;;
|
|
376
|
+
*) fail "unknown issue $issue" ;;
|
|
377
|
+
esac
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if [ "$#" -eq 6 ] && [ "$1" = list ] && [ "$2" = --ready ] && [ "$3" = --limit ] && [ "$4" = 1 ] && [ "$5" = --no-parent ] && [ "$6" = --json ]; then
|
|
381
|
+
printf '[]\n'
|
|
382
|
+
exit 0
|
|
383
|
+
fi
|
|
384
|
+
if [ "$#" -eq 6 ] && [ "$1" = list ] && [ "$2" = --ready ] && [ "$3" = --no-parent ] && [ "$4" = --limit ] && [ "$5" = 0 ] && [ "$6" = --json ]; then
|
|
385
|
+
if [ -f "$BD_STATE/claimed" ]; then
|
|
386
|
+
printf '[]\n'
|
|
387
|
+
else
|
|
388
|
+
printf '[{"id":"demo-parent","title":"Composition parent","status":"open","issue_type":"epic","priority":1,"labels":[]}]\n'
|
|
389
|
+
fi
|
|
390
|
+
exit 0
|
|
391
|
+
fi
|
|
392
|
+
if [ "$#" -eq 9 ] && [ "$1" = list ] && [ "$2" = --no-parent ] && [ "$3" = --status ] && [ "$4" = open,in_progress,blocked,deferred ] && [ "$5" = --label-pattern ] && [ "$6" = 'wf:*' ] && [ "$7" = --limit ] && [ "$8" = 0 ] && [ "$9" = --json ]; then
|
|
393
|
+
printf '[]\n'
|
|
394
|
+
exit 0
|
|
395
|
+
fi
|
|
396
|
+
if [ "$#" -eq 7 ] && [ "$1" = list ] && [ "$2" = --parent ] && [ "$3" = demo-parent ] && [ "$4" = --all ] && [ "$5" = --limit ] && [ "$6" = 0 ] && [ "$7" = --json ]; then
|
|
397
|
+
while [ ! -f "$BD_GATE_RELEASE" ]; do
|
|
398
|
+
sleep 0.01
|
|
399
|
+
done
|
|
400
|
+
out='['
|
|
401
|
+
sep=
|
|
402
|
+
if [ -f "$BD_STATE/implement" ]; then
|
|
403
|
+
out="${out}${sep}{\"id\":\"demo-parent.1\",\"title\":\"demo-parent:implement\",\"status\":\"$(issue_status demo-parent.1)\",\"issue_type\":\"task\",\"priority\":2,\"labels\":[\"wf:beadsComposition\"],\"parent\":\"demo-parent\"}"
|
|
404
|
+
sep=,
|
|
405
|
+
fi
|
|
406
|
+
if [ -f "$BD_STATE/verify" ]; then
|
|
407
|
+
out="${out}${sep}{\"id\":\"demo-parent.2\",\"title\":\"demo-parent:verify\",\"status\":\"$(issue_status demo-parent.2)\",\"issue_type\":\"task\",\"priority\":2,\"labels\":[\"wf:beadsComposition\"],\"parent\":\"demo-parent\"}"
|
|
408
|
+
fi
|
|
409
|
+
printf '%s]\n' "$out"
|
|
410
|
+
exit 0
|
|
411
|
+
fi
|
|
412
|
+
if [ "$#" -eq 3 ] && [ "$1" = show ] && [ "$3" = --json ]; then
|
|
413
|
+
issue_json "$2"
|
|
414
|
+
exit 0
|
|
415
|
+
fi
|
|
416
|
+
if [ "$#" -eq 11 ] && [ "$1" = create ] && [ "$3" = --type ] && [ "$4" = task ] && [ "$5" = --parent ] && [ "$7" = --no-inherit-labels ] && [ "$8" = --labels ] && [ "$9" = wf:beadsComposition ] && [ "${10}" = --stdin ] && [ "${11}" = --json ]; then
|
|
417
|
+
case "$2" in
|
|
418
|
+
demo-parent:implement) id=demo-parent.1; node=implement ;;
|
|
419
|
+
demo-parent:verify) id=demo-parent.2; node=verify ;;
|
|
420
|
+
*) fail "unknown child title $2" ;;
|
|
421
|
+
esac
|
|
422
|
+
cat > "$BD_STATE/$node.description"
|
|
423
|
+
: > "$BD_STATE/$node"
|
|
424
|
+
printf '%s\n' open > "$BD_STATE/$node.status"
|
|
425
|
+
printf '{"id":"%s","title":"%s","status":"open","issue_type":"task","priority":2,"labels":["wf:beadsComposition"],"parent":"demo-parent"}\n' "$id" "$2"
|
|
426
|
+
exit 0
|
|
427
|
+
fi
|
|
428
|
+
if [ "$#" -eq 5 ] && [ "$1" = update ] && [ "$2" = demo-parent ] && [ "$3" = --add-label ] && [ "$4" = wf:beadsComposition ] && [ "$5" = --json ]; then
|
|
429
|
+
: > "$BD_STATE/claimed"
|
|
430
|
+
printf '{}\n'
|
|
431
|
+
exit 0
|
|
432
|
+
fi
|
|
433
|
+
if [ "$#" -eq 5 ] && [ "$1" = update ] && [ "$3" = --status ] && [ "$5" = --json ]; then
|
|
434
|
+
file=$(status_file "$2")
|
|
435
|
+
printf '%s\n' "$4" > "$file"
|
|
436
|
+
printf '{}\n'
|
|
437
|
+
exit 0
|
|
438
|
+
fi
|
|
439
|
+
if [ "$#" -eq 3 ] && [ "$1" = comments ] && [ "$3" = --json ]; then
|
|
440
|
+
printf '[]\n'
|
|
441
|
+
exit 0
|
|
442
|
+
fi
|
|
443
|
+
if [ "$#" -eq 4 ] && [ "$1" = comment ] && [ "$3" = --stdin ] && [ "$4" = --json ]; then
|
|
444
|
+
printf 'comment %s\n' "$2" >> "$BD_STATE/comments.log"
|
|
445
|
+
cat >> "$BD_STATE/comments.log"
|
|
446
|
+
printf '\n---\n' >> "$BD_STATE/comments.log"
|
|
447
|
+
printf '{}\n'
|
|
448
|
+
exit 0
|
|
449
|
+
fi
|
|
450
|
+
fail "$@"
|
|
451
|
+
`
|