@exaudeus/workrail 3.45.0 → 3.47.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.
Files changed (47) hide show
  1. package/dist/cli/commands/index.d.ts +1 -0
  2. package/dist/cli/commands/index.js +3 -1
  3. package/dist/cli/commands/worktrain-pipeline.d.ts +17 -0
  4. package/dist/cli/commands/worktrain-pipeline.js +121 -0
  5. package/dist/console-ui/assets/{index-BpanIvmi.js → index-B77l3WBR.js} +1 -1
  6. package/dist/console-ui/index.html +1 -1
  7. package/dist/coordinators/adaptive-pipeline.d.ts +57 -0
  8. package/dist/coordinators/adaptive-pipeline.js +104 -0
  9. package/dist/coordinators/modes/full-pipeline.d.ts +4 -0
  10. package/dist/coordinators/modes/full-pipeline.js +256 -0
  11. package/dist/coordinators/modes/implement-shared.d.ts +5 -0
  12. package/dist/coordinators/modes/implement-shared.js +205 -0
  13. package/dist/coordinators/modes/implement.d.ts +3 -0
  14. package/dist/coordinators/modes/implement.js +108 -0
  15. package/dist/coordinators/modes/quick-review.d.ts +3 -0
  16. package/dist/coordinators/modes/quick-review.js +37 -0
  17. package/dist/coordinators/modes/review-only.d.ts +2 -0
  18. package/dist/coordinators/modes/review-only.js +28 -0
  19. package/dist/coordinators/routing/route-task.d.ts +21 -0
  20. package/dist/coordinators/routing/route-task.js +55 -0
  21. package/dist/manifest.json +107 -35
  22. package/dist/mcp/output-schemas.d.ts +16 -16
  23. package/dist/trigger/adapters/github-queue-poller.js +10 -7
  24. package/dist/trigger/github-queue-config.d.ts +1 -0
  25. package/dist/trigger/github-queue-config.js +9 -0
  26. package/dist/trigger/polling-scheduler.js +8 -1
  27. package/dist/trigger/trigger-listener.js +296 -1
  28. package/dist/trigger/trigger-router.d.ts +6 -1
  29. package/dist/trigger/trigger-router.js +26 -1
  30. package/dist/trigger/trigger-store.js +10 -0
  31. package/dist/trigger/types.d.ts +2 -0
  32. package/dist/v2/durable-core/schemas/artifacts/discovery-handoff.d.ts +29 -0
  33. package/dist/v2/durable-core/schemas/artifacts/discovery-handoff.js +26 -0
  34. package/dist/v2/durable-core/schemas/artifacts/index.d.ts +2 -1
  35. package/dist/v2/durable-core/schemas/artifacts/index.js +7 -1
  36. package/dist/v2/durable-core/schemas/artifacts/review-verdict.d.ts +5 -0
  37. package/dist/v2/durable-core/schemas/artifacts/review-verdict.js +12 -0
  38. package/dist/v2/durable-core/schemas/compiled-workflow/index.d.ts +8 -8
  39. package/docs/design/connect-adaptive-dispatch-candidates.md +153 -0
  40. package/docs/design/connect-adaptive-dispatch-design-review.md +88 -0
  41. package/docs/design/connect-adaptive-dispatch-implementation-plan.md +209 -0
  42. package/docs/design/queue-label-support-candidates.md +83 -0
  43. package/docs/design/queue-label-support-design-review.md +62 -0
  44. package/docs/design/queue-label-support-implementation-plan.md +158 -0
  45. package/docs/ideas/backlog.md +160 -0
  46. package/package.json +1 -1
  47. package/workflows/mr-review-workflow.agentic.v2.json +1 -1
@@ -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 |
@@ -6395,3 +6395,163 @@ When a post-implementation MR review finds a UI/UX finding (wrong affordance, mi
6395
6395
  ### Priority
6396
6396
 
6397
6397
  Design this as part of the adaptive coordinator (#3). The `touchesUI` flag belongs on the classification output alongside `taskComplexity` and `maturity`. The UI detection logic and the design workflow insertion are both coordinator-level concerns, not engine-level.
6398
+
6399
+ ---
6400
+
6401
+ ## Current state update (Apr 20, 2026)
6402
+
6403
+ **npm version: v3.45.0**
6404
+
6405
+ ### What shipped in this session (Apr 19-20, 2026)
6406
+
6407
+ All five top-priority autonomous pipeline items shipped:
6408
+
6409
+ - ✅ **#1 -- Worktree isolation + auto-commit** (PR #630) -- Each WorkTrain coding session now runs in an isolated git worktree (`~/.workrail/worktrees/<sessionId>`). `trigger.workspacePath` is never mutated; all tool factories receive `sessionWorkspacePath`. Crash recovery sidecar persists `worktreePath` for orphan cleanup. `delivery-action.ts` asserts HEAD branch before push. `test-task` trigger: `branchStrategy: worktree`, `autoCommit: true`, `autoOpenPR: true`.
6410
+
6411
+ - ✅ **#2 -- Stuck detection escalation** (PR #636) -- New `WorkflowRunResult._tag: 'stuck'` discriminant. When `repeated_tool_call` heuristic fires and `stuckAbortPolicy !== 'notify_only'` (default: `'abort'`), daemon aborts the session immediately instead of burning the 30-min wall clock. Writes structured entry to `~/.workrail/outbox.jsonl`. `stuckAbortPolicy` and `noProgressAbortEnabled` configurable per trigger in `agentConfig`. `ChildWorkflowRunResult` updated atomically.
6412
+
6413
+ - ✅ **#3 -- Adaptive pipeline coordinator** (PR #639) -- `worktrain run pipeline --issue N --workspace path` routes tasks to the right pipeline via pure static routing:
6414
+ - dep-bump + PR number → QUICK_REVIEW (delegates to `runPrReviewCoordinator`)
6415
+ - PR/MR number → REVIEW_ONLY
6416
+ - `current-pitch.md` exists → IMPLEMENT (coding + PR + review + merge)
6417
+ - Default → FULL (discovery → shaping → coding → PR → review → merge)
6418
+ - Fix loop cap: 2 iterations max. Escalating audit chain for Critical findings. UX gate for UI-touching tasks. 6 hardcoded timeout constants. Pitch archived after IMPLEMENT/FULL completes.
6419
+
6420
+ - ✅ **#4 -- GitHub issue queue poll trigger** (PR #637) -- New `github_queue_poll` trigger provider. Polls GitHub issues matching `GitHubQueueConfig` (assignee-based MVP, `label`/`mention`/`query` typed but `not_implemented`). Maturity inference from 3 deterministic heuristics. Idempotency check (conservative: parse errors = active). JSONL decision log at `~/.workrail/queue-poll.jsonl`. `maxTotalConcurrentSessions` cap. Bot identity config (`botName`, `botEmail`).
6421
+
6422
+ - ✅ **#5 -- Context assembly layer** (PR #624, shipped earlier) -- `ContextAssembler` injects git diff summary + prior session notes before turn 1. Feeds into coordinator pre-dispatch.
6423
+
6424
+ - ✅ **Performance sweep** (all 10 issues #248-257 -- already confirmed complete)
6425
+ - ✅ **Console session tree** (PR #607 -- parentSessionId rendered in UI)
6426
+ - ✅ **Daemon file-nav tools** (PR #619) -- Glob, Grep, Edit + upgraded Read/Write with staleness guard
6427
+ - ✅ **`spawn_agent` artifacts** (PR #613) -- `lastStepArtifacts` surfaced through spawn_agent return
6428
+ - ✅ **`wr.shaping` workflow** (PR #610) -- faithful Shape Up shaping, 9 steps
6429
+ - ✅ **Coding workflow Phase 0.5** (PR #610) -- upstream context detection, three-workflow pipeline
6430
+
6431
+ ### WorkTrain current capabilities (v3.45.0)
6432
+
6433
+ **Autonomous workflow execution -- confirmed working:**
6434
+ - `worktrain run pipeline --issue N` routes to the right pipeline and runs it end-to-end
6435
+ - `worktrain run pr-review` autonomous PR review with structured verdicts and auto-merge
6436
+ - Coding sessions run in isolated worktrees, auto-commit, auto-open PR
6437
+ - Sessions abort when stuck (instead of burning 30-min wall clock)
6438
+ - GitHub issue queue polling: assign issue to `worktrain-etienneb` → daemon picks it up automatically
6439
+ - All sessions start with git diff + prior session notes injected (ContextAssembler)
6440
+ - Daemon file-nav tools: Glob, Grep, Edit, Read (paginated), Write (staleness guard)
6441
+ - Escalating audit chain: Critical findings → prod audit → re-review → escalate if still Critical
6442
+ - Fix loop: minor findings → max 2 fix iterations before escalation
6443
+
6444
+ **WorkTrain agent tool set (v3.45.0):**
6445
+ `complete_step`, `continue_workflow` (deprecated), `Bash`, `Read`, `Write`, `Glob`, `Grep`, `Edit`, `report_issue`, `spawn_agent`, `signal_coordinator`
6446
+
6447
+ **Trigger system:**
6448
+ - Generic webhook, GitLab MR polling, GitHub Issues polling, GitHub PR polling
6449
+ - **NEW: `github_queue_poll`** -- assignee-based issue queue with maturity inference
6450
+ - `branchStrategy: worktree` -- isolated worktree per session
6451
+ - `autoCommit: true` / `autoOpenPR: true` -- full delivery pipeline
6452
+ - `stuckAbortPolicy: 'abort' | 'notify_only'`
6453
+ - `goalTemplate`, `referenceUrls`, `contextMapping`, `agentConfig`
6454
+
6455
+ ### Accurate limitations (v3.45.0)
6456
+
6457
+ 1. **`dispatchAdaptivePipeline()` not yet connected** -- `TriggerRouter.dispatchAdaptivePipeline()` exists but `polling-scheduler.ts` still calls `router.dispatch()`. Queue poll sessions run as generic sessions, not routed through the adaptive coordinator. Cross-PR gap documented with TODO.
6458
+
6459
+ 2. **`findingCategory` not on review-verdict** -- Audit chain always dispatches `production-readiness-audit` for Critical findings regardless of finding type. `findingCategory` field on `findings[]` items needs to be added to `wr.review_verdict` schema as a follow-up so architecture findings can route to `architecture-scalability-audit` correctly.
6460
+
6461
+ 3. **Bot account setup required before first queue run** -- `worktrain-etienneb` GitHub account must be created, PAT generated with `repo:read` scope, stored as `WORKTRAIN_BOT_TOKEN`, and added as repo collaborator. Commit identity: `worktrain-etienneb@users.noreply.github.com`. Without this, `github_queue_poll` trigger has no bot identity.
6462
+
6463
+ 4. **No auto-merge setting in `worktrain init`** -- Auto-merge policy is hardcoded in the coordinator. Should be a `~/.workrail/config.json` setting exposed during `worktrain init`.
6464
+
6465
+ 5. **Grooming loop not built** -- Three open design decisions must be settled before building (human-ack boundary, compute budget, priority signal source). Deferred until Level 1 usage data exists.
6466
+
6467
+ 6. **Knowledge graph not wired** -- `src/knowledge-graph/` module exists (DuckDB + ts-morph), `ts-morph` in devDependencies. No daemon tool yet. Architecture decision: belongs in context assembly layer, not as a daemon tool.
6468
+
6469
+ 7. **`worktrain inbox --watch` stub** -- Prints "not yet implemented." The outbox mechanism exists; just needs a polling loop.
6470
+
6471
+ 8. **Artifact store not built** -- Agents dump markdown in the repo. `~/.workrail/artifacts/` not created.
6472
+
6473
+ ### Next priorities (groomed Apr 20)
6474
+
6475
+ 1. **Connect `dispatchAdaptivePipeline()`** -- Wire `polling-scheduler.ts` to call `TriggerRouter.dispatchAdaptivePipeline()` when `context.taskCandidate` is present. Small change, unlocks the full autonomous queue → pipeline connection.
6476
+
6477
+ 2. **`findingCategory` on review-verdict schema** -- Add `findingCategory: 'correctness' | 'security' | 'architecture' | 'ux' | 'performance' | 'testing'` to `findings[]` in `ReviewVerdictArtifactV1Schema`. Update `mr-review-workflow-agentic` final step to emit it. Unlocks correct audit routing.
6478
+
6479
+ 3. **Bot account setup + `worktrain init` overhaul** -- Create `worktrain-etienneb`, add `worktrain daemon --check` command (API key + git fetch dry run), expose auto-merge policy in `worktrain init`.
6480
+
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
+
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exaudeus/workrail",
3
- "version": "3.45.0",
3
+ "version": "3.47.0",
4
4
  "description": "Step-by-step workflow enforcement for AI agents via MCP",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -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