@exaudeus/workrail 3.41.0 → 3.42.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-worktrain.js +40 -11
- package/dist/console-ui/assets/{index-CQt4UhPB.js → index-DwfWMKvv.js} +1 -1
- package/dist/console-ui/index.html +1 -1
- package/dist/context-assembly/deps.d.ts +8 -0
- package/dist/context-assembly/deps.js +2 -0
- package/dist/context-assembly/index.d.ts +6 -0
- package/dist/context-assembly/index.js +50 -0
- package/dist/context-assembly/infra.d.ts +3 -0
- package/dist/context-assembly/infra.js +154 -0
- package/dist/context-assembly/types.d.ts +30 -0
- package/dist/context-assembly/types.js +2 -0
- package/dist/coordinators/pr-review.d.ts +3 -1
- package/dist/coordinators/pr-review.js +25 -4
- package/dist/daemon/workflow-runner.js +11 -0
- package/dist/domain/execution/state.d.ts +6 -6
- package/dist/manifest.json +64 -32
- package/dist/mcp/handlers/v2-workflow.d.ts +2 -2
- package/dist/mcp/output-schemas.d.ts +234 -234
- package/dist/mcp/tools.d.ts +2 -2
- package/dist/mcp/v2/tools.d.ts +24 -24
- package/dist/v2/durable-core/schemas/artifacts/assessment.d.ts +2 -2
- package/dist/v2/durable-core/schemas/artifacts/coordinator-signal.d.ts +2 -2
- package/dist/v2/durable-core/schemas/artifacts/loop-control.d.ts +6 -6
- package/dist/v2/durable-core/schemas/artifacts/review-verdict.d.ts +6 -6
- package/dist/v2/durable-core/schemas/compiled-workflow/index.d.ts +56 -56
- package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.d.ts +83 -83
- package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +1024 -1024
- package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +2336 -2336
- package/dist/v2/durable-core/schemas/session/dag-topology.d.ts +6 -6
- package/dist/v2/durable-core/schemas/session/events.d.ts +339 -339
- package/dist/v2/durable-core/schemas/session/gaps.d.ts +30 -30
- package/dist/v2/durable-core/schemas/session/manifest.d.ts +6 -6
- package/dist/v2/durable-core/schemas/session/outputs.d.ts +8 -8
- package/dist/v2/durable-core/schemas/session/validation-event.d.ts +3 -3
- package/docs/design/context-assembly-design-candidates.md +199 -0
- package/docs/design/context-assembly-implementation-plan.md +211 -0
- package/docs/design/context-assembly-review-findings.md +112 -0
- package/docs/ideas/backlog.md +64 -0
- package/package.json +1 -1
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Implementation Plan: Context Assembly Layer v1
|
|
2
|
+
|
|
3
|
+
*Authored during coding-task-workflow-agentic session | 2026-04-19*
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Problem Statement
|
|
8
|
+
|
|
9
|
+
When the PR review coordinator dispatches a session, it passes only a goal string.
|
|
10
|
+
The agent spends its first 2-3 turns re-establishing context (re-reading PR diff,
|
|
11
|
+
starting cold with no memory of prior reviews). Before spawning each session, the
|
|
12
|
+
coordinator should assemble a context bundle (git diff summary + prior session notes)
|
|
13
|
+
and inject it into the system prompt via the existing `trigger.context` map.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Acceptance Criteria
|
|
18
|
+
|
|
19
|
+
1. `npm run build` compiles clean with zero TypeScript errors
|
|
20
|
+
2. `npx vitest run tests/unit/context-assembly.test.ts` -- all 5 tests pass
|
|
21
|
+
3. `npx vitest run` -- no regressions in full test suite
|
|
22
|
+
4. New module `src/context-assembly/` exists with 4 files
|
|
23
|
+
5. `CoordinatorDeps.contextAssembler?` optional field present
|
|
24
|
+
6. `CoordinatorDeps.spawnSession` accepts optional 4th `context?` arg
|
|
25
|
+
7. `buildSystemPrompt()` injects `## Prior Context` section when `assembledContextSummary` present in trigger context
|
|
26
|
+
8. `src/cli-worktrain.ts` wires `contextAssembler` and forwards `context` through HTTP body
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Non-Goals
|
|
31
|
+
|
|
32
|
+
1. No URL fetching from PR description bodies (v2)
|
|
33
|
+
2. No full git diff content -- `--stat` output only
|
|
34
|
+
3. No changes to `src/mcp/` or `src/v2/durable-core/`
|
|
35
|
+
4. No knowledge graph source (`ts-morph` stays in devDependencies)
|
|
36
|
+
5. No per-coordinator custom renderers
|
|
37
|
+
6. No context window budget trimming
|
|
38
|
+
7. No context assembly in `TriggerRouter.route()`
|
|
39
|
+
8. No HTTP endpoint schema changes
|
|
40
|
+
9. No workspace-scoped session filtering (v1 returns global sessions)
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Philosophy-Driven Constraints
|
|
45
|
+
|
|
46
|
+
- All interface fields `readonly` (immutability by default)
|
|
47
|
+
- `Result<T,E>` (`kind: 'ok'|'err'`) for all new code; bridge from neverthrow at `infra.ts` boundary
|
|
48
|
+
- Factory function `createContextAssembler()`, not a class
|
|
49
|
+
- `renderContextBundle()` is pure (no I/O)
|
|
50
|
+
- `ContextAssemblerDeps` interface contains all I/O; core has zero direct imports of `fs`/`execFile`
|
|
51
|
+
- Tests: fake deps, no `vi.mock()`, vitest `describe/it/expect`
|
|
52
|
+
- Import paths use `.js` extension (TypeScript ESM)
|
|
53
|
+
- JSDoc `WHY` comments on non-obvious decisions
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Invariants
|
|
58
|
+
|
|
59
|
+
1. **Partial failure**: `gitDiff` and `priorSessionNotes` fail independently; session always starts
|
|
60
|
+
2. **Zero agent turn cost**: Context injected in system prompt before turn 1
|
|
61
|
+
3. **Coordinator-controlled opt-in**: Only coordinators calling `assembler.assemble()` get enriched context
|
|
62
|
+
4. **Prior notes recency limit**: At most 3 prior sessions
|
|
63
|
+
5. **Git summary size**: File names and change counts only (no full diff)
|
|
64
|
+
6. **No engine/MCP changes**: `src/v2/durable-core/` and `src/mcp/` untouched
|
|
65
|
+
7. **Optional contextAssembler**: Backward-compatible -- all existing `CoordinatorDeps` fakes work
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Selected Approach
|
|
70
|
+
|
|
71
|
+
Use 4 new files in `src/context-assembly/` + 3 modified files, exactly as specified in
|
|
72
|
+
the pitch. `infra.ts` uses a direct `SessionEventLogReadonlyStorePortV2` adapter
|
|
73
|
+
(implementing only `load()` and `loadValidatedPrefix()` via `fs.promises` + existing Zod
|
|
74
|
+
schemas) instead of `LocalSessionEventLogStoreV2`. This eliminates the need for
|
|
75
|
+
`FileSystemPortV2` and `Sha256PortV2` shims.
|
|
76
|
+
|
|
77
|
+
**Runner-up**: Use `LocalSessionEventLogStoreV2` with write stubs. Rejected: write stubs
|
|
78
|
+
partially violate the interface contract even though they're provably unreachable.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Vertical Slices
|
|
83
|
+
|
|
84
|
+
### Slice 1: New module `src/context-assembly/` (4 files)
|
|
85
|
+
|
|
86
|
+
**Files to create**:
|
|
87
|
+
- `src/context-assembly/types.ts` (~60 LOC) - AssemblyTask, SessionNote, ContextBundle, RenderOpts, ContextAssembler interface
|
|
88
|
+
- `src/context-assembly/deps.ts` (~35 LOC) - ContextAssemblerDeps interface (execGit, execGh, listRecentSessions, nowIso)
|
|
89
|
+
- `src/context-assembly/index.ts` (~80 LOC) - createContextAssembler(), renderContextBundle(), private helpers
|
|
90
|
+
- `src/context-assembly/infra.ts` (~80 LOC) - createListRecentSessions() adapter
|
|
91
|
+
|
|
92
|
+
**Done when**: All 4 files compile clean in isolation
|
|
93
|
+
|
|
94
|
+
**Key implementation details**:
|
|
95
|
+
- `types.ts`: import `Result` from `'../runtime/result.js'`
|
|
96
|
+
- `index.ts`: factory function `createContextAssembler(deps)`, pure `renderContextBundle(bundle, _opts?)`
|
|
97
|
+
- `infra.ts`: `SessionEventLogReadonlyStorePortV2` adapter using `fs.promises.readFile` + `ManifestRecordV1Schema` + `DomainEventV1Schema`; `LocalDataDirV2(process.env)` + `LocalDirectoryListingV2(directoryListingOpsAdapter)` wired into `LocalSessionSummaryProviderV2`
|
|
98
|
+
- `infra.ts`: `_workspacePath` named with leading underscore (intentional non-use in v1)
|
|
99
|
+
- Bridge: `const result = await provider.loadHealthySummaries(); if (result.isErr()) return err(...);`
|
|
100
|
+
|
|
101
|
+
### Slice 2: Modify `src/coordinators/pr-review.ts`
|
|
102
|
+
|
|
103
|
+
**Changes**:
|
|
104
|
+
1. Add `contextAssembler?: ContextAssembler` field to `CoordinatorDeps` (after `spawnSession`, before `awaitSessions`)
|
|
105
|
+
2. Extend `spawnSession` signature: add optional 4th arg `context?: Readonly<Record<string, unknown>>`
|
|
106
|
+
3. Add context assembly block before `spawnSession` call in `runPrReviewCoordinator` for-loop
|
|
107
|
+
4. Forward same `spawnContext` to re-review spawn (~line 1265)
|
|
108
|
+
5. Do NOT add context assembly to fix-agent spawn
|
|
109
|
+
|
|
110
|
+
**Done when**: File compiles clean; existing tests still pass
|
|
111
|
+
|
|
112
|
+
### Slice 3: Modify `src/daemon/workflow-runner.ts`
|
|
113
|
+
|
|
114
|
+
**Change**: Insert 6-line block in `buildSystemPrompt()` BEFORE the `referenceUrls` block
|
|
115
|
+
|
|
116
|
+
**Done when**: File compiles clean; `buildSystemPrompt` tests (if any) still pass
|
|
117
|
+
|
|
118
|
+
### Slice 4: Modify `src/cli-worktrain.ts`
|
|
119
|
+
|
|
120
|
+
**Changes**:
|
|
121
|
+
1. Add imports for `createContextAssembler` and `createListRecentSessions`
|
|
122
|
+
2. Extend `spawnSession` lambda to accept optional 4th `context?` arg and include it in JSON body
|
|
123
|
+
3. Add `contextAssembler: createContextAssembler({...})` to deps object (after `spawnSession`)
|
|
124
|
+
4. Wire `execGit`, `execGh` inline using `promisify(execFile)`
|
|
125
|
+
|
|
126
|
+
**Done when**: File compiles clean
|
|
127
|
+
|
|
128
|
+
### Slice 5: Tests
|
|
129
|
+
|
|
130
|
+
**File**: `tests/unit/context-assembly.test.ts`
|
|
131
|
+
|
|
132
|
+
**Tests**:
|
|
133
|
+
1. `assemble()` with both sources succeeding -- returns bundle with `kind: 'ok'` on both fields
|
|
134
|
+
2. `assemble()` with gitSummary failing -- priorNotes still returns `ok([])`; gitDiff returns `kind: 'err'`
|
|
135
|
+
3. `renderBundle()` produces correct markdown sections (headings, git diff in code block)
|
|
136
|
+
4. `renderBundle()` with all-failed bundle -- returns empty string
|
|
137
|
+
5. `listRecentSessions` with no sessions directory -- returns `ok([])`
|
|
138
|
+
|
|
139
|
+
**Done when**: All 5 tests pass with `npx vitest run tests/unit/context-assembly.test.ts`
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Test Design
|
|
144
|
+
|
|
145
|
+
### Fake deps for context assembler
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
function makeFakeDeps(overrides: Partial<ContextAssemblerDeps> = {}): ContextAssemblerDeps {
|
|
149
|
+
return {
|
|
150
|
+
execGit: async () => ({ kind: 'ok', value: 'src/foo.ts | 5 ++\n' }),
|
|
151
|
+
execGh: async () => ({ kind: 'err', error: 'gh not available' }),
|
|
152
|
+
listRecentSessions: async () => ({ kind: 'ok', value: [] }),
|
|
153
|
+
nowIso: () => '2026-04-19T00:00:00.000Z',
|
|
154
|
+
...overrides,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Test for no sessions directory
|
|
160
|
+
|
|
161
|
+
Uses a real temp directory (with no sessions subdirectory) to test the graceful `ok([])` return.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Risk Register
|
|
166
|
+
|
|
167
|
+
| Risk | Likelihood | Impact | Mitigation |
|
|
168
|
+
|---|---|---|---|
|
|
169
|
+
| `infra.ts` SessionEventLogReadonlyStorePortV2 adapter has parsing bugs | Low | Medium | Tests verify behavior with fake data; manifest format well-documented |
|
|
170
|
+
| `spawnSession` 4th arg breaks existing tests | Very Low | Low | Optional arg; TypeScript enforces backward compat |
|
|
171
|
+
| `contextAssembler` not optional breaks existing tests | Very Low | Low | `?` makes it optional; existing fakes compile without it |
|
|
172
|
+
| Full test suite regressions | Very Low | High | Run `npx vitest run` before PR |
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## PR Packaging Strategy
|
|
177
|
+
|
|
178
|
+
**Single PR**: `feat/context-assembly-layer`
|
|
179
|
+
|
|
180
|
+
All 7 file changes ship together. They form a single coherent feature -- the 4 new
|
|
181
|
+
files are meaningless without the 3 modifications, and the modifications are safe
|
|
182
|
+
with or without the new files.
|
|
183
|
+
|
|
184
|
+
**Commit message**: `feat(coordinator): add context assembly layer -- git summary and prior session notes`
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Philosophy Alignment Per Slice
|
|
189
|
+
|
|
190
|
+
| Slice | Principle | Status |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| Slice 1 (types) | Make illegal states unrepresentable (discriminated union) | Satisfied |
|
|
193
|
+
| Slice 1 (index) | Functional/declarative (factory fn, pure render) | Satisfied |
|
|
194
|
+
| Slice 1 (infra) | DI for boundaries (all I/O behind interface) | Satisfied |
|
|
195
|
+
| Slice 1 (infra) | Architectural fixes over patches (reuse projection code) | Satisfied |
|
|
196
|
+
| Slice 2 (coordinator) | Errors are data (Result<T,E>) | Satisfied |
|
|
197
|
+
| Slice 2 (coordinator) | Backward compatibility via optional fields | Satisfied |
|
|
198
|
+
| Slice 3 (workflow-runner) | Validate at boundaries (type check before inject) | Satisfied |
|
|
199
|
+
| Slice 5 (tests) | Prefer fakes over mocks | Satisfied |
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Open Questions (Residual)
|
|
204
|
+
|
|
205
|
+
None. All questions were resolved during design review:
|
|
206
|
+
1. `ManifestRecordV1Schema` and `DomainEventV1Schema` -- confirmed exported
|
|
207
|
+
2. `asSessionId` -- confirmed exported from ids/index.ts
|
|
208
|
+
3. Re-review spawn context -- confirmed: forward same `spawnContext` from outer scope
|
|
209
|
+
|
|
210
|
+
**`unresolvedUnknownCount`**: 0
|
|
211
|
+
**`planConfidenceBand`**: High
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Design Review Findings: Context Assembly Layer v1
|
|
2
|
+
|
|
3
|
+
*Generated during coding-task-workflow-agentic session | 2026-04-19*
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Tradeoff Review
|
|
8
|
+
|
|
9
|
+
| Tradeoff | Verdict | Condition for Failure |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| FileSystemPortV2 write stubs | RESOLVED -- switching to direct SessionEventLogReadonlyStorePortV2 adapter | N/A |
|
|
12
|
+
| Inline DI wiring in infra.ts | Acceptable -- explicitly endorsed by pitch R1 mitigation | N/A |
|
|
13
|
+
| Global session list (no workspace filtering) | Accepted -- v2 improvement documented in pitch | Multiple simultaneous workspaces with unrelated sessions |
|
|
14
|
+
| Promise.all partial failure | Adequately handled -- each source returns Result, try/catch in infra | N/A |
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Failure Mode Review
|
|
19
|
+
|
|
20
|
+
| Failure Mode | Covered By | Residual Risk |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| Write stubs called | Outer try/catch in createListRecentSessions | Low -- FM1 resolved by switching adapter |
|
|
23
|
+
| Git commands fail | execGit/execGh return err(), fallback chain | None |
|
|
24
|
+
| Sessions dir missing | LocalDirectoryListingV2 returns [] for FS_NOT_FOUND | None |
|
|
25
|
+
| Corrupt session data | LocalSessionSummaryProviderV2 graceful skip | None |
|
|
26
|
+
| spawnSession backward compat | Optional 4th arg | None |
|
|
27
|
+
| contextAssembler not provided | Optional field + if-guard in coordinator | None |
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Runner-Up / Simpler Alternative Review
|
|
32
|
+
|
|
33
|
+
**Runner-up** (Candidate B: raw fs.promises) has no elements worth borrowing into the
|
|
34
|
+
selected design.
|
|
35
|
+
|
|
36
|
+
**Simpler variant identified**: Use a direct `SessionEventLogReadonlyStorePortV2`
|
|
37
|
+
adapter in `infra.ts` instead of `LocalSessionEventLogStoreV2`. This eliminates all
|
|
38
|
+
`FileSystemPortV2` and `Sha256PortV2` stubs while still reusing `LocalSessionSummaryProviderV2`
|
|
39
|
+
and the full projection pipeline. The adapter implements only `load()` and
|
|
40
|
+
`loadValidatedPrefix()` using `fs.promises` + existing Zod schemas (`ManifestRecordV1Schema`,
|
|
41
|
+
`DomainEventV1Schema`). Approximately 40 lines. This is strictly cleaner.
|
|
42
|
+
|
|
43
|
+
**Recommendation**: Use the simpler `SessionEventLogReadonlyStorePortV2` adapter approach.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Philosophy Alignment
|
|
48
|
+
|
|
49
|
+
| Principle | Alignment |
|
|
50
|
+
|---|---|
|
|
51
|
+
| Errors are data | STRONG -- Result<T,E> throughout |
|
|
52
|
+
| DI for boundaries | STRONG -- ContextAssemblerDeps, inline construction accepted for separate process |
|
|
53
|
+
| Prefer fakes over mocks | STRONG -- test spec uses fake deps |
|
|
54
|
+
| Immutability by default | STRONG -- readonly everywhere |
|
|
55
|
+
| Make illegal states unrepresentable | STRONG -- AssemblyTask discriminated union |
|
|
56
|
+
| Functional/declarative | STRONG -- factory functions, pure renderContextBundle |
|
|
57
|
+
| YAGNI | MILD TENSION -- empty RenderOpts interface (accepted, documented) |
|
|
58
|
+
| Architectural fixes over patches | STRONG with adapter approach |
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Findings
|
|
63
|
+
|
|
64
|
+
### YELLOW: Empty RenderOpts interface
|
|
65
|
+
|
|
66
|
+
**Severity**: Yellow (minor)
|
|
67
|
+
|
|
68
|
+
**Finding**: `RenderOpts` is an empty interface in v1. TypeScript `{}` and `interface RenderOpts {}` are equivalent. The empty interface is only useful as a future extension point.
|
|
69
|
+
|
|
70
|
+
**Assessment**: Acceptable -- the pitch explicitly documents this as an intentional v1 placeholder. No action needed.
|
|
71
|
+
|
|
72
|
+
### YELLOW: Global session filtering (no workspace scope)
|
|
73
|
+
|
|
74
|
+
**Severity**: Yellow (minor, known)
|
|
75
|
+
|
|
76
|
+
**Finding**: `listRecentSessions` returns the 3 most recent sessions globally, not scoped to the coordinator's workspace path. For users running multiple workspaces, prior notes from unrelated workspaces may appear.
|
|
77
|
+
|
|
78
|
+
**Assessment**: Acceptable for v1 -- pitch explicitly documents this as a known limitation. The `_workspacePath` parameter should be named with a leading `_` to signal intentional non-use.
|
|
79
|
+
|
|
80
|
+
### GREEN: All other design elements
|
|
81
|
+
|
|
82
|
+
Tradeoffs, failure modes, and philosophy alignment are all sound. No RED or ORANGE findings.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Recommended Revisions
|
|
87
|
+
|
|
88
|
+
1. **Use direct `SessionEventLogReadonlyStorePortV2` adapter** in `infra.ts` instead of
|
|
89
|
+
`LocalSessionEventLogStoreV2`. Eliminates all write stubs. Implements `load()` using
|
|
90
|
+
`fs.promises.readFile` + `ManifestRecordV1Schema` + `DomainEventV1Schema` parsing.
|
|
91
|
+
Approximately 40 lines.
|
|
92
|
+
|
|
93
|
+
2. **Name unused parameter** `_workspacePath` in `createListRecentSessions` to signal
|
|
94
|
+
intentional non-use.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Residual Concerns
|
|
99
|
+
|
|
100
|
+
1. **Import path for Zod schemas**: `ManifestRecordV1Schema` and `DomainEventV1Schema`
|
|
101
|
+
must be importable from `src/v2/durable-core/schemas/session/index.ts`. Verify before
|
|
102
|
+
implementing that these schemas are exported.
|
|
103
|
+
|
|
104
|
+
2. **`asSessionId` usage**: The `SessionEventLogReadonlyStorePortV2.load()` adapter
|
|
105
|
+
receives a `SessionId` branded type. When reading session directories and mapping
|
|
106
|
+
entry names to `SessionId`, the `asSessionId` import from
|
|
107
|
+
`src/v2/durable-core/ids/index.ts` must be used correctly.
|
|
108
|
+
|
|
109
|
+
3. **Neverthrow ResultAsync wrapping**: The adapter must return neverthrow `ResultAsync`
|
|
110
|
+
objects (using `okAsync`/`errAsync` from neverthrow), not the custom `Result` type.
|
|
111
|
+
The bridge from neverthrow to custom Result happens in the `createListRecentSessions`
|
|
112
|
+
function via `const result = await resultAsync; if (result.isErr()) return err(...)`.
|
package/docs/ideas/backlog.md
CHANGED
|
@@ -6183,3 +6183,67 @@ The daemon tool approach is only better for ad-hoc mid-session queries the agent
|
|
|
6183
6183
|
### Anti-pattern to avoid
|
|
6184
6184
|
|
|
6185
6185
|
Adding knowledge graph calls directly into `pr-review.ts` or any other coordinator script. That immediately creates the god class we're trying to avoid and couples the orchestration layer to a specific context source.
|
|
6186
|
+
|
|
6187
|
+
---
|
|
6188
|
+
|
|
6189
|
+
## Scheduled tasks (Apr 19, 2026)
|
|
6190
|
+
|
|
6191
|
+
**The idea:** WorkTrain runs tasks on a schedule -- not triggered by an external event, but by time. "Every Monday morning, run the code health scan." "Every night at 2am, check for new GitHub issues and triage them." "First of the month, run the production readiness audit."
|
|
6192
|
+
|
|
6193
|
+
### Why this matters for the autonomous pipeline vision
|
|
6194
|
+
|
|
6195
|
+
The full autonomous pipeline (prioritize → discover → shape → implement → test → PR → review → fix → merge) needs a way to start without a human pushing a button. Scheduled tasks are the trigger layer for proactive, time-driven work. Without them, WorkTrain is purely reactive -- it only acts when a webhook fires or a human dispatches it.
|
|
6196
|
+
|
|
6197
|
+
### What exists today
|
|
6198
|
+
|
|
6199
|
+
The trigger system (`src/trigger/`) supports `generic` (webhook) and polling providers (`gitlab_poll`, `github_issues_poll`, `github_prs_poll`). There is no native cron/schedule provider. The workaround today is OS crontab calling `curl` to fire a webhook.
|
|
6200
|
+
|
|
6201
|
+
### What to build
|
|
6202
|
+
|
|
6203
|
+
A `schedule` provider in triggers.yml:
|
|
6204
|
+
|
|
6205
|
+
```yaml
|
|
6206
|
+
triggers:
|
|
6207
|
+
- id: weekly-code-health
|
|
6208
|
+
provider: schedule
|
|
6209
|
+
cron: "0 9 * * 1" # every Monday at 9am
|
|
6210
|
+
workflowId: architecture-scalability-audit
|
|
6211
|
+
workspacePath: /path/to/repo
|
|
6212
|
+
goal: "Run weekly code health scan -- identify coupling violations, complexity hotspots, and performance anti-patterns introduced this week"
|
|
6213
|
+
|
|
6214
|
+
- id: nightly-issue-triage
|
|
6215
|
+
provider: schedule
|
|
6216
|
+
cron: "0 2 * * *" # every night at 2am
|
|
6217
|
+
workflowId: wr.discovery
|
|
6218
|
+
workspacePath: /path/to/repo
|
|
6219
|
+
goal: "Review open GitHub issues created in the last 24 hours and triage them: classify severity, identify duplicates, suggest which to prioritize"
|
|
6220
|
+
|
|
6221
|
+
- id: backlog-next-task
|
|
6222
|
+
provider: schedule
|
|
6223
|
+
cron: "0 8 * * 1-5" # weekday mornings at 8am
|
|
6224
|
+
workflowId: coding-task-workflow-agentic
|
|
6225
|
+
workspacePath: /path/to/repo
|
|
6226
|
+
goal: "Pick the highest-priority unstarted task from docs/ideas/backlog.md and implement it"
|
|
6227
|
+
```
|
|
6228
|
+
|
|
6229
|
+
### Key design decisions
|
|
6230
|
+
|
|
6231
|
+
- **Cron syntax**: standard 5-field cron (`min hour dom month dow`). Parsed by `node-cron` or equivalent -- already a pattern in the codebase (backlog mentions cron).
|
|
6232
|
+
- **Timezone**: configurable per trigger, defaults to system timezone. Important for "weekday morning" schedules that need to fire in the user's timezone.
|
|
6233
|
+
- **Missed runs**: if the daemon was down when a scheduled run should have fired, it does NOT catch up on missed runs by default. "Run at 9am Monday" means "run the next time 9am Monday arrives." Optional `catchUp: true` flag for cases where missing a run should be recovered.
|
|
6234
|
+
- **Overlap prevention**: if a scheduled run fires while the previous run is still active, it should be skipped (not queued). A `coding-task` that takes 2 hours should not spawn a second instance at the next cron tick.
|
|
6235
|
+
- **Manual trigger**: `worktrain run schedule <trigger-id>` to fire a scheduled trigger immediately without waiting for the cron time. Useful for testing.
|
|
6236
|
+
|
|
6237
|
+
### Integration with the autonomous pipeline
|
|
6238
|
+
|
|
6239
|
+
Scheduled tasks are the entry point for fully autonomous work:
|
|
6240
|
+
- "Every weekday morning, pick the next backlog item and run the full pipeline" -- this is how WorkTrain improves WorkTrain without any human input.
|
|
6241
|
+
- "Every time a PR is opened, run the MR review pipeline" -- this is github_prs_poll, already exists.
|
|
6242
|
+
- "Every Monday, run the architecture audit and file GitHub issues for findings" -- new scheduled capability.
|
|
6243
|
+
|
|
6244
|
+
### Implementation notes
|
|
6245
|
+
|
|
6246
|
+
- The `PollingScheduler` in `src/trigger/polling-scheduler.ts` already runs time-based loops for GitLab/GitHub polling. The schedule provider would be a similar loop, using cron expression matching instead of API polling.
|
|
6247
|
+
- `node-cron` or `croner` npm package for cron expression parsing and next-fire-time calculation. Lightweight, no daemon dependencies.
|
|
6248
|
+
- Scheduled triggers have no webhook payload -- `contextMapping` is empty, `goalTemplate` uses only static text or env vars.
|
|
6249
|
+
- The schedule state (last-fired-at per trigger) persists to `~/.workrail/schedule-state.json` so the daemon can detect missed runs on restart.
|