@plaited/acp-harness 0.2.5 → 0.3.1

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.
Files changed (57) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +120 -16
  3. package/bin/cli.ts +105 -636
  4. package/bin/tests/cli.spec.ts +218 -51
  5. package/package.json +20 -4
  6. package/src/acp-client.ts +5 -4
  7. package/src/acp-transport.ts +14 -7
  8. package/src/adapter-check.ts +542 -0
  9. package/src/adapter-scaffold.ts +934 -0
  10. package/src/balance.ts +232 -0
  11. package/src/calibrate.ts +300 -0
  12. package/src/capture.ts +457 -0
  13. package/src/constants.ts +94 -0
  14. package/src/grader-loader.ts +174 -0
  15. package/src/harness.ts +35 -0
  16. package/src/schemas-cli.ts +239 -0
  17. package/src/schemas.ts +567 -0
  18. package/src/summarize.ts +245 -0
  19. package/src/tests/adapter-check.spec.ts +70 -0
  20. package/src/tests/adapter-scaffold.spec.ts +112 -0
  21. package/src/tests/fixtures/grader-bad-module.ts +5 -0
  22. package/src/tests/fixtures/grader-exec-fail.py +9 -0
  23. package/src/tests/fixtures/grader-exec-invalid.py +6 -0
  24. package/src/tests/fixtures/grader-exec.py +29 -0
  25. package/src/tests/fixtures/grader-module.ts +14 -0
  26. package/src/tests/grader-loader.spec.ts +153 -0
  27. package/src/trials.ts +395 -0
  28. package/src/validate-refs.ts +188 -0
  29. package/.claude/rules/accuracy.md +0 -43
  30. package/.claude/rules/bun-apis.md +0 -80
  31. package/.claude/rules/code-review.md +0 -254
  32. package/.claude/rules/git-workflow.md +0 -37
  33. package/.claude/rules/github.md +0 -154
  34. package/.claude/rules/testing.md +0 -172
  35. package/.claude/skills/acp-harness/SKILL.md +0 -310
  36. package/.claude/skills/acp-harness/assets/Dockerfile.acp +0 -25
  37. package/.claude/skills/acp-harness/assets/docker-compose.acp.yml +0 -19
  38. package/.claude/skills/acp-harness/references/downstream.md +0 -288
  39. package/.claude/skills/acp-harness/references/output-formats.md +0 -221
  40. package/.claude-plugin/marketplace.json +0 -15
  41. package/.claude-plugin/plugin.json +0 -16
  42. package/.github/CODEOWNERS +0 -6
  43. package/.github/workflows/ci.yml +0 -63
  44. package/.github/workflows/publish.yml +0 -146
  45. package/.mcp.json +0 -20
  46. package/CLAUDE.md +0 -92
  47. package/Dockerfile.test +0 -23
  48. package/biome.json +0 -96
  49. package/bun.lock +0 -513
  50. package/docker-compose.test.yml +0 -21
  51. package/scripts/bun-test-wrapper.sh +0 -46
  52. package/src/acp.constants.ts +0 -56
  53. package/src/acp.schemas.ts +0 -161
  54. package/src/acp.types.ts +0 -28
  55. package/src/tests/fixtures/.claude/settings.local.json +0 -8
  56. package/src/tests/fixtures/.claude/skills/greeting/SKILL.md +0 -17
  57. package/tsconfig.json +0 -32
@@ -1,254 +0,0 @@
1
- # Code Review Standards
2
-
3
- The following standards are not automatically enforced by Biome but should be checked during code review.
4
-
5
- ## Automated Validation
6
-
7
- Before completing a code review, run these validation scripts:
8
-
9
- ### Plugin Skills Validation
10
-
11
- When changes touch `.claude/skills/`, validate against the AgentSkills spec by running:
12
-
13
- ```
14
- /validate-skill
15
- ```
16
-
17
- This checks:
18
- - SKILL.md exists and has required frontmatter
19
- - Scripts have proper structure
20
- - References are valid markdown files
21
-
22
- ## TypeScript Style Conventions
23
-
24
- ### Prefer `type` Over `interface`
25
-
26
- Use type aliases instead of interfaces for better consistency and flexibility:
27
-
28
- ```typescript
29
- // ✅ Good
30
- type User = {
31
- name: string
32
- email: string
33
- }
34
-
35
- // ❌ Avoid
36
- interface User {
37
- name: string
38
- email: string
39
- }
40
- ```
41
-
42
- **Rationale:** Type aliases are more flexible (unions, intersections, mapped types) and provide consistent syntax across the codebase.
43
-
44
- ### No `any` Types
45
-
46
- Always use proper types; use `unknown` if type is truly unknown and add type guards:
47
-
48
- ```typescript
49
- // ✅ Good
50
- const process = (data: unknown) => {
51
- if (typeof data === 'string') {
52
- return data.toUpperCase()
53
- }
54
- }
55
-
56
- // ❌ Avoid
57
- const process = (data: any) => {
58
- return data.toUpperCase()
59
- }
60
- ```
61
-
62
- ### PascalCase for Types and Schemas
63
-
64
- All type names use PascalCase. Zod schema names use `PascalCaseSchema` suffix:
65
-
66
- ```typescript
67
- // ✅ Good
68
- type UserConfig = { /* ... */ }
69
- const UserConfigSchema = z.object({ /* ... */ })
70
-
71
- // ❌ Avoid
72
- type userConfig = { /* ... */ }
73
- const zUserConfig = z.object({ /* ... */ }) // Don't use z-prefix
74
- ```
75
-
76
- ### Arrow Functions Preferred
77
-
78
- ```typescript
79
- // ✅ Good
80
- const greet = (name: string) => `Hello, ${name}!`
81
-
82
- // ❌ Avoid
83
- function greet(name: string) {
84
- return `Hello, ${name}!`
85
- }
86
- ```
87
-
88
- ### Object Parameter Pattern
89
-
90
- For functions with more than two parameters, use a single object parameter:
91
-
92
- ```typescript
93
- // ✅ Good: Object parameter pattern
94
- const createClient = ({
95
- command,
96
- timeout,
97
- cwd,
98
- }: {
99
- command: string[]
100
- timeout: number
101
- cwd?: string
102
- }): ACPClient => { /* ... */ }
103
-
104
- // ❌ Avoid: Multiple positional parameters
105
- const createClient = (
106
- command: string[],
107
- timeout: number,
108
- cwd?: string
109
- ): ACPClient => { /* ... */ }
110
- ```
111
-
112
- ## TypeScript Comment Directives
113
-
114
- ### `@ts-ignore` Requires Description
115
-
116
- - When using `@ts-ignore`, always include a description explaining why the type error is being suppressed
117
- - This helps future maintainers understand the reasoning
118
- - Example:
119
- ```typescript
120
- // @ts-ignore - TypeScript incorrectly infers this as string when it's actually a number from the API
121
- const value = response.data
122
- ```
123
-
124
- **Rationale:** Lost TypeScript-ESLint rule `ban-ts-comment` with `allow-with-description` option during Biome migration
125
-
126
- ## Expression Statements
127
-
128
- ### Allow Short-Circuit and Ternary Expressions
129
-
130
- - Short-circuit evaluation is acceptable: `condition && doSomething()`
131
- - Ternary expressions are acceptable: `condition ? doThis() : doThat()`
132
- - These patterns are idiomatic and improve code readability
133
-
134
- **Rationale:** Lost TypeScript-ESLint rule `no-unused-expressions` with `allowShortCircuit` and `allowTernary` options during Biome migration
135
-
136
- ## Empty Object Types
137
-
138
- ### Allow Empty Object Types When Extending a Single Interface
139
-
140
- - Empty object types are acceptable when they extend exactly one other interface
141
- - This pattern is useful for creating branded types or extending third-party interfaces
142
- - Example:
143
- ```typescript
144
- // ✅ Acceptable: Extends single interface
145
- interface CustomElement extends HTMLElement {}
146
-
147
- // ❌ Avoid: Empty with no extends or multiple extends
148
- interface Empty {}
149
- interface Multi extends Foo, Bar {}
150
- ```
151
-
152
- **Rationale:** Lost TypeScript-ESLint rule `no-empty-object-type` with `allowInterfaces: 'with-single-extends'` option during Biome migration
153
-
154
- ## Modern JavaScript Standards
155
-
156
- ### Prefer Private Fields Over `private` Keyword
157
-
158
- Use JavaScript private fields (`#field`) instead of TypeScript's `private` keyword:
159
-
160
- ```typescript
161
- // ✅ Good: JavaScript private fields (ES2022+)
162
- class EventBus {
163
- #listeners = new Map<string, Set<Function>>()
164
- #count = 0
165
-
166
- #emit(event: string) {
167
- this.#count++
168
- this.#listeners.get(event)?.forEach(fn => fn())
169
- }
170
- }
171
-
172
- // ❌ Avoid: TypeScript private keyword
173
- class EventBus {
174
- private listeners = new Map<string, Set<Function>>()
175
- private count = 0
176
-
177
- private emit(event: string) {
178
- this.count++
179
- this.listeners.get(event)?.forEach(fn => fn())
180
- }
181
- }
182
- ```
183
-
184
- **Rationale:** JavaScript private fields are a runtime feature (ES2022) providing true encapsulation. TypeScript's `private` is erased at compile time and can be bypassed. Prefer platform standards over TypeScript-only features
185
-
186
- ### JSON Import Attributes
187
-
188
- Use import attributes when importing JSON files. This is required because the tsconfig uses `verbatimModuleSyntax: true` without `resolveJsonModule`:
189
-
190
- ```typescript
191
- // ✅ Good: Import attribute for JSON
192
- import { version } from '../package.json' with { type: 'json' }
193
- import config from './config.json' with { type: 'json' }
194
-
195
- // ❌ Avoid: Missing import attribute
196
- import { version } from '../package.json'
197
- import config from './config.json'
198
- ```
199
-
200
- **Rationale:** Import attributes (ES2025) explicitly declare module types to the runtime. The `with { type: 'json' }` syntax is the standard (replacing the deprecated `assert` keyword). This provides runtime enforcement—if the file isn't valid JSON, the import fails.
201
-
202
- ## Module Organization
203
-
204
- Organize module exports into separate files by category:
205
-
206
- ```
207
- module/
208
- ├── module.types.ts # Type definitions only
209
- ├── module.schemas.ts # Zod schemas and schema-inferred types
210
- ├── module.constants.ts # Constants and enum-like objects
211
- ├── module-client.ts # Implementation
212
- └── index.ts # Public API exports (barrel file)
213
- ```
214
-
215
- ### Key Principles
216
-
217
- - **Types file**: Contains only type definitions, does NOT re-export from sibling files
218
- - **Schemas file**: Contains Zod schemas and their inferred types, does NOT export constants
219
- - **Constants file**: Contains all constant values (error codes, method names, defaults)
220
- - **Barrel file**: Uses wildcard exports; non-public files are simply not included
221
-
222
- ```typescript
223
- // ✅ Good: Direct imports from specific files
224
- import type { Config } from './module.types.ts'
225
- import { ConfigSchema } from './module.schemas.ts'
226
- import { METHODS, ERROR_CODES } from './module.constants.ts'
227
-
228
- // ❌ Avoid: Expecting types file to re-export everything
229
- import { Config, ConfigSchema, METHODS } from './module.types.ts'
230
- ```
231
-
232
- **Rationale:** Prevents circular dependencies, makes dependencies explicit, improves tree-shaking.
233
-
234
- ## Documentation Standards
235
-
236
- ### Mermaid Diagrams Only
237
-
238
- Use [mermaid](https://mermaid.js.org/) syntax for all diagrams in markdown files:
239
-
240
- ```markdown
241
- \```mermaid
242
- flowchart TD
243
- A[Start] --> B[Process]
244
- B --> C[End]
245
- \```
246
- ```
247
-
248
- **Avoid**: ASCII box-drawing characters (`┌`, `│`, `└`, `─`, etc.)
249
-
250
- **Rationale:** Token efficiency, clearer semantic meaning, easier maintenance.
251
-
252
- ### No `@example` Sections in TSDoc
253
-
254
- Tests serve as living examples. Do not add `@example` sections to TSDoc comments.
@@ -1,37 +0,0 @@
1
- # Git Workflow
2
-
3
- ## Commit Message Format
4
-
5
- When creating commits with multi-line messages, use single-quoted strings instead of heredocs. The sandbox environment restricts temp file creation needed for heredocs.
6
-
7
- ```bash
8
- # ✅ CORRECT: Single-quoted multi-line string
9
- git commit -m 'refactor: description here
10
-
11
- Additional context on second line.
12
-
13
- 🤖 Generated with [Claude Code](https://claude.com/claude-code)
14
-
15
- Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>'
16
-
17
- # ❌ WRONG: Heredoc syntax (fails in sandbox)
18
- git commit -m "$(cat <<'EOF'
19
- refactor: description here
20
- EOF
21
- )"
22
- ```
23
-
24
- The heredoc approach fails with:
25
- ```
26
- (eval):1: can't create temp file for here document: operation not permitted
27
- ```
28
-
29
- ## Commit Conventions
30
-
31
- This project follows conventional commits:
32
- - `feat:` - New features
33
- - `fix:` - Bug fixes
34
- - `refactor:` - Code changes that neither fix bugs nor add features
35
- - `docs:` - Documentation only changes
36
- - `chore:` - Maintenance tasks
37
- - `test:` - Adding or updating tests
@@ -1,154 +0,0 @@
1
- # GitHub Integration
2
-
3
- ## Prefer GitHub CLI Over Web Fetch
4
-
5
- When given GitHub URLs (PRs, issues, repos), use the `gh` CLI instead of WebFetch for more reliable and complete data access.
6
-
7
- ### PR Information
8
-
9
- ```bash
10
- # Get PR details with comments and reviews (comprehensive)
11
- gh pr view <number> --repo <owner>/<repo> --json title,body,comments,reviews,reviewRequests,state,author,additions,deletions,changedFiles
12
-
13
- # Get PR diff
14
- gh pr diff <number> --repo <owner>/<repo>
15
-
16
- # Get PR files changed
17
- gh pr view <number> --repo <owner>/<repo> --json files
18
-
19
- # Get PR checks status
20
- gh pr checks <number> --repo <owner>/<repo>
21
- ```
22
-
23
- ### Complete PR Evaluation Workflow
24
-
25
- When asked to evaluate PR feedback, you MUST fetch **all** feedback sources. Do not just fetch comments/reviews - also check security alerts and inline code comments.
26
-
27
- **Step 1: Fetch PR comments and reviews**
28
- ```bash
29
- gh pr view <number> --repo <owner>/<repo> --json title,body,comments,reviews,state
30
- ```
31
-
32
- **Step 2: Fetch code scanning alerts (security vulnerabilities)**
33
- ```bash
34
- gh api repos/<owner>/<repo>/code-scanning/alerts --jq '
35
- .[] | select(.state == "open") | {
36
- number: .number,
37
- rule: .rule.description,
38
- severity: .rule.severity,
39
- file: .most_recent_instance.location.path,
40
- line: .most_recent_instance.location.start_line
41
- }
42
- '
43
- ```
44
-
45
- **Step 3: Fetch inline review comments (code quality, suggestions)**
46
- ```bash
47
- gh api repos/<owner>/<repo>/pulls/<number>/comments --jq '
48
- .[] | {
49
- id: .id,
50
- user: .user.login,
51
- file: .path,
52
- line: .line,
53
- body: .body
54
- }
55
- '
56
- ```
57
-
58
- **Step 4: Address ALL feedback**
59
- Create a checklist and address each item:
60
- - [ ] Human reviewer comments
61
- - [ ] Claude Code review comments
62
- - [ ] GitHub Advanced Security alerts (ReDoS, injection, etc.)
63
- - [ ] GitHub Code Quality comments (dead code, useless assignments)
64
- - [ ] Inline review suggestions
65
-
66
- ### Comment Sources
67
-
68
- | Source | API/Location | Description |
69
- |--------|--------------|-------------|
70
- | Human reviewers | `gh pr view --json reviews` | Code owners, team members |
71
- | Claude Code | `gh pr view --json comments` | AI-generated review (login: `claude`) |
72
- | GitHub Advanced Security | `gh api .../code-scanning/alerts` | Security vulnerabilities (ReDoS, injection) |
73
- | GitHub Code Quality | `gh api .../pulls/.../comments` | Code quality issues (login: `github-code-quality[bot]`) |
74
- | Inline suggestions | `gh api .../pulls/.../comments` | Line-specific review comments |
75
-
76
- ### Filtering by Author
77
-
78
- ```bash
79
- # Get all automated reviews from PR
80
- gh pr view <number> --repo <owner>/<repo> --json reviews --jq '
81
- .reviews[] | select(.author.login | test("github-|claude")) | {author: .author.login, state: .state}
82
- '
83
-
84
- # Get specific inline comment by ID
85
- gh api repos/<owner>/<repo>/pulls/<number>/comments --jq '
86
- .[] | select(.id == <comment_id>)
87
- '
88
- ```
89
-
90
- ### URL Patterns for Specific Feedback
91
-
92
- | URL Pattern | How to Fetch |
93
- |-------------|--------------|
94
- | `.../pull/<n>#issuecomment-<id>` | `gh pr view <n> --json comments` |
95
- | `.../pull/<n>#discussion_r<id>` | `gh api repos/.../pulls/<n>/comments` |
96
- | `.../security/code-scanning/<id>` | `gh api repos/.../code-scanning/alerts/<id>` |
97
-
98
- ### Review States
99
- - `APPROVED` - Reviewer approved changes
100
- - `CHANGES_REQUESTED` - Reviewer requested changes
101
- - `COMMENTED` - Review with comments only
102
- - `PENDING` - Review not yet submitted
103
-
104
- ### Issue Information
105
-
106
- ```bash
107
- # Get issue details
108
- gh issue view <number> --repo <owner>/<repo> --json title,body,comments,state,author
109
-
110
- # List issues
111
- gh issue list --repo <owner>/<repo> --json number,title,state
112
- ```
113
-
114
- ### Repository Information
115
-
116
- ```bash
117
- # Get repo info
118
- gh repo view <owner>/<repo> --json name,description,url
119
-
120
- # List workflows
121
- gh workflow list --repo <owner>/<repo>
122
-
123
- # View run logs
124
- gh run view <run-id> --repo <owner>/<repo> --log
125
- ```
126
-
127
- ### URL Parsing
128
-
129
- When given a GitHub URL, extract the components:
130
-
131
- | URL Pattern | Command |
132
- |-------------|---------|
133
- | `github.com/<owner>/<repo>/pull/<number>` | `gh pr view <number> --repo <owner>/<repo>` |
134
- | `github.com/<owner>/<repo>/issues/<number>` | `gh issue view <number> --repo <owner>/<repo>` |
135
- | `github.com/<owner>/<repo>` | `gh repo view <owner>/<repo>` |
136
- | `github.com/<owner>/<repo>/actions/runs/<id>` | `gh run view <id> --repo <owner>/<repo>` |
137
-
138
- ### JSON Output Fields
139
-
140
- Common useful JSON fields for PRs:
141
- - `title`, `body`, `state`, `author`
142
- - `comments` - PR comments
143
- - `reviews` - Review comments and approvals
144
- - `additions`, `deletions`, `changedFiles`
145
- - `files` - List of changed files
146
- - `commits` - Commit history
147
-
148
- ### Benefits Over WebFetch
149
-
150
- 1. **Complete data** - Access to all comments, reviews, and metadata
151
- 2. **Authentication** - Uses configured GitHub credentials
152
- 3. **Structured output** - JSON format for reliable parsing
153
- 4. **No rate limiting issues** - Authenticated requests have higher limits
154
- 5. **Access to private repos** - Works with repos you have access to
@@ -1,172 +0,0 @@
1
- # Testing
2
-
3
- This project uses Bun's built-in test runner for unit and integration tests.
4
-
5
- ## Test Types
6
-
7
- ### Unit/Integration Tests (`*.spec.ts`)
8
-
9
- - Standard Bun tests using `*.spec.ts` extension
10
- - Run with `bun test` command
11
- - Used for testing business logic, utilities, and non-visual functionality
12
-
13
- ### Docker Integration Tests (`*.docker.ts`)
14
-
15
- - Tests that require external services or API keys run in Docker containers
16
- - Use `*.docker.ts` extension
17
- - Run with `bun run test:docker`
18
-
19
- ## Running Tests
20
-
21
- ```bash
22
- # Run all unit tests
23
- bun test
24
-
25
- # Run a specific spec test file
26
- bun test path/to/file.spec.ts
27
-
28
- # Run tests matching a pattern
29
- bun test pattern
30
-
31
- # Run Docker integration tests (requires ANTHROPIC_API_KEY)
32
- ANTHROPIC_API_KEY=sk-... bun run test:docker
33
- ```
34
-
35
- ## Test Style Conventions
36
-
37
- ### Use `test` Instead of `it`
38
-
39
- Use `test` instead of `it` in test files for consistency:
40
-
41
- ```typescript
42
- // ✅ Good
43
- test('should create ACP client correctly', () => {
44
- // ...
45
- })
46
-
47
- // ❌ Avoid
48
- it('should create ACP client correctly', () => {
49
- // ...
50
- })
51
- ```
52
-
53
- ## Skill Script Tests
54
-
55
- Claude Code skills in `.claude/skills/` may include executable scripts. Tests for these scripts follow a specific structure:
56
-
57
- ### Directory Structure
58
-
59
- ```
60
- .claude/skills/<skill-name>/
61
- ├── SKILL.md
62
- ├── scripts/
63
- │ ├── script-name.ts # Executable script
64
- │ └── tests/
65
- │ └── script-name.spec.ts # Tests for the script
66
- ```
67
-
68
- ### Running Skill Script Tests
69
-
70
- ```bash
71
- # From skill directory
72
- bun test scripts/tests/
73
- ```
74
-
75
- ### Test Pattern
76
-
77
- Scripts that output JSON can be tested using Bun's shell API:
78
-
79
- ```typescript
80
- import { describe, test, expect } from 'bun:test'
81
- import { join } from 'node:path'
82
- import { $ } from 'bun'
83
-
84
- const scriptsDir = join(import.meta.dir, '..')
85
-
86
- describe('script-name', () => {
87
- test('outputs expected JSON', async () => {
88
- const result = await $`bun ${scriptsDir}/script-name.ts arg1 arg2`.json()
89
- expect(result.filePath).toEndWith('expected.ts')
90
- })
91
-
92
- test('exits with error on invalid input', async () => {
93
- const proc = Bun.spawn(['bun', `${scriptsDir}/script-name.ts`], {
94
- stderr: 'pipe',
95
- })
96
- const exitCode = await proc.exited
97
- expect(exitCode).toBe(1)
98
- })
99
- })
100
- ```
101
-
102
- ## Docker Integration Tests
103
-
104
- Tests that require the Anthropic API run in Docker containers for consistent, isolated execution.
105
-
106
- ### ACP Integration Tests
107
-
108
- The ACP client integration tests require the Anthropic API and run in a Docker container:
109
-
110
- ```bash
111
- # Run locally with Docker (requires ANTHROPIC_API_KEY)
112
- ANTHROPIC_API_KEY=sk-... docker compose -f docker-compose.test.yml run --rm acp-test
113
-
114
- # Or using the npm script (still requires Docker)
115
- ANTHROPIC_API_KEY=sk-... bun run test:docker
116
- ```
117
-
118
- ### File Naming
119
-
120
- - **`*.docker.ts`**: Tests that run in Docker containers
121
- - These are excluded from `bun test` and run separately in CI
122
-
123
- ### CI Workflow
124
-
125
- Docker tests use path filtering to reduce API costs:
126
-
127
- ```yaml
128
- # .github/workflows/ci.yml
129
- jobs:
130
- changes:
131
- # Detects which paths changed
132
- steps:
133
- - uses: dorny/paths-filter@v3
134
- with:
135
- filters: |
136
- acp:
137
- - 'src/**'
138
-
139
- test-acp-integration:
140
- needs: changes
141
- if: ${{ needs.changes.outputs.acp == 'true' }}
142
- # Only runs when src/ files change
143
- ```
144
-
145
- ## Anti-Patterns
146
-
147
- ### No Conditionals Around Assertions
148
-
149
- Never wrap assertions in conditionals. Tests should fail explicitly, not silently skip assertions.
150
-
151
- ```typescript
152
- // ❌ WRONG: Conditional assertion
153
- if (result) {
154
- expect(result.value).toBe(expected)
155
- }
156
-
157
- // ❌ WRONG: Optional chaining with assertion
158
- result?.value && expect(result.value).toBe(expected)
159
-
160
- // ✅ CORRECT: Assert the condition, then assert the value
161
- expect(result).toBeDefined()
162
- expect(result.value).toBe(expected)
163
-
164
- // ✅ CORRECT: Use type narrowing assertion
165
- expect(result).not.toBeNull()
166
- expect(result!.value).toBe(expected)
167
- ```
168
-
169
- If a value might not exist, the test should either:
170
- 1. Assert that it exists first, then check its value
171
- 2. Assert that it doesn't exist (if that's the expected behavior)
172
- 3. Restructure the test to ensure the value is always present