relay-flow 0.2.3-alpha → 0.2.4-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.
Files changed (55) hide show
  1. package/README.md +116 -10
  2. package/cmd/relay-flow/main.go +2 -0
  3. package/cmd/relay-flow/pi_wiring_test.go +64 -0
  4. package/cmd/relay-flow/serve.go +2 -0
  5. package/examples/beads-workflow.yaml +3 -3
  6. package/examples/config-reference.yaml +144 -0
  7. package/examples/minimal-beads-task-workflow.yaml +34 -0
  8. package/examples/minimal-jira-task-workflow.yaml +68 -0
  9. package/examples/workflow-reference.yaml +111 -0
  10. package/internal/harness/opencode/opencode_test.go +1 -1
  11. package/internal/harness/opencode/repo_setup.go +1 -1
  12. package/internal/harness/pi/config_test.go +46 -0
  13. package/internal/harness/pi/lifecycle_test.go +69 -0
  14. package/internal/harness/pi/pi.go +261 -0
  15. package/internal/harness/pi/pi_test.go +322 -0
  16. package/internal/harness/pi/prompt_test.go +121 -0
  17. package/internal/harness/pi/testdata/pi-0.84.1/capture.json +126 -0
  18. package/internal/harness/pi/testdata/pi-0.84.1/noninteractive-output.txt +11 -0
  19. package/internal/harness/pi/testdata/pi-0.84.1/tui-output-sanitized.txt +17 -0
  20. package/internal/harness/pi/validation_test.go +168 -0
  21. package/internal/runner/herdr/baseref.go +60 -0
  22. package/internal/runner/herdr/herdr.go +578 -0
  23. package/internal/runner/herdr/herdr_test.go +688 -0
  24. package/internal/runner/herdr/herdrcli/contract.go +128 -0
  25. package/internal/runner/herdr/herdrcli/exec.go +68 -0
  26. package/internal/runner/herdr/herdrcli/herdrcli_test.go +274 -0
  27. package/internal/runner/herdr/herdrcli/live_test.go +132 -0
  28. package/internal/runner/herdr/herdrcli/operations.go +281 -0
  29. package/internal/runner/herdr/herdrcli/response.go +116 -0
  30. package/internal/runner/herdr/herdrcli/testdata/empty-panes.json +1 -0
  31. package/internal/runner/herdr/herdrcli/testdata/empty-tabs.json +1 -0
  32. package/internal/runner/herdr/herdrcli/testdata/error-not-git-worktree.json +1 -0
  33. package/internal/runner/herdr/herdrcli/testdata/error-pane-not-found.json +1 -0
  34. package/internal/runner/herdr/herdrcli/testdata/error-workspace-not-found.json +1 -0
  35. package/internal/runner/herdr/herdrcli/testdata/error-worktree-not-found.json +1 -0
  36. package/internal/runner/herdr/herdrcli/testdata/malformed.json +1 -0
  37. package/internal/runner/herdr/herdrcli/testdata/pane-close.json +6 -0
  38. package/internal/runner/herdr/herdrcli/testdata/pane-get.json +25 -0
  39. package/internal/runner/herdr/herdrcli/testdata/pane-list.json +45 -0
  40. package/internal/runner/herdr/herdrcli/testdata/pane-process-info-shell.json +22 -0
  41. package/internal/runner/herdr/herdrcli/testdata/pane-process-info.json +23 -0
  42. package/internal/runner/herdr/herdrcli/testdata/pane-rename.json +23 -0
  43. package/internal/runner/herdr/herdrcli/testdata/snapshot.json +213 -0
  44. package/internal/runner/herdr/herdrcli/testdata/strict-herdr.sh +175 -0
  45. package/internal/runner/herdr/herdrcli/testdata/tab-create.json +31 -0
  46. package/internal/runner/herdr/herdrcli/testdata/tab-list.json +26 -0
  47. package/internal/runner/herdr/herdrcli/testdata/workspace-close.json +6 -0
  48. package/internal/runner/herdr/herdrcli/testdata/worktree-create.json +58 -0
  49. package/internal/runner/herdr/herdrcli/testdata/worktree-list.json +35 -0
  50. package/internal/runner/herdr/herdrcli/testdata/worktree-open.json +59 -0
  51. package/internal/task/beads/beads.go +0 -16
  52. package/internal/task/beads/config_compatibility_test.go +7 -8
  53. package/internal/task/jira/filters_test.go +32 -6
  54. package/internal/task/jira/jira.go +27 -17
  55. package/package.json +1 -1
@@ -232,6 +232,33 @@ func TestCompileFilterAssigneeMatchesEmail(t *testing.T) {
232
232
  }
233
233
  }
234
234
 
235
+ func TestCompileFilterCurrentUserMatchesAuthenticatedJiraEmail(t *testing.T) {
236
+ sys := newSystemWithFake(t, &fakeJira{}).(*system)
237
+ sys.currentUser = "me@example.com"
238
+ match, err := sys.CompileFilter(config.RawValues{
239
+ "filters": map[string]any{"assignees": []any{"currentUser()"}},
240
+ })
241
+ if err != nil {
242
+ t.Fatalf("CompileFilter failed: %v", err)
243
+ }
244
+ if !match(task.Ticket{Key: "PAY-1", Fields: map[string]any{"assignee": "ME@EXAMPLE.COM"}}) {
245
+ t.Fatal("currentUser() did not resolve to the authenticated Jira email")
246
+ }
247
+ if match(task.Ticket{Key: "PAY-2", Fields: map[string]any{"assignee": "other@example.com"}}) {
248
+ t.Fatal("currentUser() matched another Jira assignee")
249
+ }
250
+ }
251
+
252
+ func TestCompileFilterCurrentUserRequiresCredentials(t *testing.T) {
253
+ sys := newSystemWithFake(t, &fakeJira{})
254
+ _, err := sys.CompileFilter(config.RawValues{
255
+ "filters": map[string]any{"assignees": []any{"currentUser()"}},
256
+ })
257
+ if err == nil || !strings.Contains(err.Error(), "authenticated Jira credentials") {
258
+ t.Fatalf("CompileFilter error = %v, want authenticated-credentials error", err)
259
+ }
260
+ }
261
+
235
262
  func TestCompileFilterAssigneeMatchAndMismatch(t *testing.T) {
236
263
  // Assignee is a supported structured filter dimension
237
264
  // (repo-workflow-routing: "parent statuses, issue types, labels, and
@@ -253,7 +280,7 @@ func TestCompileFilterAssigneeMatchAndMismatch(t *testing.T) {
253
280
  }
254
281
  }
255
282
 
256
- func TestCompileFilterInheritsEffectiveAssigneeCaseInsensitively(t *testing.T) {
283
+ func TestCompileFilterDoesNotUseEffectiveAssigneeAsFilter(t *testing.T) {
257
284
  sys, err := newSystem(context.Background(), &fakeClient{fake: &fakeJira{}}, task.RepoSpec{
258
285
  Name: "payments",
259
286
  RootConfig: config.RawValues{"assignee": "root@example.com"},
@@ -266,11 +293,10 @@ func TestCompileFilterInheritsEffectiveAssigneeCaseInsensitively(t *testing.T) {
266
293
  if err != nil {
267
294
  t.Fatal(err)
268
295
  }
269
- if !match(task.Ticket{Fields: map[string]any{"assignee": "repo.bot@example.COM"}}) {
270
- t.Fatal("effective repo assignee did not match normalized email case-insensitively")
271
- }
272
- if match(task.Ticket{Fields: map[string]any{"assignee": "other@example.com"}}) {
273
- t.Fatal("ticket assigned to another user matched effective assignee")
296
+ for _, assignee := range []string{"root@example.com", "repo.bot@example.COM", "other@example.com"} {
297
+ if !match(task.Ticket{Fields: map[string]any{"assignee": assignee}}) {
298
+ t.Fatalf("assignee %q was filtered without filters.assignees", assignee)
299
+ }
274
300
  }
275
301
  }
276
302
 
@@ -53,6 +53,7 @@ const (
53
53
  defaultStartParentStatus = "In Progress"
54
54
  defaultWorkTaskStatus = "In Progress"
55
55
  defaultEndParentStatus = "Done"
56
+ currentUserAssigneeFilter = "currentUser()"
56
57
  defaultMailboxDescription = `Parent ticket: {{ticket}}
57
58
  Workflow: {{workflow}}
58
59
  Node: {{node}}
@@ -147,7 +148,12 @@ func init() {
147
148
  if err != nil {
148
149
  return nil, err
149
150
  }
150
- return newSystem(ctx, client, spec)
151
+ sys, err := newSystem(ctx, client, spec)
152
+ if err != nil {
153
+ return nil, err
154
+ }
155
+ sys.currentUser = strings.TrimSpace(creds.Email)
156
+ return sys, nil
151
157
  },
152
158
  })
153
159
  }
@@ -173,10 +179,11 @@ func sharedClient(site, email, token string) (*jirarest.HTTPClient, error) {
173
179
  // system is the repo-bound Jira task.System. It is safe for concurrent use;
174
180
  // the REST client owns connection reuse, caches, and request limiting.
175
181
  type system struct {
176
- cli jirarest.Client
177
- repoName string
178
- base config.RawValues
179
- effective Config // root+repo merged
182
+ cli jirarest.Client
183
+ repoName string
184
+ currentUser string
185
+ base config.RawValues
186
+ effective Config // root+repo merged
180
187
  }
181
188
 
182
189
  func newSystem(ctx context.Context, cli jirarest.Client, spec task.RepoSpec) (*system, error) {
@@ -323,9 +330,11 @@ func (s *system) CompileFilter(workflowTaskConfig config.RawValues) (func(task.T
323
330
  return nil, err
324
331
  }
325
332
  f := cfg.Filters
326
- if !hasAssigneeFilter(merged) && cfg.Assignee != "" {
327
- f.Assignees = []string{cfg.Assignee}
333
+ resolvedAssignees, err := s.resolveAssigneeFilters(f.Assignees)
334
+ if err != nil {
335
+ return nil, err
328
336
  }
337
+ f.Assignees = resolvedAssignees
329
338
  return func(t task.Ticket) bool {
330
339
  if len(f.ParentStatuses) > 0 && !contains(f.ParentStatuses, strField(t.Fields, "status")) {
331
340
  return false
@@ -348,17 +357,18 @@ func (s *system) CompileFilter(workflowTaskConfig config.RawValues) (func(task.T
348
357
  }, nil
349
358
  }
350
359
 
351
- func hasAssigneeFilter(raw config.RawValues) bool {
352
- switch filters := raw["filters"].(type) {
353
- case map[string]any:
354
- _, ok := filters["assignees"]
355
- return ok
356
- case config.RawValues:
357
- _, ok := filters["assignees"]
358
- return ok
359
- default:
360
- return false
360
+ func (s *system) resolveAssigneeFilters(values []string) ([]string, error) {
361
+ resolved := make([]string, 0, len(values))
362
+ for _, value := range values {
363
+ if value == currentUserAssigneeFilter {
364
+ if s.currentUser == "" {
365
+ return nil, fmt.Errorf("jira: filters.assignees value %q requires authenticated Jira credentials", currentUserAssigneeFilter)
366
+ }
367
+ value = s.currentUser
368
+ }
369
+ resolved = append(resolved, value)
361
370
  }
371
+ return resolved, nil
362
372
  }
363
373
 
364
374
  // --- Claim ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relay-flow",
3
- "version": "0.2.3-alpha",
3
+ "version": "0.2.4-alpha",
4
4
  "description": "Graph-based agent workflow engine — tracker-agnostic, pluggable runners",
5
5
  "bin": {
6
6
  "relay-flow": "./bin/relay-flow.js",