feat: [ENG-2238] AutoHarness V2 HarnessContext + module contract#494
feat: [ENG-2238] AutoHarness V2 HarnessContext + module contract#494danhdoan merged 2 commits intoproj/autoharness-v2from
Conversation
Phase 3 Task 3.1 — the seam that unblocks to start Phase 4.3 (templates) on day one. Ships the `HarnessContext` / `HarnessModule` / `HarnessLoadResult` type surface so templates can be authored against a stable contract before the module-builder (Task 3.2) and loader (Task 3.3) exist. Types added to `src/agent/core/domain/harness/types.ts`: - HarnessContextEnv (commandType, projectType, workingDirectory) - HarnessContextTools (curate, readFile — 2 methods for v1.0) - HarnessContext (abort, env, tools) - HarnessModule (meta required; curate/query optional) - HarnessLoadResult (discriminated union on `loaded`) All fields `readonly`. Runtime freezing comes later in Task 3.2's module builder via `Object.freeze`. Shipped with 2 methods on `HarnessContextTools` instead of the 4 enumerated in the task doc draft. Reason: layering. `grep` and `searchKnowledge` reference `GrepOptions` / `SearchKnowledgeOptions` / `SearchKnowledgeResult` which currently live in `src/agent/infra/sandbox/tools-sdk.ts`. Importing `infra/` types into `core/domain/` would reintroduce the layering violation ENG-2222 fixed. The 2-method surface covers Phase 4 pass-through curate templates; extending is additive when a real consumer needs more (moving the infra types to `core/` first is a prerequisite one-line PR). New barrel `src/agent/core/domain/harness/index.ts` consolidates the domain import surface. 8 type-level tests (using `expect-type`) cover the discriminated-union narrowing + readonly enforcement + HarnessModule required/optional shape. Full suite: 6665 passing, 0 failing.
|
Claude finished @danhdoan's task in 2m 28s —— View job PR Review: feat: [ENG-2238] AutoHarness V2 HarnessContext + module contractTasks
SummaryScore: 4.5 / 5 Clean, well-scoped PR. The type surface is minimal and defensible, the import discipline is solid, and the test approach (compile-time invariants via Layering / Import Boundaries ✅New imports in
No
|

Summary
HarnessContextto declare their function signatures. Without the type surface shipped, can't write a single line of Phase 4 implementation that compiles. This PR is the seam.HarnessContextEnv,HarnessContextTools,HarnessContext,HarnessModule,HarnessLoadResult) tosrc/agent/core/domain/harness/types.ts. New barrel atsrc/agent/core/domain/harness/index.ts. 8-scenario type-level test suite.HarnessModuleBuilder(Task 3.2). NoSandboxServicemodifications (Task 3.3). No template content (Phase 4). No consumer imports these types yet.Type of change
Scope (select all touched areas)
Linked issues
infra/— the layering discipline this PR preserves)Root cause (bug fixes only, otherwise write
N/A)Test plan
test/unit/agent/harness/types-context.test.tsHarnessModule.metarequired;curate/queryoptional (2 tests, one positive + one@ts-expect-errornegative)HarnessContext/HarnessContextEnv/HarnessContextToolsreadonly enforcement (3 tests, all@ts-expect-error-based — compile-time invariants)HarnessLoadResultdiscriminated-union narrowing (3 tests:loaded: trueexposesmodule+version;loaded: falseexposesreason; failure variant'skeyofexcludesmodule)User-visible changes
None. Types-only addition; no runtime behavior.
harness.enabled = falseremains the public default.Evidence
Before this PR, the test file didn't exist and the types weren't declared. After: all 8 pass. Full suite: 6665 passing / 0 failing.
Checklist
npm test) — 8 new tests; full suite 6665 passing / 0 failingnpm run lint) — 0 errors, 224 pre-existing warningsnpm run typecheck) — exit=0npm run build) — exit=0feat: [ENG-2234] ...features/autoharness-v2/tasks/phase_3/task_01-harness-module-contract.md(research repo) describes the scope; the 2-methods-vs-4 deviation is flagged in "Notes for reviewers" for post-merge task-doc tightening (same pattern as Tasks 0.5/0.6/1.2/1.3/1.4)main— targetsproj/autoharness-v2, notmainRisks and mitigations
Risk:
HarnessContextToolsships with 2 methods (curate,readFile), not the 4 the task doc sketched. If Phase 4 Task 4.3's query template needssearchKnowledgeon the context (likely), it'll fail to compile until the type is extended.HarnessContextTools. The prerequisite is movingSearchKnowledgeOptions/SearchKnowledgeResult(and forgrep,GrepOptions) fromsrc/agent/infra/sandbox/tools-sdk.tsintocore/domain/orcore/interfaces/— a small mechanical PR that preserves the layering discipline. When Phat reaches Task 4.3 and declares what his query template actually needs, we decide together whether to extendHarnessContextTools(my preference) or inline the call differently in the template.Risk: Task doc (research repo) drafted the surface as 4 methods (
curate,query,search,readFile) with invented type names (CurateArgs,SearchOpts,FileReadResult). Shipped uses real tools-SDK signatures. Anyone reading the task doc in isolation will expect 4 methods.Risk:
readonlyis TypeScript-only. At runtime the context is mutable unless frozen.Object.freeze(ctx)before passing into VM evaluation. Called out in this PR's test file comments and in Task 3.2's task doc. A bug that skipped the freeze would leak only to harness code, not to outer sandbox — and the isolation integration test (Task 3.5) would catch real escapes.Notes for reviewers
The
HarnessContextToolssurface decision is the only interesting design call in this PR. Task doc sketched 4 methods; shipped 2. The reasoning is in the JSDoc onHarnessContextToolsitself — imports frominfra/would violate the layering we established in ENG-2222. If the team prefers the fuller surface, the fix is a separate PR movingSearchKnowledgeOptions/SearchKnowledgeResult/GrepOptionsintocore/first. I recommend deferring that move until a real consumer (Phase 4 Task 4.3 or Phase 6 refinement) materializes, so we're not moving types speculatively.readonlyvs.Readonly<T>modifier choice. I used inlinereadonlyon every field rather than wrapping the interface inReadonly<...>. Reason: property-levelreadonlycomposes better with discriminated unions and partial mocks in tests. The tradeoff is 5 extra characters per field declaration.HarnessModule.queryis typedPromise<unknown>— notPromise<QueryResult>as the task doc draft suggested. Reason:QueryResultdoesn't exist in the codebase yet; the closest analog isSearchKnowledgeResult, which lives in infra and can't be imported into core per the layering rule.unknownis the safe placeholder — Phase 4 Task 4.3's query template will produce whatever shape matches its use case, and Phase 5's mode-selector / LLM surface layer-over the result when it matters. If tightening to a concrete type becomes useful, that's additive in a later PR.The "does NOT expose
moduleon loaded: false" test usesExtract<...>+keyofrather than an in-branch@ts-expect-erroraccess. Reason:@ts-expect-errorinside a narrowed-type branch is TS-version-sensitive — some versions allow "harmless" property access on narrowed unions, and a passing-by-accident test would mask real drift. TheExtract<HarnessLoadResult, {loaded: false}>['keyof']assertion is explicit and version-stable.Related
src/agent/core/domain/harness/types.tssrc/agent/core/domain/harness/index.ts(new)test/unit/agent/harness/types-context.test.ts(new)features/autoharness-v2/tasks/phase_3/task_01-harness-module-contract.md(research repo)features/autoharness-v2/execution-plan.md § Phase 3HarnessModuleBuilder