@exaudeus/workrail 3.41.0 → 3.43.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-Sb57DW4B.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.d.ts +11 -1
- package/dist/daemon/workflow-runner.js +82 -9
- package/dist/domain/execution/state.d.ts +6 -6
- package/dist/manifest.json +76 -44
- 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/trigger/delivery-action.d.ts +2 -0
- package/dist/trigger/delivery-action.js +24 -0
- package/dist/trigger/trigger-router.js +24 -1
- package/dist/trigger/trigger-store.js +42 -0
- package/dist/trigger/types.d.ts +3 -0
- 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/adaptive-coordinator-context-candidates.md +265 -0
- package/docs/design/adaptive-coordinator-context-review.md +101 -0
- package/docs/design/adaptive-coordinator-context.md +504 -0
- package/docs/design/adaptive-coordinator-routing-candidates.md +340 -0
- package/docs/design/adaptive-coordinator-routing-design-review.md +135 -0
- package/docs/design/adaptive-coordinator-routing-review.md +156 -0
- package/docs/design/adaptive-coordinator-routing.md +660 -0
- 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-layer-design-review.md +110 -0
- package/docs/design/context-assembly-layer.md +622 -0
- package/docs/design/context-assembly-review-findings.md +112 -0
- package/docs/design/stuck-escalation-candidates.md +176 -0
- package/docs/design/stuck-escalation-design-review.md +70 -0
- package/docs/design/stuck-escalation.md +326 -0
- package/docs/design/worktrain-task-queue-candidates.md +252 -0
- package/docs/design/worktrain-task-queue-design-review.md +109 -0
- package/docs/design/worktrain-task-queue.md +443 -0
- package/docs/design/worktree-review-findings-candidates.md +101 -0
- package/docs/design/worktree-review-findings-design-review.md +65 -0
- package/docs/design/worktree-review-findings-implementation-plan.md +153 -0
- package/docs/ideas/backlog.md +212 -0
- package/package.json +3 -3
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Design Candidates: Context Assembly Layer v1
|
|
2
|
+
|
|
3
|
+
*Generated during coding-task-workflow-agentic session | 2026-04-19*
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Problem Understanding
|
|
8
|
+
|
|
9
|
+
### Core Tensions
|
|
10
|
+
|
|
11
|
+
1. **Clean DI vs. pragmatic inline construction**: `LocalSessionSummaryProviderV2` is
|
|
12
|
+
wired through the full DI container in the MCP server process. The coordinator runs
|
|
13
|
+
as a CLI in a separate process. We cannot share the DI container. The coordinator
|
|
14
|
+
composition root (`src/cli-worktrain.ts`) must construct the provider inline.
|
|
15
|
+
|
|
16
|
+
2. **Neverthrow ResultAsync vs. custom Result**: Storage layer uses neverthrow. New
|
|
17
|
+
`src/context-assembly/` uses the repo's custom `Result<T,E>` (`kind: 'ok'|'err'`).
|
|
18
|
+
The bridge must happen at the `infra.ts` boundary.
|
|
19
|
+
|
|
20
|
+
3. **Partial failure semantics**: Each source (gitDiff, priorNotes) must fail
|
|
21
|
+
independently without aborting context assembly. `Promise.all()` is safe because
|
|
22
|
+
each source always resolves to a `Result` (never throws) when implemented correctly.
|
|
23
|
+
|
|
24
|
+
4. **Backward compatibility vs. interface evolution**: Adding `contextAssembler?` as
|
|
25
|
+
optional to `CoordinatorDeps` and optional 4th arg to `spawnSession` keeps all
|
|
26
|
+
existing tests and callers working without modification.
|
|
27
|
+
|
|
28
|
+
### Likely Seam
|
|
29
|
+
|
|
30
|
+
The real seam is `CoordinatorDeps.spawnSession`. The coordinator calls this to dispatch
|
|
31
|
+
a session. Context bundle assembly belongs immediately before this call.
|
|
32
|
+
|
|
33
|
+
### What Makes This Hard
|
|
34
|
+
|
|
35
|
+
- `LocalSessionEventLogStoreV2` requires `FileSystemPortV2` (a composite of 5 sub-ports
|
|
36
|
+
including write/fd ops). But `load()` only calls `readFileUtf8`. All write methods
|
|
37
|
+
can be stubs for read-only use, but this is a partial lie against the interface contract.
|
|
38
|
+
- `LocalDirectoryListingV2` takes `DirectoryListingOpsPortV2` (separate, simpler) --
|
|
39
|
+
NOT the full `FileSystemPortV2`. These are separate ports.
|
|
40
|
+
- Two Result type systems (neverthrow vs custom) must coexist without leaking across
|
|
41
|
+
the `infra.ts` boundary.
|
|
42
|
+
- `Sha256PortV2` is injected into `LocalSessionEventLogStoreV2` but only called in
|
|
43
|
+
`append()`, not `load()`. Safe to stub.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Philosophy Constraints
|
|
48
|
+
|
|
49
|
+
From `CLAUDE.md` (system-wide agent instructions):
|
|
50
|
+
|
|
51
|
+
- **Errors are data**: Use `Result<T,E>` values. No throwing for expected failures.
|
|
52
|
+
- **DI for boundaries**: Inject I/O effects; keep core logic testable.
|
|
53
|
+
- **Prefer fakes over mocks**: Tests use realistic substitutes (no `vi.mock()`).
|
|
54
|
+
- **YAGNI with discipline**: No speculative abstractions.
|
|
55
|
+
- **Architectural fixes over patches**: Reuse canonical parsing logic.
|
|
56
|
+
- **Immutability by default**: `readonly` on all fields.
|
|
57
|
+
|
|
58
|
+
**Potential conflict**: Creating write stubs on `FileSystemPortV2` is pragmatic but
|
|
59
|
+
technically lies about the contract. The pitch (R1 mitigation) explicitly endorses this
|
|
60
|
+
approach. Acceptable given the inline-construction pattern is already acknowledged as
|
|
61
|
+
a special case.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Impact Surface
|
|
66
|
+
|
|
67
|
+
- `src/coordinators/pr-review.ts`: `CoordinatorDeps.spawnSession` (add optional 4th arg),
|
|
68
|
+
`contextAssembler?` optional field. Backward-compatible.
|
|
69
|
+
- `src/daemon/workflow-runner.ts`: `buildSystemPrompt()` reads `trigger.context['assembledContextSummary']`.
|
|
70
|
+
No signature change needed -- `trigger.context` already typed as `Readonly<Record<string,unknown>>`.
|
|
71
|
+
- `src/cli-worktrain.ts`: `spawnSession` impl forwards 4th arg; `contextAssembler` wired.
|
|
72
|
+
- All existing tests that construct `CoordinatorDeps` fakes: safe (optional fields).
|
|
73
|
+
- No changes to `src/mcp/` or `src/v2/durable-core/`.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Candidates
|
|
78
|
+
|
|
79
|
+
### Candidate A: Real local infra classes with minimal read-only shims (SELECTED)
|
|
80
|
+
|
|
81
|
+
**Summary**: Construct `LocalDataDirV2`, `LocalDirectoryListingV2`, and
|
|
82
|
+
`LocalSessionEventLogStoreV2` directly inside `infra.ts`. Create inline adapters:
|
|
83
|
+
- `DirectoryListingOpsPortV2`: wraps `fs.promises.readdir` + `stat` with `okAsync/errAsync`
|
|
84
|
+
- `FileSystemPortV2`: only `readFileUtf8` is real; write/fd methods throw 'context-assembly: write ops not supported'
|
|
85
|
+
- `Sha256PortV2`: stub that throws (never called during `load()`)
|
|
86
|
+
|
|
87
|
+
**Tensions resolved**: R1 (DI wiring scope). Uses battle-tested session loading and
|
|
88
|
+
projection code. Neverthrow/custom Result bridge stays at `infra.ts` boundary only.
|
|
89
|
+
|
|
90
|
+
**Tensions accepted**: Write stubs on `FileSystemPortV2` are a partial lie.
|
|
91
|
+
|
|
92
|
+
**Boundary solved at**: `src/context-assembly/infra.ts` -- coordinator composition root.
|
|
93
|
+
|
|
94
|
+
**Why this boundary is best-fit**: The coordinator is already a composition root
|
|
95
|
+
(`src/cli-worktrain.ts` wires all real deps). `infra.ts` is a thin adapter layer that
|
|
96
|
+
belongs exactly here.
|
|
97
|
+
|
|
98
|
+
**Failure mode**: If `LocalSessionEventLogStoreV2.load()` ever internally calls a write
|
|
99
|
+
method (unlikely but possible in future refactors), `infra.ts` would throw at runtime
|
|
100
|
+
rather than returning `err()`. This would be caught immediately by tests.
|
|
101
|
+
|
|
102
|
+
**Repo-pattern relationship**: Follows existing local infra pattern exactly.
|
|
103
|
+
`LocalDataDirV2`, `LocalDirectoryListingV2`, `LocalSessionEventLogStoreV2` are already
|
|
104
|
+
the canonical local implementations.
|
|
105
|
+
|
|
106
|
+
**Gains**: Reuses battle-tested store loading and projection code. No risk of format
|
|
107
|
+
divergence if session file structure changes.
|
|
108
|
+
|
|
109
|
+
**Losses**: Write stubs feel slightly dishonest. Minor complexity in `infra.ts`.
|
|
110
|
+
|
|
111
|
+
**Scope judgment**: Best-fit. Stays within `src/context-assembly/` with no v2 changes.
|
|
112
|
+
|
|
113
|
+
**Philosophy fit**: Honors DI for boundaries (composition root pattern), errors-as-data
|
|
114
|
+
(Result bridge), architectural fixes over patches (reuses canonical code). Minor
|
|
115
|
+
tension with YAGNI (write stubs) and interface honesty.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
### Candidate B: Read session files directly with fs.promises
|
|
120
|
+
|
|
121
|
+
**Summary**: Skip the port hierarchy. Read `manifest.jsonl` and `events/*.jsonl` directly
|
|
122
|
+
using `fs.promises.readFile`. Parse manually or reuse just the schema validators.
|
|
123
|
+
|
|
124
|
+
**Tensions resolved**: R1 completely -- no DI wiring at all.
|
|
125
|
+
|
|
126
|
+
**Tensions accepted**: Duplicates session reading logic. Any change to session file
|
|
127
|
+
format requires updates in two places.
|
|
128
|
+
|
|
129
|
+
**Failure mode**: Silent breakage if session file format changes.
|
|
130
|
+
|
|
131
|
+
**Repo-pattern relationship**: Departs from established pattern (re-implements existing logic).
|
|
132
|
+
|
|
133
|
+
**Scope judgment**: Too narrow -- creates a fragile parallel implementation.
|
|
134
|
+
|
|
135
|
+
**Philosophy fit**: Conflicts with 'architectural fixes over patches'.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
### Candidate C: Add createLocalSessionSummaryProvider() factory to v2 module
|
|
140
|
+
|
|
141
|
+
**Summary**: Add a `createLocalSessionSummaryProvider()` factory function to
|
|
142
|
+
`src/v2/infra/local/session-summary-provider/index.ts` that wires real deps and
|
|
143
|
+
returns a `SessionSummaryProviderPortV2`. Import and call from `infra.ts`.
|
|
144
|
+
|
|
145
|
+
**Tensions resolved**: Cleanest DI wiring.
|
|
146
|
+
|
|
147
|
+
**Tensions accepted**: Modifies an existing v2 infra module.
|
|
148
|
+
|
|
149
|
+
**Failure mode**: Entangles CLI-coordinator concerns into the storage layer.
|
|
150
|
+
|
|
151
|
+
**Scope judgment**: Slightly too broad -- modifies v2 module with no functional benefit
|
|
152
|
+
over Candidate A.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Comparison and Recommendation
|
|
157
|
+
|
|
158
|
+
**Recommendation: Candidate A**
|
|
159
|
+
|
|
160
|
+
All candidates agree on the high-level approach (4 new files, 3 modified files, exact
|
|
161
|
+
pitch interfaces). The only real design decision is `infra.ts` implementation.
|
|
162
|
+
|
|
163
|
+
Candidate A wins because:
|
|
164
|
+
1. It reuses battle-tested code for session reading and health projection
|
|
165
|
+
2. It stays within `src/context-assembly/` -- no v2 module changes
|
|
166
|
+
3. The write stubs are safe: `load()` provably does not call write methods
|
|
167
|
+
4. The pitch explicitly endorses this approach as R1 mitigation
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Self-Critique
|
|
172
|
+
|
|
173
|
+
**Strongest counter-argument**: The write stubs on `FileSystemPortV2` create a silent
|
|
174
|
+
contract violation. If a future `load()` implementation adds a write call internally,
|
|
175
|
+
the error would surface at runtime (throw) rather than compile-time.
|
|
176
|
+
|
|
177
|
+
**Narrower option that lost**: Candidate B (raw fs.promises). Lost because it duplicates
|
|
178
|
+
canonical parsing logic and diverges from existing patterns.
|
|
179
|
+
|
|
180
|
+
**Broader option and evidence required**: Candidate C would be justified if multiple
|
|
181
|
+
other CLI commands needed access to session summaries -- adding a factory to the v2
|
|
182
|
+
module would be worth the scope increase. No evidence of this need in v1.
|
|
183
|
+
|
|
184
|
+
**Assumption that would invalidate this design**: If `LocalSessionEventLogStoreV2.load()`
|
|
185
|
+
internally calls any of the stubbed write methods (currently provably false by code
|
|
186
|
+
inspection of `loadImpl()`, `loadValidatedPrefixImpl()`, `readManifestOrEmpty()`).
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Open Questions for Main Agent
|
|
191
|
+
|
|
192
|
+
1. Verify: does `Sha256PortV2` have a simple interface that can be satisfied with a
|
|
193
|
+
1-line `node:crypto` implementation? (If so, use real impl rather than throw stub.)
|
|
194
|
+
2. Verify: does the `FileSystemPortV2` shim need anything beyond `readFileUtf8` for
|
|
195
|
+
the `load()` path? (Current analysis: no -- `readManifestOrEmpty` and segment loading
|
|
196
|
+
both use `readFileUtf8` only.)
|
|
197
|
+
3. The re-review spawn (line ~1265 in pr-review.ts) should forward `spawnContext` from
|
|
198
|
+
the outer scope per the pitch. Confirm this is the correct approach (vs. reassembling
|
|
199
|
+
context for re-reviews).
|
|
@@ -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,110 @@
|
|
|
1
|
+
# Context Assembly Layer -- Design Review Findings
|
|
2
|
+
|
|
3
|
+
**Reviewing:** Candidate B-hybrid (ContextAssembler service + typed assembledContext field on WorkflowTrigger)
|
|
4
|
+
**Date:** 2026-04-19
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Tradeoff Review
|
|
9
|
+
|
|
10
|
+
| Tradeoff | Acceptable? | What would make it unacceptable |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| Context serialized to string for agent consumption | YES (agents consume natural language) | If coordinators need to inspect bundle content for dispatch decisions -- not a planned use case |
|
|
13
|
+
| 150 LOC new module vs. 50 LOC inline | YES (justified by multi-coordinator reuse) | If second coordinator never arrives within 6 months |
|
|
14
|
+
| buildSystemPrompt() change (3 lines) | YES (daemon-only, not engine) | If buildSystemPrompt() is treated as a versioned public API -- it is not |
|
|
15
|
+
| workflow-runner.ts imports ContextBundle | YES (coupling direction is correct) | If ContextBundle grows to include I/O -- it must remain a pure value type |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Failure Mode Review
|
|
20
|
+
|
|
21
|
+
| Failure Mode | Design handling | Missing mitigation | Risk |
|
|
22
|
+
|---|---|---|---|
|
|
23
|
+
| renderContextBundle() accumulates coordinator-specific formatting | Not addressed in v1 | Add `RenderOpts` interface with optional section labels | LOW |
|
|
24
|
+
| Agent ignores assembled context (attention threshold) | Section appended at end of system prompt | Test with prior session notes first; add reference in step prompts | **MEDIUM -- existential** |
|
|
25
|
+
| gitDiff source fails silently | Per-field Result<T,string> -- fails gracefully | Add WARN log in coordinator when source fails | LOW |
|
|
26
|
+
| Second coordinator wants different rendering | Not addressed | RenderOpts mitigation from FM1 | LOW (v1) |
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Runner-Up / Simpler Alternative Review
|
|
31
|
+
|
|
32
|
+
**Candidate A strength incorporated:** Typed `assembledContext?: ContextBundle` field on `WorkflowTrigger` is cleaner than string-in-context-map. This is the B-hybrid improvement over pure Candidate B.
|
|
33
|
+
|
|
34
|
+
**Simpler alternative (prior session notes only):** Would satisfy 3/5 acceptance criteria but NOT criterion 4 (pr-review.ts shrinks by 100 LOC). Not sufficient.
|
|
35
|
+
|
|
36
|
+
**Hybrid adopted:** B + typed field from A. This is the recommended design.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Philosophy Alignment
|
|
41
|
+
|
|
42
|
+
| Principle | Status |
|
|
43
|
+
|---|---|
|
|
44
|
+
| Dependency injection for boundaries | SATISFIED |
|
|
45
|
+
| Errors are data | SATISFIED |
|
|
46
|
+
| Immutability by default | SATISFIED |
|
|
47
|
+
| Make illegal states unrepresentable | SATISFIED (discriminated union) |
|
|
48
|
+
| Compose with small, pure functions | SATISFIED |
|
|
49
|
+
| Validate at boundaries | SATISFIED |
|
|
50
|
+
| YAGNI | ACCEPTABLE TENSION (coding_task kind is speculative but planned) |
|
|
51
|
+
| Explicit domain types | MINOR TENSION -- define SessionNote as a proper interface, not string |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Findings
|
|
56
|
+
|
|
57
|
+
### RED (blocking)
|
|
58
|
+
|
|
59
|
+
None.
|
|
60
|
+
|
|
61
|
+
### ORANGE (should be addressed before implementation)
|
|
62
|
+
|
|
63
|
+
**O1: Assembled context visibility to agent is unverified**
|
|
64
|
+
|
|
65
|
+
The system prompt injection in `buildSystemPrompt()` adds the assembled context section AFTER the base system prompt and workspace context. In a dense system prompt (32KB CLAUDE.md + soul file), the assembled context may receive insufficient agent attention. This is the existential risk for the design.
|
|
66
|
+
|
|
67
|
+
**Required action before implementation:** Run one session with only `priorSessionNotes` injected. Verify the agent cites or references the prior session notes in its first turn. If it does not, investigate prompt positioning.
|
|
68
|
+
|
|
69
|
+
### YELLOW (note before implementation)
|
|
70
|
+
|
|
71
|
+
**Y1: SessionNote is not defined as a domain type**
|
|
72
|
+
|
|
73
|
+
`ContextAssemblerDeps.listRecentSessions` returns `Result<readonly SessionNote[], string>`. `SessionNote` must be defined as a proper `readonly` interface:
|
|
74
|
+
```typescript
|
|
75
|
+
export interface SessionNote {
|
|
76
|
+
readonly sessionId: string;
|
|
77
|
+
readonly recapSnippet: string;
|
|
78
|
+
readonly sessionTitle: string | null;
|
|
79
|
+
readonly gitBranch: string | null;
|
|
80
|
+
readonly lastModifiedMs: number;
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
This maps directly to `HealthySessionSummary` fields in `LocalSessionSummaryProviderV2`.
|
|
84
|
+
|
|
85
|
+
**Y2: renderContextBundle needs RenderOpts stub**
|
|
86
|
+
|
|
87
|
+
Even in v1, add an optional `RenderOpts` parameter to `renderContextBundle()` (empty interface for now). This preserves the extension point without implementing it.
|
|
88
|
+
|
|
89
|
+
**Y3: WARN logging for source failures**
|
|
90
|
+
|
|
91
|
+
When `bundle.gitDiff.kind === 'err'`, the coordinator should log at WARN level. Add this to the coordinator refactor spec.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Recommended Revisions
|
|
96
|
+
|
|
97
|
+
1. **Keep B-hybrid:** ContextAssembler service + typed `assembledContext?: ContextBundle` field on `WorkflowTrigger`. Do NOT use string-in-context-map.
|
|
98
|
+
2. **Add O1 mitigation:** Pilot test with `priorSessionNotes` source only before wiring all three sources.
|
|
99
|
+
3. **Define `SessionNote` interface** in `src/context-assembly/types.ts` before implementation.
|
|
100
|
+
4. **Add `RenderOpts` stub** to `renderContextBundle()` signature.
|
|
101
|
+
5. **Add WARN logs** in coordinator when source fails.
|
|
102
|
+
6. **Position assembled context section** in `buildSystemPrompt()` BEFORE the referenceUrls section (higher attention) -- small positioning change.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Residual Concerns
|
|
107
|
+
|
|
108
|
+
1. **Second coordinator timeline is unscheduled.** The multi-coordinator reuse argument for Candidate B over A depends on a second coordinator being built. If this is more than 6 months away, Candidate A is the pragmatic choice.
|
|
109
|
+
2. **`LocalSessionSummaryProviderV2` wiring.** The session summary provider is currently wired only to the MCP server/console path. Using it from `ContextAssemblerDeps` requires either re-exporting its ports or constructing a lightweight adapter. Scope should be confirmed during shaping.
|
|
110
|
+
3. **git diff strategy.** `git diff HEAD~1 --stat` gives a file list; `git diff HEAD~1` gives full diff content. Full diff for large PRs could be 50-100KB. The v1 strategy should be `--stat` (file names and change counts only) -- this fits in the assembled context without hitting budget limits.
|