@exaudeus/workrail 3.46.0 → 3.48.0
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/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.js +3 -1
- package/dist/cli/commands/worktrain-trigger-test.d.ts +21 -0
- package/dist/cli/commands/worktrain-trigger-test.js +123 -0
- package/dist/cli-worktrain.js +65 -0
- package/dist/console-ui/assets/{index-BQFhoMcY.js → index-CecBgrR7.js} +1 -1
- package/dist/console-ui/index.html +1 -1
- package/dist/coordinators/modes/implement-shared.d.ts +2 -1
- package/dist/coordinators/modes/implement-shared.js +7 -3
- package/dist/manifest.json +44 -36
- package/dist/mcp/output-schemas.d.ts +2 -2
- package/dist/trigger/adapters/github-queue-poller.js +10 -7
- package/dist/trigger/github-queue-config.d.ts +1 -0
- package/dist/trigger/github-queue-config.js +9 -0
- package/dist/trigger/polling-scheduler.js +8 -1
- package/dist/trigger/trigger-listener.js +296 -1
- package/dist/trigger/trigger-router.d.ts +4 -2
- package/dist/trigger/trigger-router.js +19 -3
- package/dist/trigger/trigger-store.js +10 -0
- package/dist/trigger/types.d.ts +2 -0
- package/dist/v2/durable-core/schemas/artifacts/review-verdict.d.ts +5 -0
- package/dist/v2/durable-core/schemas/artifacts/review-verdict.js +12 -0
- package/docs/design/connect-adaptive-dispatch-candidates.md +153 -0
- package/docs/design/connect-adaptive-dispatch-design-review.md +88 -0
- package/docs/design/connect-adaptive-dispatch-implementation-plan.md +209 -0
- package/docs/design/queue-label-support-candidates.md +83 -0
- package/docs/design/queue-label-support-design-review.md +62 -0
- package/docs/design/queue-label-support-implementation-plan.md +158 -0
- package/docs/ideas/backlog.md +147 -0
- package/package.json +1 -1
- package/workflows/mr-review-workflow.agentic.v2.json +1 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Design Candidates: Label-Based Queue Filter for github_queue_poll
|
|
2
|
+
|
|
3
|
+
## Problem Understanding
|
|
4
|
+
|
|
5
|
+
### Tensions
|
|
6
|
+
1. **Trigger YAML config vs runtime config.json**: `queueType`/`queueLabel` appear in triggers.yml (per-trigger), but `GitHubQueueConfig` is loaded from `~/.workrail/config.json` (global daemon-level config). Both need to converge on the same data at poll time. The test surface validates at the poller level, which takes a `GitHubQueueConfig` directly - meaning the poller just needs to handle label type correctly, regardless of which source built the config.
|
|
7
|
+
|
|
8
|
+
2. **Additive field vs type-safe discriminated union**: Adding `queueLabel?: string` (optional) to `GitHubQueueConfig` makes the illegal state (`type==='label'` without `queueLabel`) detectable only at load time. A discriminated union would catch it at compile time. The 3-file scope constraint makes the discriminated union approach infeasible.
|
|
9
|
+
|
|
10
|
+
3. **Existing `name?` field vs new `queueLabel?` field**: `GitHubQueueConfig` already has `name?: string` (read from `q['name']` in `loadQueueConfig()`). Adding `queueLabel?` alongside it creates two optional label fields. This is a naming inconsistency from an earlier design iteration.
|
|
11
|
+
|
|
12
|
+
### Likely Seam
|
|
13
|
+
`pollGitHubQueueIssues()` in `github-queue-poller.ts` - the URL construction is where the actual behavior diverges between filter types.
|
|
14
|
+
|
|
15
|
+
### What Makes This Hard
|
|
16
|
+
- The existing test at line 126 (`returns not_implemented for non-assignee queue type`) tests that label returns `not_implemented`. After this change, label with `queueLabel` succeeds. This test must be replaced, not kept alongside new tests.
|
|
17
|
+
- `polling-scheduler.ts` has a guard at line 393 that still checks `queueConfig.type !== 'assignee'` - this file is out of scope. The scheduler will remain blocking for label type from config.json after our changes. Tests at the poller unit level bypass the scheduler.
|
|
18
|
+
|
|
19
|
+
## Philosophy Constraints
|
|
20
|
+
|
|
21
|
+
From `CLAUDE.md` and repo patterns:
|
|
22
|
+
- **Result types, no throws**: all boundary functions return `Result<T, E>`. `err({ kind: 'not_implemented', ... })` is the established pattern for unimplemented branches.
|
|
23
|
+
- **Validate at boundaries**: `loadQueueConfig()` must return `err` if `type === 'label'` and `queueLabel` is absent.
|
|
24
|
+
- **Immutability by default**: new fields must be `readonly`.
|
|
25
|
+
- **YAGNI**: no mention/query stubs, no new abstractions.
|
|
26
|
+
- **Make illegal states unrepresentable**: satisfied at load time by validation; compile-time enforcement would require discriminated union (out of scope).
|
|
27
|
+
|
|
28
|
+
No philosophy conflicts in this case.
|
|
29
|
+
|
|
30
|
+
## Impact Surface
|
|
31
|
+
|
|
32
|
+
- `polling-scheduler.ts` line 393: guard `queueConfig.type !== 'assignee'` remains. Out of scope. Label type from config.json will still be blocked by the scheduler even after our changes. This is an intentional stepping stone.
|
|
33
|
+
- `tests/unit/github-queue-poller.test.ts` line 126: existing test must be replaced/updated (not just added to).
|
|
34
|
+
- `src/trigger/types.ts`: `GitHubQueuePollingSource` needs `queueType?`/`queueLabel?` fields to store trigger YAML values. Not restricted by scope.
|
|
35
|
+
|
|
36
|
+
## Candidates
|
|
37
|
+
|
|
38
|
+
### Candidate 1: Additive optional field + load-time validation + poller branch (SELECTED)
|
|
39
|
+
|
|
40
|
+
**Summary**: Add `readonly queueLabel?: string` to `GitHubQueueConfig`, validate it in `loadQueueConfig()` (err if `type==='label'` and `queueLabel` absent), add `labels=` branch in `pollGitHubQueueIssues()`, parse `queueType`/`queueLabel` in trigger-store, extend `GitHubQueuePollingSource` in types.ts.
|
|
41
|
+
|
|
42
|
+
**Tensions resolved**: Label config is validated at load time; assignee path unchanged; no new abstractions.
|
|
43
|
+
**Tension accepted**: `name?` field remains alongside `queueLabel?` (minor naming inconsistency).
|
|
44
|
+
**Boundary**: `pollGitHubQueueIssues()` for URL construction; `loadQueueConfig()` for validation.
|
|
45
|
+
**Failure mode**: Forgetting to update the existing `not_implemented` test for label type. The test at line 126 will fail if not replaced.
|
|
46
|
+
**Repo pattern**: Follows exactly - mirrors how `user?` is handled for assignee type.
|
|
47
|
+
**Gain**: Minimal change surface, no breaking changes.
|
|
48
|
+
**Give up**: `name?` naming inconsistency stays.
|
|
49
|
+
**Scope**: best-fit.
|
|
50
|
+
**Philosophy fit**: Honors validate-at-boundaries, immutability, YAGNI, Result types.
|
|
51
|
+
|
|
52
|
+
### Candidate 2: Discriminated union for GitHubQueueConfig (type-safe)
|
|
53
|
+
|
|
54
|
+
**Summary**: Refactor `GitHubQueueConfig` into `AssigneeQueueConfig | LabelQueueConfig | MentionQueueConfig | QueryQueueConfig` so `queueLabel` is `string` (not optional) in `LabelQueueConfig`.
|
|
55
|
+
|
|
56
|
+
**Tensions resolved**: Makes illegal states unrepresentable at compile time.
|
|
57
|
+
**Tension accepted**: Breaks all existing callers; touches files outside scope.
|
|
58
|
+
**Boundary**: Entire `GitHubQueueConfig` interface plus all callers.
|
|
59
|
+
**Failure mode**: Cascading type errors in polling-scheduler.ts and tests.
|
|
60
|
+
**Repo pattern**: Departs - no discriminated union for config types in this codebase.
|
|
61
|
+
**Gain**: Compile-time safety.
|
|
62
|
+
**Give up**: Simplicity, scope compliance.
|
|
63
|
+
**Scope**: too broad.
|
|
64
|
+
**Philosophy fit**: Honors make-illegal-states-unrepresentable but conflicts with YAGNI and scope constraint.
|
|
65
|
+
|
|
66
|
+
## Comparison and Recommendation
|
|
67
|
+
|
|
68
|
+
**Selected: Candidate 1.**
|
|
69
|
+
|
|
70
|
+
All acceptance criteria are satisfied at minimum change surface. Load-time validation in `loadQueueConfig()` catches the illegal state (`type==='label'` without `queueLabel`) before any poll cycle runs. The `pollGitHubQueueIssues()` change is purely additive - the assignee branch is unchanged.
|
|
71
|
+
|
|
72
|
+
## Self-Critique
|
|
73
|
+
|
|
74
|
+
**Strongest counter-argument**: The `name?` field in `GitHubQueueConfig` was the original design for label support. Adding `queueLabel?` alongside it creates two ways to express the same concept. The cleaner fix would remove `name?` and replace it, but that's a breaking change to anyone who already uses `config.name` in code paths we're not touching (polling-scheduler at line 471 uses `queueConfig.type` but not `queueConfig.name`).
|
|
75
|
+
|
|
76
|
+
**Pivot conditions**: If end-to-end testing (not just unit tests) is required, we'd need to update `polling-scheduler.ts` too. That's out of scope per the task description.
|
|
77
|
+
|
|
78
|
+
**Invalidating assumption**: If `polling-scheduler.ts` is required for the feature to work at all, then the scope restriction is wrong. But the task explicitly says to verify with unit tests at the poller level.
|
|
79
|
+
|
|
80
|
+
## Open Questions for the Main Agent
|
|
81
|
+
|
|
82
|
+
1. Should the `name?` field be deprecated or removed? The task says add `queueLabel?` - leaving `name?` creates naming confusion. Recommend leaving it as-is and not removing it (YAGNI, out of scope).
|
|
83
|
+
2. Should `GitHubQueuePollingSource` in `types.ts` gain `queueType?`/`queueLabel?` fields? Yes - trigger-store.ts parses them from YAML and needs somewhere to store them.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Design Review: Label-Based Queue Filter for github_queue_poll
|
|
2
|
+
|
|
3
|
+
## Tradeoff Review
|
|
4
|
+
|
|
5
|
+
**T1: `name?` stays alongside `queueLabel?`**
|
|
6
|
+
- Acceptable. Load-time validation in `loadQueueConfig()` requires `queueLabel` when `type==='label'`. A user who writes `"name": "my-label"` in config.json gets an error at load time (queueLabel absent), not a silent failure.
|
|
7
|
+
- Fails if: user somehow bypasses `loadQueueConfig()` and constructs `GitHubQueueConfig` directly with `name` but not `queueLabel`. Poller returns `not_implemented` (else branch). Acceptable - direct construction is a test-only pattern.
|
|
8
|
+
|
|
9
|
+
**T2: `polling-scheduler.ts` guard remains blocking**
|
|
10
|
+
- Acceptable within task scope. Unit tests validate at the poller level. End-to-end production use requires a follow-up PR to update the scheduler guard. Document in PR description.
|
|
11
|
+
- Fails if: task author requires end-to-end production functionality. Risk level: medium for production, zero for acceptance criteria.
|
|
12
|
+
|
|
13
|
+
**T3: Optional `queueLabel?` (not required at type level)**
|
|
14
|
+
- Acceptable. Mitigated by load-time validation. The `pollGitHubQueueIssues` else branch returns `not_implemented` if `queueLabel` is absent, which is correct defensive behavior.
|
|
15
|
+
|
|
16
|
+
## Failure Mode Review
|
|
17
|
+
|
|
18
|
+
**FM1 (highest risk): Existing test at line 126 must be replaced**
|
|
19
|
+
- Test currently expects `not_implemented` for `{type: 'label', name: 'my-label'}`.
|
|
20
|
+
- After change: test will fail. Must replace with: (a) label success test, (b) label missing-queueLabel error test, (c) assignee regression test.
|
|
21
|
+
- Mitigation: explicit action in implementation plan.
|
|
22
|
+
|
|
23
|
+
**FM2: URL encoding**
|
|
24
|
+
- Handled automatically by `URLSearchParams.set()` (established pattern in codebase).
|
|
25
|
+
|
|
26
|
+
**FM3: GitHub API filter behavior**
|
|
27
|
+
- Not our responsibility. Tests verify the URL contains the correct `labels=` param.
|
|
28
|
+
|
|
29
|
+
**FM4: Scheduler guard**
|
|
30
|
+
- Filed as known limitation (T2 above). Document in PR.
|
|
31
|
+
|
|
32
|
+
## Runner-Up / Simpler Alternative Review
|
|
33
|
+
|
|
34
|
+
- Discriminated union: more type-safe but requires files outside scope. Nothing worth borrowing.
|
|
35
|
+
- Skipping trigger-store changes: fails acceptance criteria. Not viable.
|
|
36
|
+
- Skipping types.ts extension: TypeScript would flag unused parsed values. Necessary.
|
|
37
|
+
|
|
38
|
+
## Philosophy Alignment
|
|
39
|
+
|
|
40
|
+
- Result types: satisfied throughout
|
|
41
|
+
- Validate at boundaries: satisfied - `loadQueueConfig()` validates label requires `queueLabel`
|
|
42
|
+
- Immutability: satisfied - all new fields are `readonly`
|
|
43
|
+
- YAGNI: satisfied - no new abstractions
|
|
44
|
+
- Make illegal states unrepresentable: partially satisfied (load-time, not compile-time). Accepted tension.
|
|
45
|
+
|
|
46
|
+
## Findings
|
|
47
|
+
|
|
48
|
+
**YELLOW - polling-scheduler.ts guard**: Feature works at unit test level but not in production. The scheduler guard at line 393 (`queueConfig.type !== 'assignee'`) will still block label-type configs. This is a known, accepted, out-of-scope issue. Document in PR.
|
|
49
|
+
|
|
50
|
+
**YELLOW - test replacement**: The test at line 126 uses `{type: 'label', name: 'my-label'}` and expects `not_implemented`. This test MUST be replaced or it will fail after the change. High likelihood of causing CI failure if missed.
|
|
51
|
+
|
|
52
|
+
No RED findings.
|
|
53
|
+
|
|
54
|
+
## Recommended Revisions
|
|
55
|
+
|
|
56
|
+
1. In implementation: replace test at line 126 with three new tests (label success, label missing queueLabel, assignee regression).
|
|
57
|
+
2. In PR description: note that `polling-scheduler.ts` guard is a follow-up item.
|
|
58
|
+
|
|
59
|
+
## Residual Concerns
|
|
60
|
+
|
|
61
|
+
- `name?` field in `GitHubQueueConfig` is now superseded by `queueLabel?`. Future cleanup: remove `name?` and update any remaining references. Out of scope for this PR.
|
|
62
|
+
- No other residual concerns.
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Implementation Plan: Label-Based Queue Filter for github_queue_poll
|
|
2
|
+
|
|
3
|
+
## Problem Statement
|
|
4
|
+
|
|
5
|
+
The `github_queue_poll` trigger supports `queueType: label` and `queueLabel: "worktrain:ready"` in `triggers.yml`, but these fields are silently ignored. The `GitHubQueueConfig` type supports `type: 'label'` but throws `not_implemented` at runtime. Label-based queue filtering lets WorkTrain pick up issues without a dedicated bot account -- any issue labeled `worktrain:ready` becomes a candidate.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Acceptance Criteria
|
|
10
|
+
|
|
11
|
+
1. `pollGitHubQueueIssues()` with `config.type === 'label'` and `config.queueLabel === 'worktrain:ready'` sends `GET /repos/:owner/:repo/issues?state=open&labels=worktrain%3Aready&per_page=100`
|
|
12
|
+
2. `pollGitHubQueueIssues()` with `config.type === 'label'` and no `config.queueLabel` returns `err({ kind: 'not_implemented', ... })` (config validation error path)
|
|
13
|
+
3. `pollGitHubQueueIssues()` with `config.type === 'assignee'` still works (regression)
|
|
14
|
+
4. `loadQueueConfig()` with `type: 'label'` and no `queueLabel` field in config.json returns `err(...)` (not `ok`)
|
|
15
|
+
5. `loadQueueConfig()` with `type: 'label'` and `queueLabel: 'worktrain:ready'` returns `ok(config)` with `config.queueLabel === 'worktrain:ready'`
|
|
16
|
+
6. `trigger-store.ts` parses `queueType` and `queueLabel` from triggers.yml into `GitHubQueuePollingSource`
|
|
17
|
+
7. `npm run build` succeeds (no TypeScript errors)
|
|
18
|
+
8. `npx vitest run tests/unit/github-queue-poller.test.ts` -- all tests pass
|
|
19
|
+
9. `npx vitest run` -- no regressions
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Non-Goals
|
|
24
|
+
|
|
25
|
+
- Do not touch `src/mcp/`
|
|
26
|
+
- Do not touch `polling-scheduler.ts`
|
|
27
|
+
- Do not implement `mention` or `query` queue types
|
|
28
|
+
- Do not refactor surrounding code
|
|
29
|
+
- Do not remove the existing `name?` field from `GitHubQueueConfig`
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Philosophy-Driven Constraints
|
|
34
|
+
|
|
35
|
+
- All boundary functions return `Result<T, E>` -- no throws
|
|
36
|
+
- All new interface fields are `readonly`
|
|
37
|
+
- Validation at load time (`loadQueueConfig`): err if `type==='label'` and `queueLabel` absent
|
|
38
|
+
- Defensive else branch in `pollGitHubQueueIssues`: unknown types return `not_implemented`
|
|
39
|
+
- `encodeURIComponent` / URLSearchParams for URL safety
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Invariants
|
|
44
|
+
|
|
45
|
+
- I1: `pollGitHubQueueIssues` with `type='assignee'` and `config.user` sends `assignee=<user>` param (unchanged)
|
|
46
|
+
- I2: `pollGitHubQueueIssues` with `type='label'` and `config.queueLabel` sends `labels=<encoded>` param
|
|
47
|
+
- I3: `pollGitHubQueueIssues` with `type='label'` and no `config.queueLabel` returns `err({ kind: 'not_implemented' })`
|
|
48
|
+
- I4: `pollGitHubQueueIssues` with any other type returns `err({ kind: 'not_implemented' })`
|
|
49
|
+
- I5: `loadQueueConfig` with `type='label'` and no `queueLabel` key in config.json returns `err(string)`
|
|
50
|
+
- I6: No throws at any boundary
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Selected Approach
|
|
55
|
+
|
|
56
|
+
**Additive optional field + load-time validation + poller URL branch**
|
|
57
|
+
|
|
58
|
+
Four changes:
|
|
59
|
+
1. `GitHubQueueConfig` interface: add `readonly queueLabel?: string`
|
|
60
|
+
2. `loadQueueConfig()`: parse `q['queueLabel']`, validate it when `type==='label'`
|
|
61
|
+
3. `GitHubQueuePollingSource` (types.ts): add `readonly queueType?: string`, `readonly queueLabel?: string`
|
|
62
|
+
4. `trigger-store.ts`: parse `queueType`/`queueLabel` in `ParsedTriggerRaw` + `setTriggerField()` + assembly block
|
|
63
|
+
5. `pollGitHubQueueIssues()`: replace hard not_implemented guard with assignee/label/else branching
|
|
64
|
+
|
|
65
|
+
Runner-up was discriminated union -- rejected due to scope constraint and unnecessary complexity for this bounded change.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Vertical Slices
|
|
70
|
+
|
|
71
|
+
### Slice 1: `github-queue-config.ts` -- add `queueLabel` field + validation
|
|
72
|
+
**Files**: `src/trigger/github-queue-config.ts`
|
|
73
|
+
**What**: Add `readonly queueLabel?: string` to `GitHubQueueConfig` interface. In `loadQueueConfig()`, parse `q['queueLabel']` and add validation: if `rawType === 'label'` and `!queueLabel`, return `err('config.queue.queueLabel is required when type is "label"')`. Build the return object with `queueLabel` when present.
|
|
74
|
+
**Done when**: Interface has `queueLabel?`, validation fires correctly, `npm run build` clean.
|
|
75
|
+
|
|
76
|
+
### Slice 2: `types.ts` -- extend `GitHubQueuePollingSource`
|
|
77
|
+
**Files**: `src/trigger/types.ts`
|
|
78
|
+
**What**: Add `readonly queueType?: string` and `readonly queueLabel?: string` to `GitHubQueuePollingSource`.
|
|
79
|
+
**Done when**: Fields present in interface, build clean.
|
|
80
|
+
|
|
81
|
+
### Slice 3: `trigger-store.ts` -- parse `queueType`/`queueLabel` from YAML
|
|
82
|
+
**Files**: `src/trigger/trigger-store.ts`
|
|
83
|
+
**What**:
|
|
84
|
+
- Add `queueType?: string` and `queueLabel?: string` to `ParsedTriggerRaw` interface
|
|
85
|
+
- Handle them in `setTriggerField()` switch
|
|
86
|
+
- In the `github_queue_poll` assembly block, read `raw.queueType` and `raw.queueLabel` and include them in the `GitHubQueuePollingSource` object
|
|
87
|
+
**Done when**: `triggers.yml` `self-improvement` trigger parses correctly with these fields; build clean.
|
|
88
|
+
|
|
89
|
+
### Slice 4: `github-queue-poller.ts` -- implement label branch
|
|
90
|
+
**Files**: `src/trigger/adapters/github-queue-poller.ts`
|
|
91
|
+
**What**: Replace the current `if (config.type !== 'assignee') return err(not_implemented)` guard with:
|
|
92
|
+
```typescript
|
|
93
|
+
if (config.type === 'assignee' && config.user) {
|
|
94
|
+
url.searchParams.set('assignee', config.user);
|
|
95
|
+
} else if (config.type === 'label' && config.queueLabel) {
|
|
96
|
+
url.searchParams.set('labels', config.queueLabel);
|
|
97
|
+
} else {
|
|
98
|
+
return err({ kind: 'not_implemented', message: `Queue type "${config.type}" is not yet implemented` });
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
Remove the old `if (config.user)` block that was AFTER the guard.
|
|
102
|
+
Update JSDoc to reflect both supported types.
|
|
103
|
+
**Done when**: Function correctly handles both assignee and label, build clean.
|
|
104
|
+
|
|
105
|
+
### Slice 5: Tests -- update + add new tests
|
|
106
|
+
**Files**: `tests/unit/github-queue-poller.test.ts`
|
|
107
|
+
**What**:
|
|
108
|
+
- REPLACE existing test at line 126 (`returns not_implemented for non-assignee queue type`) -- this test currently expects not_implemented for `{type: 'label', name: 'my-label'}`. It MUST be replaced, not kept.
|
|
109
|
+
- ADD: `type: 'label'` with `queueLabel: 'worktrain:ready'` -> fetches with `labels=worktrain%3Aready` param
|
|
110
|
+
- ADD: `type: 'label'` without `queueLabel` -> returns err with kind not_implemented (or config validation path)
|
|
111
|
+
- KEEP as regression: `type: 'assignee'` test at line 105 (already tests assignee param)
|
|
112
|
+
- Update `makeConfig()` helper: `name?` field can stay but add examples with `queueLabel`
|
|
113
|
+
**Done when**: `npx vitest run tests/unit/github-queue-poller.test.ts` all pass.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Test Design
|
|
118
|
+
|
|
119
|
+
| Test | Expected behavior | Assertion |
|
|
120
|
+
|------|-------------------|-----------|
|
|
121
|
+
| label type + queueLabel | fetches with `labels=` param | URL contains `labels=worktrain%3Aready` |
|
|
122
|
+
| label type + no queueLabel | returns not_implemented | `result.error.kind === 'not_implemented'` |
|
|
123
|
+
| assignee type + user (regression) | fetches with `assignee=` param | URL contains `assignee=bob` |
|
|
124
|
+
|
|
125
|
+
Existing tests for network_error, http_error, rate_limit, field mapping, maturity, idempotency: unchanged.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Risk Register
|
|
130
|
+
|
|
131
|
+
| Risk | Probability | Impact | Mitigation |
|
|
132
|
+
|------|-------------|--------|------------|
|
|
133
|
+
| Forgetting to replace test at line 126 | High | CI fails | Explicit: replace that test first |
|
|
134
|
+
| `polling-scheduler.ts` guard still blocks end-to-end | Certain | Production feature blocked | Document in PR description as follow-up |
|
|
135
|
+
| URL encoding of `:` in `worktrain:ready` | Low | Wrong API call | Use `url.searchParams.set()` which encodes automatically |
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## PR Packaging Strategy
|
|
140
|
+
|
|
141
|
+
Single PR: `feat/queue-label-support`
|
|
142
|
+
Commit: `feat(trigger): implement label-based queue filter for github_queue_poll`
|
|
143
|
+
|
|
144
|
+
PR description should note: the `polling-scheduler.ts` guard at line 393 still checks `queueConfig.type !== 'assignee'` and will need a follow-up update for end-to-end production use. This PR implements the foundational layer.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Philosophy Alignment Per Slice
|
|
149
|
+
|
|
150
|
+
| Slice | Principle | Status |
|
|
151
|
+
|-------|-----------|--------|
|
|
152
|
+
| 1 (config) | validate-at-boundaries | satisfied: err if type=label and queueLabel absent |
|
|
153
|
+
| 1 (config) | immutability by default | satisfied: readonly field |
|
|
154
|
+
| 2 (types) | explicit domain types | satisfied: typed fields not raw strings |
|
|
155
|
+
| 3 (trigger-store) | validate-at-boundaries | satisfied: queueType parsed and stored |
|
|
156
|
+
| 4 (poller) | Result types, no throws | satisfied: err() return, no throw |
|
|
157
|
+
| 4 (poller) | exhaustiveness | tension: else branch catches unknowns but not exhaustive switch |
|
|
158
|
+
| 5 (tests) | prefer fakes over mocks | satisfied: injectable fetchFn mock |
|
package/docs/ideas/backlog.md
CHANGED
|
@@ -6481,3 +6481,150 @@ All five top-priority autonomous pipeline items shipped:
|
|
|
6481
6481
|
4. **Level 1 usage: run WorkTrain on its own backlog** -- Create `worktrain:ready` issues for the top 10 ready tasks, assign to `worktrain-etienneb`, observe one full queue → pipeline run. Collect data on misclassifications and weak PRs before designing the grooming loop.
|
|
6482
6482
|
|
|
6483
6483
|
5. **`worktrain inbox --watch`** -- Close the notification loop. Outbox exists, just needs the polling implementation.
|
|
6484
|
+
|
|
6485
|
+
---
|
|
6486
|
+
|
|
6487
|
+
## WorkTrain identity model: act as the user, not as a bot (Apr 20, 2026)
|
|
6488
|
+
|
|
6489
|
+
**Design decision:** WorkTrain acts as the configured user, not as a separate bot account.
|
|
6490
|
+
|
|
6491
|
+
### Why bot accounts are the wrong default
|
|
6492
|
+
|
|
6493
|
+
Most developers -- especially at companies -- cannot create separate bot GitHub accounts. Jira, GitLab, and other enterprise systems tie authentication to employee identity. Requiring a separate account creates friction that blocks adoption entirely.
|
|
6494
|
+
|
|
6495
|
+
WorkTrain's attribution signal is the **work pattern**, not the identity:
|
|
6496
|
+
- Branch name: `worktrain/<sessionId>` -- immediately recognizable
|
|
6497
|
+
- PR body footer: "🤖 Automated by WorkTrain" + session ID + workflow name
|
|
6498
|
+
- Commit co-author: `Co-Authored-By: WorkTrain <worktrain@noreply>`
|
|
6499
|
+
|
|
6500
|
+
Anyone reviewing a PR knows it was autonomous. The developer's name on the PR is not a lie -- they configured WorkTrain to do this work on their behalf.
|
|
6501
|
+
|
|
6502
|
+
### Queue membership without a bot account
|
|
6503
|
+
|
|
6504
|
+
Assignee-based opt-in only works with a dedicated bot account. Label-based opt-in works with any setup:
|
|
6505
|
+
- Apply `worktrain:ready` label to an issue → WorkTrain picks it up
|
|
6506
|
+
- The queue poll trigger uses `queueType: label` + `queueLabel: "worktrain:ready"`
|
|
6507
|
+
- No bot account, no special permissions, no friction
|
|
6508
|
+
|
|
6509
|
+
`workOnAll: true` (future) processes any open issue -- also requires no bot account.
|
|
6510
|
+
|
|
6511
|
+
### Token: use your own PAT
|
|
6512
|
+
|
|
6513
|
+
`$GITHUB_TOKEN` (your personal token) or a fine-grained PAT scoped to the target repo. WorkTrain uses it for API calls; the commit identity (`git user.name`, `git user.email`) is set separately in the worktree and can be whatever you want.
|
|
6514
|
+
|
|
6515
|
+
---
|
|
6516
|
+
|
|
6517
|
+
## Jira + GitLab integration for WorkTrain (Apr 20, 2026)
|
|
6518
|
+
|
|
6519
|
+
**Context:** Most enterprise developers use Jira for tickets and GitLab for code hosting. WorkTrain should work in this environment without requiring GitHub or a bot account.
|
|
6520
|
+
|
|
6521
|
+
### What exists
|
|
6522
|
+
|
|
6523
|
+
`gitlab_poll` trigger already exists -- polls GitLab MR list and dispatches sessions when new/updated MRs appear. WorkTrain can already do autonomous MR review on GitLab.
|
|
6524
|
+
|
|
6525
|
+
### What's missing
|
|
6526
|
+
|
|
6527
|
+
**`jira_poll` trigger:** Poll a Jira board/sprint/filter for issues in a specific status (e.g., "In Progress", "Ready for Dev") assigned to the configured user, and dispatch WorkTrain sessions for them. The developer labels Jira issues for WorkTrain the same way they'd assign to a teammate.
|
|
6528
|
+
|
|
6529
|
+
Proposed `jira_poll` config:
|
|
6530
|
+
```yaml
|
|
6531
|
+
- id: jira-queue
|
|
6532
|
+
provider: jira_poll
|
|
6533
|
+
jiraBaseUrl: https://zillow.atlassian.net
|
|
6534
|
+
token: $JIRA_API_TOKEN
|
|
6535
|
+
project: ACEI
|
|
6536
|
+
statusFilter: "Ready for Dev"
|
|
6537
|
+
assigneeFilter: "$JIRA_USERNAME"
|
|
6538
|
+
workspacePath: /path/to/repo
|
|
6539
|
+
branchStrategy: worktree
|
|
6540
|
+
autoCommit: true
|
|
6541
|
+
autoOpenPR: true
|
|
6542
|
+
agentConfig:
|
|
6543
|
+
maxSessionMinutes: 90
|
|
6544
|
+
```
|
|
6545
|
+
|
|
6546
|
+
**GitLab issue queue:** Same as `github_queue_poll` but for GitLab issues. Dispatch coding sessions for GitLab issues labeled `worktrain` or in a specific milestone.
|
|
6547
|
+
|
|
6548
|
+
### Implementation notes
|
|
6549
|
+
|
|
6550
|
+
- `jira_poll` follows the same `PollingSource` discriminated union pattern as `gitlab_poll` and `github_queue_poll`
|
|
6551
|
+
- Jira REST API v3: `GET /rest/api/3/search?jql=project=X+AND+status="Ready for Dev"+AND+assignee=currentUser()`
|
|
6552
|
+
- Token: Jira API token (not OAuth -- simpler for developer tools)
|
|
6553
|
+
- `jira_poll` should extract issue title + description as the goal, and the Jira issue URL as `upstreamSpecUrl` in `TaskCandidate`
|
|
6554
|
+
|
|
6555
|
+
### Priority
|
|
6556
|
+
|
|
6557
|
+
Medium. GitLab MR review already works. Jira issue queue is the next most impactful integration for enterprise users. Design alongside the label-based GitHub queue -- the patterns are identical, just different API shapes.
|
|
6558
|
+
|
|
6559
|
+
---
|
|
6560
|
+
|
|
6561
|
+
## Queue opt-in design: unresolved decisions (Apr 20, 2026)
|
|
6562
|
+
|
|
6563
|
+
**Status: DO NOT IMPLEMENT until these questions are answered.**
|
|
6564
|
+
|
|
6565
|
+
The self-improvement queue was partially implemented using label-based opt-in, then later walked back. This section records what's actually unresolved so future work starts from the right place.
|
|
6566
|
+
|
|
6567
|
+
### What's wrong with the current state
|
|
6568
|
+
|
|
6569
|
+
The `github_queue_poll` trigger now supports both `assignee` and `label` queue types. The code is correct. But `triggers.yml` has no active queue trigger because the opt-in mechanism isn't settled -- see below.
|
|
6570
|
+
|
|
6571
|
+
The label approach was implemented as a practical fallback when "no bot account" ruled out assignee-based. But labels were what we explicitly rejected in the original design because they require humans to apply them per issue. Reversing that decision without acknowledging it was a mistake. The right answer isn't to pick one mechanism -- it's to keep the queue shape configurable (which we already designed) and pick the right shape per context.
|
|
6572
|
+
|
|
6573
|
+
### The configurable queue shape (already designed, partially implemented)
|
|
6574
|
+
|
|
6575
|
+
```
|
|
6576
|
+
{ "queue": { "type": "github_assignee", "user": "worktrain-etienneb" } }
|
|
6577
|
+
{ "queue": { "type": "github_label", "name": "worktrain:ready" } }
|
|
6578
|
+
{ "queue": { "type": "github_query", "search": "is:issue is:open ..." } }
|
|
6579
|
+
{ "queue": { "type": "jql", "query": "assignee=currentUser() AND status='Ready for Dev'" } }
|
|
6580
|
+
{ "queue": { "type": "gitlab_label", "name": "worktrain" } }
|
|
6581
|
+
```
|
|
6582
|
+
|
|
6583
|
+
For the workrail repo specifically: either `github_assignee` (accept the conflation between your personal assignments and WorkTrain's queue -- fine for a solo repo) or `github_label` (apply label per issue -- more discipline, more friction). Neither is wrong; pick based on preference.
|
|
6584
|
+
|
|
6585
|
+
### Enterprise implications that must be resolved before Zillow work
|
|
6586
|
+
|
|
6587
|
+
Three questions for the user to verify before designing any Zillow path:
|
|
6588
|
+
|
|
6589
|
+
1. **Service account process**: Does Zillow have a ServiceDesk or security review process for requesting service accounts (`worktrain-etienneb@zillow`)? If yes, request one through proper channels rather than acting under your personal identity.
|
|
6590
|
+
|
|
6591
|
+
2. **AUP check**: Does Zillow's Acceptable Use Policy permit automation acting under employee identities without an explicit security review? If not, "WorkTrain acts as you" is not viable -- a service account is required.
|
|
6592
|
+
|
|
6593
|
+
3. **Self-approval rules**: Can you approve your own MRs in Zillow's GitLab? If "no self-approval" is enforced, every WorkTrain MR needs a human reviewer. That changes the pipeline (no auto-merge under personal identity).
|
|
6594
|
+
|
|
6595
|
+
These three answers determine the entire architecture for Zillow. Do not design the Jira/GitLab path until they are known.
|
|
6596
|
+
|
|
6597
|
+
### Enterprise identity risk (important)
|
|
6598
|
+
|
|
6599
|
+
"WorkTrain acts as you" is different from "Dependabot acts as you." Dependabot does narrow, predictable operations (dependency bumps). WorkTrain does arbitrary LLM-driven code changes. Every autonomous action -- MR opened, commit pushed, comment posted -- is attributed to you in audit logs. If WorkTrain does something wrong under your identity, the audit trail points to you. Understand this risk before turning on autonomy against company repos.
|
|
6600
|
+
|
|
6601
|
+
### Jira return path (missing from current jira_poll design)
|
|
6602
|
+
|
|
6603
|
+
The `jira_poll` backlog entry describes pulling tickets from Jira. It does not describe writing back:
|
|
6604
|
+
- Moving the ticket to "In Review" when an MR is opened
|
|
6605
|
+
- Adding the MR URL to the Jira ticket (a Jira field or comment)
|
|
6606
|
+
- Reacting to Jira transitions mid-work (ticket moved back to "To Do" → WorkTrain stops)
|
|
6607
|
+
|
|
6608
|
+
The full Jira integration is a round-trip, not just a poll. Design the return path before implementing `jira_poll`.
|
|
6609
|
+
|
|
6610
|
+
---
|
|
6611
|
+
|
|
6612
|
+
## Gate 2 follow-up: per-trigger gh CLI token for delivery (Apr 20, 2026)
|
|
6613
|
+
|
|
6614
|
+
`delivery-action.ts` calls `gh pr create` using whatever `gh` CLI auth is configured globally -- it does not pass a per-trigger token. For single-identity (always acting as yourself) this is fine. For multi-identity (Zillow service account alongside personal trigger), the globally authenticated `gh` user handles all PR creation, silently using the wrong identity.
|
|
6615
|
+
|
|
6616
|
+
**Fix when multi-identity is needed:** Pass `GH_TOKEN=<triggerToken>` env override to `execFn` when calling `gh pr create` and `gh pr merge`. Not a blocker for single-identity. Prerequisite for multi-identity support.
|
|
6617
|
+
|
|
6618
|
+
---
|
|
6619
|
+
|
|
6620
|
+
## Queue config discriminated union tightening (Apr 20, 2026)
|
|
6621
|
+
|
|
6622
|
+
`GitHubQueueConfig` uses a flat interface with runtime validation. Should be a proper TypeScript discriminated union so `type: 'assignee'` requires `user` at compile time. Low priority but tracked per "make illegal states unrepresentable."
|
|
6623
|
+
|
|
6624
|
+
---
|
|
6625
|
+
|
|
6626
|
+
## Kill switch and commit signing (Apr 20, 2026)
|
|
6627
|
+
|
|
6628
|
+
**Kill switch:** `worktrain kill-sessions` -- aborts all running daemon sessions immediately. Useful when WorkTrain is doing something unexpected. Sends abort signal to all active sessions, marks them user-killed in the event log.
|
|
6629
|
+
|
|
6630
|
+
**Commit signing:** verify `git commit` honors existing `commit.gpgsign` config, or add explicit opt-out for bot identities that don't have signing keys. Empirically verify before declaring this solved.
|
package/package.json
CHANGED
|
@@ -312,7 +312,7 @@
|
|
|
312
312
|
{
|
|
313
313
|
"id": "phase-6-final-handoff",
|
|
314
314
|
"title": "Phase 6: Final Handoff",
|
|
315
|
-
"prompt": "Provide the final MR review handoff.\n\nInclude:\n- MR title and purpose\n- review mode used\n- final recommendation and confidence band\n- confidence assessment summary, including the most important reason confidence was capped if it was not High\n- counts of Critical / Major / Minor / Nit findings\n- top findings with rationale\n- strongest remaining areas of uncertainty, if any\n- summary of the coverage ledger, especially any still-uncertain domains\n- ready-to-post MR comments summary\n- any validation outcomes a human reviewer should see\n- review environment status:\n - what review target/context sources were successfully used\n - what important sources were missing or ambiguous\n - boundary confidence and context confidence\n - how those limits affected the review\n- path to the full human-facing review artifact (`reviewDocPath`) only if one was created\n\nRules:\n- the final recommendation assists a human reviewer; it does not replace them\n- if `reviewDocPath` exists, treat it as a human-facing companion artifact only\n- be explicit when missing PR/ticket/doc/boundary context limited confidence\n- do not post comments, approve, reject, or merge unless the user explicitly asks\n\nIMPORTANT: After writing your notes, emit a structured verdict via complete_step's artifacts[] parameter using EXACTLY this schema (no extra fields):\n{\n \"kind\": \"wr.review_verdict\",\n \"verdict\": \"clean\" | \"minor\" | \"blocking\",\n \"confidence\": \"high\" | \"medium\" | \"low\",\n \"findings\": [ { \"severity\": \"critical\" | \"major\" | \"minor\" | \"nit\", \"summary\": \"one-line description\" } ],\n \"summary\": \"one-line overall verdict summary\"\n}\nFor a clean review with no findings, use findings: []. The verdict field maps to severity: clean = no blocking issues, minor = small issues only, blocking = critical or major issues found.",
|
|
315
|
+
"prompt": "Provide the final MR review handoff.\n\nInclude:\n- MR title and purpose\n- review mode used\n- final recommendation and confidence band\n- confidence assessment summary, including the most important reason confidence was capped if it was not High\n- counts of Critical / Major / Minor / Nit findings\n- top findings with rationale\n- strongest remaining areas of uncertainty, if any\n- summary of the coverage ledger, especially any still-uncertain domains\n- ready-to-post MR comments summary\n- any validation outcomes a human reviewer should see\n- review environment status:\n - what review target/context sources were successfully used\n - what important sources were missing or ambiguous\n - boundary confidence and context confidence\n - how those limits affected the review\n- path to the full human-facing review artifact (`reviewDocPath`) only if one was created\n\nRules:\n- the final recommendation assists a human reviewer; it does not replace them\n- if `reviewDocPath` exists, treat it as a human-facing companion artifact only\n- be explicit when missing PR/ticket/doc/boundary context limited confidence\n- do not post comments, approve, reject, or merge unless the user explicitly asks\n\nIMPORTANT: After writing your notes, emit a structured verdict via complete_step's artifacts[] parameter using EXACTLY this schema (no extra fields):\n{\n \"kind\": \"wr.review_verdict\",\n \"verdict\": \"clean\" | \"minor\" | \"blocking\",\n \"confidence\": \"high\" | \"medium\" | \"low\",\n \"findings\": [ { \"severity\": \"critical\" | \"major\" | \"minor\" | \"nit\", \"summary\": \"one-line description\", \"findingCategory\": \"correctness\" | \"security\" | \"architecture\" | \"ux\" | \"performance\" | \"testing\" | \"style\" } ],\n \"summary\": \"one-line overall verdict summary\"\n}\nFor a clean review with no findings, use findings: []. The verdict field maps to severity: clean = no blocking issues, minor = small issues only, blocking = critical or major issues found. For findingCategory use: correctness = wrong behavior/logic errors, security = auth/authz issues/injection/data exposure, architecture = wrong abstraction/tight coupling/invariant violations, ux = usability/accessibility/interaction design, performance = inefficiency/N+1/blocking operations, testing = missing tests/wrong test approach, style = naming/formatting/conventions.",
|
|
316
316
|
"outputContract": {
|
|
317
317
|
"contractRef": "wr.contracts.review_verdict",
|
|
318
318
|
"required": false
|