@exaudeus/workrail 3.40.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/commands/init.js +0 -3
- package/dist/cli-worktrain.js +48 -11
- package/dist/cli.js +0 -18
- package/dist/config/app-config.d.ts +0 -16
- package/dist/config/app-config.js +0 -14
- package/dist/config/config-file.js +0 -3
- package/dist/console-ui/assets/index-DGj8EsFR.css +1 -0
- package/dist/console-ui/assets/index-DwfWMKvv.js +28 -0
- package/dist/console-ui/index.html +2 -2
- 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 +20 -1
- package/dist/coordinators/pr-review.js +189 -4
- package/dist/daemon/daemon-events.d.ts +9 -1
- package/dist/daemon/soul-template.d.ts +2 -2
- package/dist/daemon/soul-template.js +11 -1
- package/dist/daemon/workflow-runner.d.ts +14 -1
- package/dist/daemon/workflow-runner.js +406 -25
- package/dist/di/container.js +1 -25
- package/dist/di/tokens.d.ts +0 -3
- package/dist/di/tokens.js +0 -3
- package/dist/domain/execution/state.d.ts +6 -6
- package/dist/engine/engine-factory.js +0 -1
- package/dist/infrastructure/console-defaults.d.ts +1 -0
- package/dist/infrastructure/console-defaults.js +4 -0
- package/dist/infrastructure/session/index.d.ts +0 -1
- package/dist/infrastructure/session/index.js +1 -3
- package/dist/manifest.json +138 -122
- package/dist/mcp/handlers/session.d.ts +1 -0
- package/dist/mcp/handlers/session.js +61 -13
- package/dist/mcp/handlers/v2-workflow.d.ts +2 -2
- package/dist/mcp/output-schemas.d.ts +234 -234
- package/dist/mcp/server.js +1 -18
- package/dist/mcp/tools.d.ts +2 -2
- package/dist/mcp/transports/http-entry.js +0 -2
- package/dist/mcp/transports/stdio-entry.js +1 -2
- package/dist/mcp/types.d.ts +0 -2
- package/dist/mcp/v2/tools.d.ts +24 -24
- package/dist/trigger/daemon-console.d.ts +2 -0
- package/dist/trigger/daemon-console.js +1 -1
- package/dist/trigger/trigger-listener.d.ts +2 -0
- package/dist/trigger/trigger-listener.js +3 -1
- package/dist/trigger/trigger-router.d.ts +4 -3
- package/dist/trigger/trigger-router.js +4 -3
- package/dist/trigger/trigger-store.js +17 -4
- 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/dist/v2/usecases/console-routes.d.ts +2 -1
- package/dist/v2/usecases/console-routes.js +29 -5
- package/dist/v2/usecases/console-service.js +14 -0
- package/dist/v2/usecases/console-types.d.ts +1 -0
- package/docs/authoring.md +16 -16
- 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/design/coordinator-message-queue-drain-plan.md +241 -0
- package/docs/design/coordinator-message-queue-drain-review.md +120 -0
- package/docs/design/coordinator-message-queue-drain.md +289 -0
- package/docs/design/shaping-workflow-external-research.md +119 -0
- package/docs/discovery/late-bound-goals-impl-plan.md +147 -0
- package/docs/discovery/late-bound-goals-review.md +82 -0
- package/docs/discovery/late-bound-goals.md +118 -0
- package/docs/discovery/steer-endpoint-design-candidates.md +288 -0
- package/docs/discovery/steer-endpoint-design-review-findings.md +104 -0
- package/docs/discovery/steer-endpoint-implementation-plan.md +284 -0
- package/docs/ideas/backlog.md +356 -0
- package/docs/ideas/design-candidates-console-session-tree-impl.md +64 -0
- package/docs/ideas/design-candidates-session-tree-view.md +196 -0
- package/docs/ideas/design-review-findings-console-session-tree-impl.md +75 -0
- package/docs/ideas/design-review-findings-session-tree-view.md +88 -0
- package/docs/ideas/implementation_plan_session_tree_view.md +238 -0
- package/package.json +2 -1
- package/spec/authoring-spec.json +16 -16
- package/spec/shape.schema.json +178 -0
- package/spec/workflow-tags.json +232 -47
- package/workflows/coding-task-workflow-agentic.json +491 -480
- package/workflows/wr.shaping.json +182 -0
- package/dist/console-ui/assets/index-8dh0Psu-.css +0 -1
- package/dist/console-ui/assets/index-CXWCAonr.js +0 -28
- package/dist/infrastructure/session/DashboardHeartbeat.d.ts +0 -8
- package/dist/infrastructure/session/DashboardHeartbeat.js +0 -39
- package/dist/infrastructure/session/DashboardLockRelease.d.ts +0 -2
- package/dist/infrastructure/session/DashboardLockRelease.js +0 -29
- package/dist/infrastructure/session/HttpServer.d.ts +0 -60
- package/dist/infrastructure/session/HttpServer.js +0 -912
- package/workflows/coding-task-workflow-agentic.lean.v2.json +0 -648
- package/workflows/coding-task-workflow-agentic.v2.json +0 -324
|
@@ -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,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(...)`.
|