@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.
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.js +3 -1
- package/dist/cli/commands/worktrain-pipeline.d.ts +17 -0
- package/dist/cli/commands/worktrain-pipeline.js +121 -0
- package/dist/console-ui/assets/{index-BpanIvmi.js → index-B77l3WBR.js} +1 -1
- package/dist/console-ui/index.html +1 -1
- package/dist/coordinators/adaptive-pipeline.d.ts +57 -0
- package/dist/coordinators/adaptive-pipeline.js +104 -0
- package/dist/coordinators/modes/full-pipeline.d.ts +4 -0
- package/dist/coordinators/modes/full-pipeline.js +256 -0
- package/dist/coordinators/modes/implement-shared.d.ts +5 -0
- package/dist/coordinators/modes/implement-shared.js +205 -0
- package/dist/coordinators/modes/implement.d.ts +3 -0
- package/dist/coordinators/modes/implement.js +108 -0
- package/dist/coordinators/modes/quick-review.d.ts +3 -0
- package/dist/coordinators/modes/quick-review.js +37 -0
- package/dist/coordinators/modes/review-only.d.ts +2 -0
- package/dist/coordinators/modes/review-only.js +28 -0
- package/dist/coordinators/routing/route-task.d.ts +21 -0
- package/dist/coordinators/routing/route-task.js +55 -0
- package/dist/manifest.json +107 -35
- package/dist/mcp/output-schemas.d.ts +16 -16
- 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 +6 -1
- package/dist/trigger/trigger-router.js +26 -1
- package/dist/trigger/trigger-store.js +10 -0
- package/dist/trigger/types.d.ts +2 -0
- package/dist/v2/durable-core/schemas/artifacts/discovery-handoff.d.ts +29 -0
- package/dist/v2/durable-core/schemas/artifacts/discovery-handoff.js +26 -0
- package/dist/v2/durable-core/schemas/artifacts/index.d.ts +2 -1
- package/dist/v2/durable-core/schemas/artifacts/index.js +7 -1
- 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/dist/v2/durable-core/schemas/compiled-workflow/index.d.ts +8 -8
- 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 +160 -0
- package/package.json +1 -1
- package/workflows/mr-review-workflow.agentic.v2.json +1 -1
|
@@ -14,6 +14,18 @@ exports.ReviewVerdictArtifactV1Schema = zod_1.z
|
|
|
14
14
|
.object({
|
|
15
15
|
severity: zod_1.z.enum(['critical', 'major', 'minor', 'nit']),
|
|
16
16
|
summary: zod_1.z.string().min(1),
|
|
17
|
+
findingCategory: zod_1.z
|
|
18
|
+
.enum([
|
|
19
|
+
'correctness',
|
|
20
|
+
'security',
|
|
21
|
+
'architecture',
|
|
22
|
+
'ux',
|
|
23
|
+
'performance',
|
|
24
|
+
'testing',
|
|
25
|
+
'style',
|
|
26
|
+
])
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('Category of the finding. Used by coordinators to route audit chains.'),
|
|
17
29
|
})
|
|
18
30
|
.strict()),
|
|
19
31
|
summary: zod_1.z.string().min(1),
|
|
@@ -22,8 +22,8 @@ export declare const CompiledWorkflowSnapshotV1Schema: z.ZodDiscriminatedUnion<"
|
|
|
22
22
|
}, "strip", z.ZodTypeAny, {
|
|
23
23
|
workflowId: string;
|
|
24
24
|
name: string;
|
|
25
|
-
description: string;
|
|
26
25
|
version: string;
|
|
26
|
+
description: string;
|
|
27
27
|
schemaVersion: 1;
|
|
28
28
|
sourceKind: "v1_preview";
|
|
29
29
|
preview: {
|
|
@@ -34,8 +34,8 @@ export declare const CompiledWorkflowSnapshotV1Schema: z.ZodDiscriminatedUnion<"
|
|
|
34
34
|
}, {
|
|
35
35
|
workflowId: string;
|
|
36
36
|
name: string;
|
|
37
|
-
description: string;
|
|
38
37
|
version: string;
|
|
38
|
+
description: string;
|
|
39
39
|
schemaVersion: 1;
|
|
40
40
|
sourceKind: "v1_preview";
|
|
41
41
|
preview: {
|
|
@@ -132,8 +132,8 @@ export declare const CompiledWorkflowSnapshotV1Schema: z.ZodDiscriminatedUnion<"
|
|
|
132
132
|
}, "strip", z.ZodTypeAny, {
|
|
133
133
|
workflowId: string;
|
|
134
134
|
name: string;
|
|
135
|
-
description: string;
|
|
136
135
|
version: string;
|
|
136
|
+
description: string;
|
|
137
137
|
schemaVersion: 1;
|
|
138
138
|
sourceKind: "v1_pinned";
|
|
139
139
|
definition?: unknown;
|
|
@@ -168,8 +168,8 @@ export declare const CompiledWorkflowSnapshotV1Schema: z.ZodDiscriminatedUnion<"
|
|
|
168
168
|
}, {
|
|
169
169
|
workflowId: string;
|
|
170
170
|
name: string;
|
|
171
|
-
description: string;
|
|
172
171
|
version: string;
|
|
172
|
+
description: string;
|
|
173
173
|
schemaVersion: 1;
|
|
174
174
|
sourceKind: "v1_pinned";
|
|
175
175
|
definition?: unknown;
|
|
@@ -226,8 +226,8 @@ export declare const CompiledWorkflowSnapshotSchema: z.ZodDiscriminatedUnion<"so
|
|
|
226
226
|
}, "strip", z.ZodTypeAny, {
|
|
227
227
|
workflowId: string;
|
|
228
228
|
name: string;
|
|
229
|
-
description: string;
|
|
230
229
|
version: string;
|
|
230
|
+
description: string;
|
|
231
231
|
schemaVersion: 1;
|
|
232
232
|
sourceKind: "v1_preview";
|
|
233
233
|
preview: {
|
|
@@ -238,8 +238,8 @@ export declare const CompiledWorkflowSnapshotSchema: z.ZodDiscriminatedUnion<"so
|
|
|
238
238
|
}, {
|
|
239
239
|
workflowId: string;
|
|
240
240
|
name: string;
|
|
241
|
-
description: string;
|
|
242
241
|
version: string;
|
|
242
|
+
description: string;
|
|
243
243
|
schemaVersion: 1;
|
|
244
244
|
sourceKind: "v1_preview";
|
|
245
245
|
preview: {
|
|
@@ -336,8 +336,8 @@ export declare const CompiledWorkflowSnapshotSchema: z.ZodDiscriminatedUnion<"so
|
|
|
336
336
|
}, "strip", z.ZodTypeAny, {
|
|
337
337
|
workflowId: string;
|
|
338
338
|
name: string;
|
|
339
|
-
description: string;
|
|
340
339
|
version: string;
|
|
340
|
+
description: string;
|
|
341
341
|
schemaVersion: 1;
|
|
342
342
|
sourceKind: "v1_pinned";
|
|
343
343
|
definition?: unknown;
|
|
@@ -372,8 +372,8 @@ export declare const CompiledWorkflowSnapshotSchema: z.ZodDiscriminatedUnion<"so
|
|
|
372
372
|
}, {
|
|
373
373
|
workflowId: string;
|
|
374
374
|
name: string;
|
|
375
|
-
description: string;
|
|
376
375
|
version: string;
|
|
376
|
+
description: string;
|
|
377
377
|
schemaVersion: 1;
|
|
378
378
|
sourceKind: "v1_pinned";
|
|
379
379
|
definition?: unknown;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Design Candidates: Connect adaptive dispatch in polling-scheduler.ts
|
|
2
|
+
|
|
3
|
+
**Task:** Connect `TriggerRouter.dispatchAdaptivePipeline()` in `doPollGitHubQueue()` so queue poll sessions route through the adaptive coordinator instead of generic `dispatch()`.
|
|
4
|
+
|
|
5
|
+
**Scope:** `src/trigger/polling-scheduler.ts` and `src/trigger/trigger-router.ts` only.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Problem Understanding
|
|
10
|
+
|
|
11
|
+
### Tensions
|
|
12
|
+
|
|
13
|
+
1. **Fire-and-forget vs. async coordination**: `dispatch()` is synchronous and returns a `string` immediately. `dispatchAdaptivePipeline()` is `async` and returns `Promise<PipelineOutcome>`. The scheduler calls both as void fire-and-forget. The tension is that the pipeline outcome is never surfaced to the scheduler -- errors are only logged, never propagated.
|
|
14
|
+
|
|
15
|
+
2. **Deps ownership vs. scheduler simplicity**: `dispatchAdaptivePipeline()` currently requires `AdaptiveCoordinatorDeps` and `ModeExecutors` from the caller. The scheduler has neither. Either the router must own these deps (stored via DI), or the scheduler must carry them (wrong boundary), or the method builds them internally (violates DI principle).
|
|
16
|
+
|
|
17
|
+
3. **Type safety vs. runtime flexibility**: The task requests a duck-type guard (`'dispatchAdaptivePipeline' in this.router`) rather than relying on TypeScript's static type. This is unusual for a strongly-typed codebase but necessary for test fakes that may not implement the method.
|
|
18
|
+
|
|
19
|
+
4. **Production readiness vs. YAGNI**: Wiring `AdaptiveCoordinatorDeps` in production requires `spawnSession`, `awaitSessions`, `getAgentResult`, etc. -- HTTP-based, port-dependent. Providing that wiring now is out of scope for a "connect" task.
|
|
20
|
+
|
|
21
|
+
### Likely seam
|
|
22
|
+
|
|
23
|
+
`doPollGitHubQueue()` in `polling-scheduler.ts`, line 494: `this.router.dispatch(workflowTrigger)`. This is the correct seam -- it's where the dispatch decision is made, and where `context.taskCandidate` is available.
|
|
24
|
+
|
|
25
|
+
### What makes this hard
|
|
26
|
+
|
|
27
|
+
- `dispatchAdaptivePipeline` was added incomplete by design (JSDoc: "intentionally unconnected until that branch is rebased onto main"). It's a placeholder with a signature that requires caller-supplied deps that the scheduler cannot provide.
|
|
28
|
+
- Production `AdaptiveCoordinatorDeps` wiring does not yet exist on `TriggerRouter`.
|
|
29
|
+
- A naive swap crashes at runtime (deps are `undefined`, `runAdaptivePipeline` calls `deps.now()` immediately).
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Philosophy Constraints
|
|
34
|
+
|
|
35
|
+
From CLAUDE.md and repo patterns:
|
|
36
|
+
|
|
37
|
+
- **DI for boundaries**: inject external effects (I/O, clocks) -- do not construct them inside logic modules
|
|
38
|
+
- **YAGNI with discipline**: avoid speculative abstractions; the task is to "connect" not to "fully implement"
|
|
39
|
+
- **Immutability by default**: all TriggerRouter fields are `private readonly`
|
|
40
|
+
- **Type safety first**: prefer compile-time guarantees -- but the type guard is an accepted runtime concession for mock compatibility
|
|
41
|
+
|
|
42
|
+
### Conflicts
|
|
43
|
+
|
|
44
|
+
- Stated philosophy ("type safety first") vs. requested duck-type guard. The runtime guard exists because test fakes may not implement `dispatchAdaptivePipeline`. This is an intentional concession to test flexibility, not a philosophical violation.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Impact Surface
|
|
49
|
+
|
|
50
|
+
- `TriggerRouter` constructor signature (minor -- optional params only, no breaking change)
|
|
51
|
+
- All existing `TriggerRouter` tests -- must continue to pass without supplying new optional params
|
|
52
|
+
- `PollingScheduler` -- minimal change at the dispatch call site
|
|
53
|
+
- All existing `PollingScheduler` tests -- must continue to pass; the type guard means mock routers without `dispatchAdaptivePipeline` silently fall back to `dispatch()`
|
|
54
|
+
- Production bootstrap (e.g. `src/trigger/trigger-listener.ts`) -- NOT changed in this PR (wiring `coordinatorDeps` is a follow-up task)
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Candidates
|
|
59
|
+
|
|
60
|
+
### Candidate A: Store optional coordinator deps on TriggerRouter (DI pattern)
|
|
61
|
+
|
|
62
|
+
**Summary**: Add optional `coordinatorDeps?: AdaptiveCoordinatorDeps` and `modeExecutors?: ModeExecutors` constructor params to `TriggerRouter`. Simplify `dispatchAdaptivePipeline` to use stored fields (not caller-provided params). In `doPollGitHubQueue`, type-guard check and call `void this.router.dispatchAdaptivePipeline(goal, workspace, context)`; fall back to `dispatch()` when the method is absent or deps are missing.
|
|
63
|
+
|
|
64
|
+
**Tensions resolved**: DI boundary (router owns deps). Fire-and-forget preserved. Type guard enables test fallback.
|
|
65
|
+
|
|
66
|
+
**Tensions accepted**: Production adaptive dispatch deferred (always falls back to `dispatch()` until deps are wired). Adds optional params to TriggerRouter constructor.
|
|
67
|
+
|
|
68
|
+
**Boundary**: `TriggerRouter` -- correct. It already owns `execFn`, `emitter`, `notificationService`, `steerRegistry` via the same pattern.
|
|
69
|
+
|
|
70
|
+
**Why this boundary**: The scheduler's job is to poll and dispatch. It should not know what `AdaptiveCoordinatorDeps` is. The router is the infrastructure boundary.
|
|
71
|
+
|
|
72
|
+
**Failure mode**: If `coordinatorDeps` is never injected in production, adaptive dispatch silently falls back to `dispatch()`. Mitigated by a `console.warn` log.
|
|
73
|
+
|
|
74
|
+
**Repo pattern**: Follows the existing TriggerRouter optional DI pattern exactly. No new patterns invented.
|
|
75
|
+
|
|
76
|
+
**Gains**: Clean separation; minimal change; all existing tests pass unchanged.
|
|
77
|
+
|
|
78
|
+
**Gives up**: Production adaptive dispatch doesn't work until a follow-up PR wires `coordinatorDeps`.
|
|
79
|
+
|
|
80
|
+
**Scope judgment**: Best-fit. The task says "connect" -- this creates the wiring path without full production readiness.
|
|
81
|
+
|
|
82
|
+
**Philosophy fit**: Honors DI for boundaries, YAGNI, immutability. Minor concession: duck-type guard is necessary for mock compatibility.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
### Candidate B: Add coordinator deps to PollingScheduler constructor
|
|
87
|
+
|
|
88
|
+
**Summary**: Leave `TriggerRouter.dispatchAdaptivePipeline` signature as-is. Add `coordinatorDeps?` and `modeExecutors?` to `PollingScheduler` constructor. Pass them through to `dispatchAdaptivePipeline` in `doPollGitHubQueue`.
|
|
89
|
+
|
|
90
|
+
**Tensions resolved**: No TriggerRouter API change.
|
|
91
|
+
|
|
92
|
+
**Tensions accepted**: `PollingScheduler` becomes responsible for coordinator wiring -- wrong boundary. Scheduler's constructor grows with unrelated concerns.
|
|
93
|
+
|
|
94
|
+
**Boundary**: `PollingScheduler` -- wrong. The scheduler manages polling intervals; it should not know about `AdaptiveCoordinatorDeps`.
|
|
95
|
+
|
|
96
|
+
**Failure mode**: Two-level optional param threading. If either is absent, must guard in two places (scheduler constructor + method call).
|
|
97
|
+
|
|
98
|
+
**Repo pattern**: Departs -- nothing in `PollingScheduler` currently takes coordinator-level dependencies.
|
|
99
|
+
|
|
100
|
+
**Scope judgment**: Too broad -- changes the wrong boundary.
|
|
101
|
+
|
|
102
|
+
**Philosophy conflict**: Violates DI for boundaries (wrong injection point) and YAGNI.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
### Candidate C: Lazily construct AdaptiveCoordinatorDeps inside dispatchAdaptivePipeline
|
|
107
|
+
|
|
108
|
+
**Summary**: No constructor changes. `dispatchAdaptivePipeline` builds `AdaptiveCoordinatorDeps` lazily from `this.ctx`, dynamically imports mode executors (same as CLI pattern), and calls `runAdaptivePipeline` self-contained. In the scheduler, type-guard + call with just `(goal, workspace, context)`.
|
|
109
|
+
|
|
110
|
+
**Tensions resolved**: No constructor API change. Self-contained per call.
|
|
111
|
+
|
|
112
|
+
**Tensions accepted**: Requires full `AdaptiveCoordinatorDeps` implementation inside `trigger-router.ts` -- requires console port, HTTP client, `gh` CLI. Major scope increase. Violates DI principle (building deps inside module).
|
|
113
|
+
|
|
114
|
+
**Failure mode**: Incomplete impl crashes at `deps.now()` or later; requires entire `AdaptiveCoordinatorDeps` surface implemented.
|
|
115
|
+
|
|
116
|
+
**Scope judgment**: Too broad. This is a separate task ("implement coordinator deps for TriggerRouter").
|
|
117
|
+
|
|
118
|
+
**Philosophy conflict**: Violates DI for boundaries.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Comparison and Recommendation
|
|
123
|
+
|
|
124
|
+
**Recommendation: Candidate A.**
|
|
125
|
+
|
|
126
|
+
All meaningful tensions favor Candidate A:
|
|
127
|
+
|
|
128
|
+
- **Right boundary**: TriggerRouter already uses the exact same DI pattern for `execFn`, `emitter`, `notificationService`, `steerRegistry`. Adding `coordinatorDeps` and `modeExecutors` extends an established pattern at the established boundary.
|
|
129
|
+
- **Most manageable failure mode**: The fallback to `dispatch()` with a warning log is explicit and visible. Candidate B has the same fallback complexity but at the wrong boundary. Candidate C crashes if not fully implemented.
|
|
130
|
+
- **Scope fit**: The task is explicitly a "connect" task. Candidate A makes the connection; production wiring is deferred to a follow-up (which is correct -- the pitch says "wire queue poller -> runAdaptivePipeline()" as a modification to `trigger-router.ts`, not as production bootstrap wiring).
|
|
131
|
+
- **Easiest to evolve**: When the production `AdaptiveCoordinatorDeps` is ready, it's injected once at the bootstrap level. No changes to scheduler or method internals required.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Self-Critique
|
|
136
|
+
|
|
137
|
+
**Strongest counter-argument**: If `coordinatorDeps` is always `undefined` in production, this PR "connects" nothing in practice -- the fallback to `dispatch()` always fires. The connection exists in the code but not in behavior.
|
|
138
|
+
|
|
139
|
+
Response: This is the explicitly stated intent. The JSDoc says "intentionally unconnected until that branch is rebased onto main." The task is to create the call path; making it operational is a separate step.
|
|
140
|
+
|
|
141
|
+
**Narrower option considered**: Don't change `trigger-router.ts` at all -- add the type guard in `polling-scheduler.ts` and call the existing method with `undefined` for the `coordinatorDeps`/`modeExecutors` params. Problem: the existing method has those params as required (not optional). This requires making them optional in `trigger-router.ts` anyway, which is essentially the same as Candidate A without the constructor injection.
|
|
142
|
+
|
|
143
|
+
**Broader option that might be justified**: Candidate C, if the production deps implementation already existed elsewhere and just needed wiring. Evidence required: a `makeAdaptiveCoordinatorDeps(ctx, port)` factory function somewhere. None found.
|
|
144
|
+
|
|
145
|
+
**Assumption that could invalidate this design**: If `dispatchAdaptivePipeline` was intended to always receive fresh deps from the caller (not stored), and the production caller is NOT `PollingScheduler` but some higher-level bootstrap that wraps the router. In that case, the method signature should stay as-is and the scheduler should call a different wrapper. However, the task description explicitly says to call `router.dispatchAdaptivePipeline()` from the scheduler, which rules out this interpretation.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Open Questions for Main Agent
|
|
150
|
+
|
|
151
|
+
1. Should the fallback log a `console.warn` (visible noise) or `console.log` (informational)? The pattern for "expected missing optional dep" in this codebase is `console.warn` (see TriggerRouter constructor for missing `maxConcurrentSessions`).
|
|
152
|
+
|
|
153
|
+
2. Should the async `dispatchAdaptivePipeline` fire-and-forget (same as `dispatch()`) or should errors be awaited and logged? The existing `dispatch()` pattern uses `void this.queue.enqueue(...)` for fire-and-forget with result logging inside the callback. The adaptive dispatch should do the same: `void (async () => { ... })()` with outcome logging inside.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Design Review: Connect adaptive dispatch in polling-scheduler.ts
|
|
2
|
+
|
|
3
|
+
**Design under review:** `connect-adaptive-dispatch-candidates.md`, Candidate A (with hybrid revision)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Tradeoff Review
|
|
8
|
+
|
|
9
|
+
| Tradeoff | Status |
|
|
10
|
+
|----------|--------|
|
|
11
|
+
| Production activation deferred | Acceptable -- documented, warned, follow-up task |
|
|
12
|
+
| Duck-type guard instead of static narrowing | Acceptable -- required for test mock compatibility |
|
|
13
|
+
| Signature change removes 2 required params | Safe -- no tests call `dispatchAdaptivePipeline` directly |
|
|
14
|
+
| Async fire-and-forget via void IIFE | Acceptable -- consistent with `dispatch()` pattern |
|
|
15
|
+
|
|
16
|
+
All tradeoffs were verified against acceptance criteria and invariants. None violates the task's stated requirements.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Failure Mode Review
|
|
21
|
+
|
|
22
|
+
| Failure Mode | Mitigation | Risk Level |
|
|
23
|
+
|-------------|-----------|-----------|
|
|
24
|
+
| `coordinatorDeps` absent -- silent fallback | `console.warn` log | Low (documented deferral) |
|
|
25
|
+
| `runAdaptivePipeline` throws | Try-catch in IIFE + scheduler-level catch | Low |
|
|
26
|
+
| Type guard false for real TriggerRouter | Cannot happen -- method on prototype | N/A |
|
|
27
|
+
| Interface drift (new required deps method) | TypeScript compile-time catch | Low |
|
|
28
|
+
| Concurrent poll cycles | Skip-cycle guard already prevents | N/A |
|
|
29
|
+
|
|
30
|
+
No unmitigated failure modes identified.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Runner-Up / Simpler Alternative Review
|
|
35
|
+
|
|
36
|
+
**Simpler variant considered**: Keep `dispatchAdaptivePipeline` signature with `coordinatorDeps` and `modeExecutors` as optional method params (not stored on constructor). Scheduler calls with `(goal, workspace, undefined, undefined, context)`.
|
|
37
|
+
|
|
38
|
+
**Why rejected as-is**: 5-param call site with two explicit `undefined` arguments at the scheduler call site is noisy and implies the caller is responsible for deps it cannot provide.
|
|
39
|
+
|
|
40
|
+
**Hybrid adopted**: Keep optional method params (for flexibility) AND store them as constructor fields (for default injection). The scheduler calls with `(goal, workspace, context)` -- 3 params, no explicit `undefined`. Production can inject deps at construction; tests can pass them per-call if needed. This is strictly more flexible than the original Candidate A.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Philosophy Alignment
|
|
45
|
+
|
|
46
|
+
- **DI for boundaries**: Satisfied. Deps injected at construction, scheduler passes nothing.
|
|
47
|
+
- **Immutability by default**: Satisfied. New fields are `private readonly`.
|
|
48
|
+
- **YAGNI with discipline**: Satisfied. No production deps wiring in this PR.
|
|
49
|
+
- **Type safety first**: Minor tension with duck-type guard. Acceptable -- required for test mock compatibility.
|
|
50
|
+
- **Make illegal states unrepresentable**: Minor tension -- `coordinatorDeps` and `modeExecutors` are independent optionals. Acceptable -- consistent with existing router pattern.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Findings
|
|
55
|
+
|
|
56
|
+
### Yellow: Deferred production activation
|
|
57
|
+
|
|
58
|
+
**Severity**: Yellow (informational, not a blocker)
|
|
59
|
+
|
|
60
|
+
`coordinatorDeps` and `modeExecutors` will be `undefined` in production until a follow-up PR wires them into the `TriggerRouter` constructor. The `console.warn` log makes this visible. This is intentional per the pitch's "connect" framing, but should be tracked as a follow-up task.
|
|
61
|
+
|
|
62
|
+
**No revision required.** The warning log is sufficient mitigation.
|
|
63
|
+
|
|
64
|
+
### Yellow: Method signature flexibility
|
|
65
|
+
|
|
66
|
+
**Severity**: Yellow (quality, not a correctness issue)
|
|
67
|
+
|
|
68
|
+
The hybrid approach (optional params + stored fields) adds slight complexity to `dispatchAdaptivePipeline` internals (must merge caller-provided and stored fields). The precedence rule (caller-provided params override stored fields when both are present) should be documented in the method's JSDoc to prevent future confusion.
|
|
69
|
+
|
|
70
|
+
**Revision required**: Add JSDoc note to `dispatchAdaptivePipeline` clarifying that caller-provided `coordinatorDeps`/`modeExecutors` override the stored fields.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Recommended Revisions
|
|
75
|
+
|
|
76
|
+
1. **Adopt hybrid approach**: `dispatchAdaptivePipeline` accepts optional `coordinatorDeps?` and `modeExecutors?` params (caller override) with fallback to `this._coordinatorDeps` and `this._modeExecutors` (stored fields). Three-param call site from scheduler: `(goal, workspace, context)`.
|
|
77
|
+
|
|
78
|
+
2. **Add JSDoc clarification**: Document the precedence rule (caller params override stored fields) in `dispatchAdaptivePipeline`'s JSDoc.
|
|
79
|
+
|
|
80
|
+
3. **Warning log text**: The fallback warning should include: triggerId, reason ("coordinatorDeps not injected -- falling back to dispatch()"), and suggest where to inject.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Residual Concerns
|
|
85
|
+
|
|
86
|
+
- **Production wiring gap**: The adaptive coordinator will not activate in production until `AdaptiveCoordinatorDeps` is implemented and injected. This is tracked as a follow-up task by the task description. No action required in this PR.
|
|
87
|
+
|
|
88
|
+
- **Test coverage gap**: No test verifies that `dispatchAdaptivePipeline` is called instead of `dispatch()` for queue-poll sessions when deps are present. This would require adding `dispatchAdaptivePipeline` to the mock router in `polling-scheduler.test.ts`. Out of scope for this PR per the "small targeted change" instruction, but recommended as a follow-up.
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Implementation Plan: Connect adaptive dispatch in polling-scheduler.ts
|
|
2
|
+
|
|
3
|
+
## Problem Statement
|
|
4
|
+
|
|
5
|
+
`TriggerRouter.dispatchAdaptivePipeline()` was added in PR #639 but left unconnected. `polling-scheduler.ts` still calls `router.dispatch()` for all sessions including `github_queue_poll` ones. The queue poll sessions run as generic workflow sessions instead of being routed through the adaptive coordinator. This PR creates the wiring connection.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Acceptance Criteria
|
|
10
|
+
|
|
11
|
+
1. In `doPollGitHubQueue()`, when a task candidate is dispatched, `router.dispatchAdaptivePipeline()` is called (via type guard) instead of `router.dispatch()` -- when `dispatchAdaptivePipeline` exists on the router.
|
|
12
|
+
|
|
13
|
+
2. When `dispatchAdaptivePipeline` is absent on the router (test mocks), `dispatch()` is called as fallback -- no crash, no error.
|
|
14
|
+
|
|
15
|
+
3. When `dispatchAdaptivePipeline` exists but `coordinatorDeps` is not injected, a `console.warn` is emitted and the method falls back gracefully (returns a dry_run outcome or delegates to dispatch-equivalent behavior). The polling loop continues normally.
|
|
16
|
+
|
|
17
|
+
4. `TriggerRouter` accepts optional `coordinatorDeps?: AdaptiveCoordinatorDeps` and `modeExecutors?: ModeExecutors` constructor params. When provided, `dispatchAdaptivePipeline` uses them as defaults.
|
|
18
|
+
|
|
19
|
+
5. `dispatchAdaptivePipeline` signature is simplified: takes `(goal, workspace, context?, coordinatorDeps?, modeExecutors?)` -- last two are caller overrides (take precedence over stored fields). Returns `Promise<PipelineOutcome>`.
|
|
20
|
+
|
|
21
|
+
6. `npm run build` produces no TypeScript errors.
|
|
22
|
+
|
|
23
|
+
7. `npx vitest run` -- all 353 test files pass, no regressions.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Non-Goals
|
|
28
|
+
|
|
29
|
+
- Do NOT implement `AdaptiveCoordinatorDeps` production wiring inside `TriggerRouter` (that's a follow-up PR).
|
|
30
|
+
- Do NOT wire `coordinatorDeps` in `trigger-listener.ts` or any bootstrap file.
|
|
31
|
+
- Do NOT modify any `src/mcp/` files.
|
|
32
|
+
- Do NOT change `AdaptiveCoordinatorDeps` or `ModeExecutors` interface definitions.
|
|
33
|
+
- Do NOT add new tests for the adaptive dispatch path (test mock router doesn't implement `dispatchAdaptivePipeline`; the fallback path is the tested path).
|
|
34
|
+
- Do NOT change `PollingScheduler` constructor signature.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Philosophy-Driven Constraints
|
|
39
|
+
|
|
40
|
+
- **DI for boundaries**: `coordinatorDeps` stored on `TriggerRouter`, not constructed inside it.
|
|
41
|
+
- **Immutability by default**: New fields are `private readonly`.
|
|
42
|
+
- **YAGNI**: Only the wiring is added; no production deps implementation.
|
|
43
|
+
- **Errors as data**: Absent deps produce a logged warning and graceful fallback, never a thrown error.
|
|
44
|
+
- **Determinism**: Fallback behavior (when deps absent) is always the same -- log warn + skip adaptive dispatch.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Invariants
|
|
49
|
+
|
|
50
|
+
1. At-least-once delivery ordering is preserved: adaptive dispatch fires BEFORE `appendQueuePollLog` records the event (same ordering as the `dispatch()` call it replaces).
|
|
51
|
+
|
|
52
|
+
2. Fire-and-forget semantics preserved: `doPollGitHubQueue` does not await the adaptive dispatch outcome.
|
|
53
|
+
|
|
54
|
+
3. Fallback to `dispatch()` when `dispatchAdaptivePipeline` is absent on the router (type guard false).
|
|
55
|
+
|
|
56
|
+
4. `dispatchAdaptivePipeline` never throws: any internal errors are caught and logged; the method returns `PipelineOutcome { kind: 'escalated' }` on unexpected errors.
|
|
57
|
+
|
|
58
|
+
5. Existing `TriggerRouter` tests pass without modification -- new constructor params are optional.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Selected Approach
|
|
63
|
+
|
|
64
|
+
**Hybrid approach (Candidate A refined):**
|
|
65
|
+
|
|
66
|
+
### In `trigger-router.ts`
|
|
67
|
+
|
|
68
|
+
1. Add to constructor parameters (after `steerRegistry?`):
|
|
69
|
+
- `coordinatorDeps?: AdaptiveCoordinatorDeps`
|
|
70
|
+
- `modeExecutors?: ModeExecutors`
|
|
71
|
+
|
|
72
|
+
2. Store as `private readonly _coordinatorDeps?: AdaptiveCoordinatorDeps` and `private readonly _modeExecutors?: ModeExecutors`.
|
|
73
|
+
|
|
74
|
+
3. Change `dispatchAdaptivePipeline` signature from:
|
|
75
|
+
```typescript
|
|
76
|
+
async dispatchAdaptivePipeline(
|
|
77
|
+
goal: string,
|
|
78
|
+
workspace: string,
|
|
79
|
+
coordinatorDeps: AdaptiveCoordinatorDeps,
|
|
80
|
+
modeExecutors: ModeExecutors,
|
|
81
|
+
context?: Readonly<Record<string, unknown>>,
|
|
82
|
+
)
|
|
83
|
+
```
|
|
84
|
+
to:
|
|
85
|
+
```typescript
|
|
86
|
+
async dispatchAdaptivePipeline(
|
|
87
|
+
goal: string,
|
|
88
|
+
workspace: string,
|
|
89
|
+
context?: Readonly<Record<string, unknown>>,
|
|
90
|
+
coordinatorDeps?: AdaptiveCoordinatorDeps,
|
|
91
|
+
modeExecutors?: ModeExecutors,
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
Effective deps = caller-provided (override) OR stored fields (default).
|
|
95
|
+
|
|
96
|
+
4. Inside `dispatchAdaptivePipeline`, if no effective deps are available:
|
|
97
|
+
- Log `console.warn('[TriggerRouter] dispatchAdaptivePipeline called but coordinatorDeps not injected -- adaptive dispatch disabled. Inject coordinatorDeps in TriggerRouter constructor to activate.')`
|
|
98
|
+
- Return `{ kind: 'escalated', escalationReason: { phase: 'dispatch', reason: 'coordinatorDeps not injected' } }` as PipelineOutcome.
|
|
99
|
+
|
|
100
|
+
5. Update JSDoc to document:
|
|
101
|
+
- The `@see feat/github-queue-poll` comment (already there -- keep it)
|
|
102
|
+
- The caller-override vs. stored-field precedence rule
|
|
103
|
+
- That absent deps produce a warn + escalated outcome (not a throw)
|
|
104
|
+
|
|
105
|
+
### In `polling-scheduler.ts`
|
|
106
|
+
|
|
107
|
+
1. In `doPollGitHubQueue`, replace line:
|
|
108
|
+
```typescript
|
|
109
|
+
this.router.dispatch(workflowTrigger);
|
|
110
|
+
```
|
|
111
|
+
with:
|
|
112
|
+
```typescript
|
|
113
|
+
if ('dispatchAdaptivePipeline' in this.router && typeof (this.router as { dispatchAdaptivePipeline?: unknown }).dispatchAdaptivePipeline === 'function') {
|
|
114
|
+
void (this.router as { dispatchAdaptivePipeline: (goal: string, workspace: string, context?: Readonly<Record<string, unknown>>) => Promise<unknown> }).dispatchAdaptivePipeline(
|
|
115
|
+
workflowTrigger.goal,
|
|
116
|
+
workflowTrigger.workspacePath,
|
|
117
|
+
workflowTrigger.context,
|
|
118
|
+
);
|
|
119
|
+
} else {
|
|
120
|
+
this.router.dispatch(workflowTrigger);
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
Note: The type cast is necessary because `TriggerRouter` is imported as a type (not class) in `polling-scheduler.ts`. The duck-type check is the runtime safety mechanism.
|
|
124
|
+
|
|
125
|
+
2. Add a log line after the adaptive dispatch call:
|
|
126
|
+
```typescript
|
|
127
|
+
console.log(`[QueuePoll] dispatched via adaptivePipeline: goal="${workflowTrigger.goal.slice(0, 80)}"`);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Runner-Up
|
|
133
|
+
|
|
134
|
+
**Candidate B** (PollingScheduler carries coordinator deps) -- rejected because it places infrastructure-level deps at the wrong boundary. PollingScheduler should only know about the router interface.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Vertical Slices
|
|
139
|
+
|
|
140
|
+
### Slice 1: Modify `trigger-router.ts`
|
|
141
|
+
- Add optional constructor params `coordinatorDeps?` and `modeExecutors?`
|
|
142
|
+
- Store as `private readonly` fields
|
|
143
|
+
- Simplify `dispatchAdaptivePipeline` signature (context before deps)
|
|
144
|
+
- Add absent-deps fallback with `console.warn` and `escalated` return
|
|
145
|
+
- Update JSDoc
|
|
146
|
+
|
|
147
|
+
**Done when**: `npm run build` succeeds; `dispatchAdaptivePipeline` method compiles with new signature.
|
|
148
|
+
|
|
149
|
+
### Slice 2: Modify `polling-scheduler.ts`
|
|
150
|
+
- Replace `this.router.dispatch(workflowTrigger)` with type-guard + adaptive dispatch call + fallback
|
|
151
|
+
- Add log line for adaptive dispatch path
|
|
152
|
+
|
|
153
|
+
**Done when**: `npm run build` succeeds; existing `polling-scheduler.test.ts` tests pass (mock router only has `dispatch`, type guard falls back -- no regressions).
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Test Design
|
|
158
|
+
|
|
159
|
+
**No new test files.** The task says "small targeted change" and the adaptive path requires `coordinatorDeps` injection which the existing test infrastructure doesn't support. Existing tests verify:
|
|
160
|
+
- The fallback path: mock router only has `dispatch()`, type guard is `false`, `dispatch()` is called (existing tests already verify this behavior -- they just don't explicitly test the type guard).
|
|
161
|
+
|
|
162
|
+
**Manual verification**: After implementation, verify that `npm run build` and `npx vitest run` both pass cleanly.
|
|
163
|
+
|
|
164
|
+
**Follow-up test** (not in this PR): Add `dispatchAdaptivePipeline` to the mock router in `polling-scheduler.test.ts` and assert it's called for queue-poll sessions.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Risk Register
|
|
169
|
+
|
|
170
|
+
| Risk | Likelihood | Impact | Mitigation |
|
|
171
|
+
|------|-----------|--------|-----------|
|
|
172
|
+
| Type cast for duck-type check causes TS error | Low | Build failure | Use `as { dispatchAdaptivePipeline?: unknown }` minimal cast; verify in Slice 2 |
|
|
173
|
+
| `dispatchAdaptivePipeline` return type mismatch after signature change | Low | Build failure | Verify `ReturnType<typeof runAdaptivePipeline>` still matches `Promise<PipelineOutcome>` |
|
|
174
|
+
| Missing `(this.router as TriggerRouter).dispatchAdaptivePipeline` direct call | None | N/A | The import in `polling-scheduler.ts` is `import type { TriggerRouter }` -- the type guard is the correct approach |
|
|
175
|
+
| Existing trigger-router.test.ts fails after constructor change | Low | Test failure | New params are optional -- all existing test construction sites pass `undefined` implicitly |
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## PR Packaging Strategy
|
|
180
|
+
|
|
181
|
+
**Single PR**: `fix/connect-adaptive-dispatch`
|
|
182
|
+
|
|
183
|
+
Commit: `fix(trigger): connect queue poll dispatch to adaptive coordinator`
|
|
184
|
+
|
|
185
|
+
All changes in one commit: `trigger-router.ts` + `polling-scheduler.ts` + design docs.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Philosophy Alignment
|
|
190
|
+
|
|
191
|
+
| Principle | Status | Reason |
|
|
192
|
+
|-----------|--------|--------|
|
|
193
|
+
| DI for boundaries | Satisfied | `coordinatorDeps` injected at construction, not built internally |
|
|
194
|
+
| Immutability by default | Satisfied | New fields are `private readonly` |
|
|
195
|
+
| YAGNI with discipline | Satisfied | No production deps wiring; only the call path is connected |
|
|
196
|
+
| Errors as data | Satisfied | Absent deps return `escalated` PipelineOutcome, never throw |
|
|
197
|
+
| Type safety first | Minor tension | Duck-type guard is runtime check; acceptable for mock compatibility |
|
|
198
|
+
| Make illegal states unrepresentable | Minor tension | `coordinatorDeps` and `modeExecutors` are independent optionals; acceptable per existing router pattern |
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Estimation
|
|
203
|
+
|
|
204
|
+
- **`unresolvedUnknownCount`**: 0
|
|
205
|
+
- **`planConfidenceBand`**: High
|
|
206
|
+
- **`estimatedPRCount`**: 1
|
|
207
|
+
- **`followUpTickets`**:
|
|
208
|
+
1. Wire `AdaptiveCoordinatorDeps` into `TriggerRouter` constructor in `trigger-listener.ts` bootstrap
|
|
209
|
+
2. Add test for `dispatchAdaptivePipeline` call path in `polling-scheduler.test.ts`
|
|
@@ -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.
|