ai-or-die 0.1.22 → 0.1.24
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/.github/workflows/ci.yml +29 -4
- package/CLAUDE.md +14 -2
- package/docs/adrs/0008-e2e-parallelization.md +65 -0
- package/docs/adrs/0008-file-browser-architecture.md +82 -0
- package/docs/agent-instructions/02-testing-and-validation.md +19 -7
- package/docs/agent-instructions/03-tooling-and-pipelines.md +7 -8
- package/docs/agent-instructions/04-handoff-protocol.md +63 -0
- package/docs/agent-instructions/05-defensive-coding.md +170 -0
- package/docs/agent-instructions/06-ci-first-testing.md +268 -0
- package/docs/agent-instructions/07-docs-hygiene.md +124 -0
- package/docs/agent-instructions/08-multi-agent-consultation.md +168 -0
- package/docs/specs/client-app.md +106 -0
- package/docs/specs/file-browser.md +557 -0
- package/docs/specs/server.md +123 -0
- package/e2e/playwright.config.js +7 -3
- package/package.json +1 -1
- package/src/public/app.js +50 -0
- package/src/public/command-palette.js +36 -0
- package/src/public/components/file-browser.css +592 -0
- package/src/public/file-browser.js +1543 -0
- package/src/public/file-editor.js +549 -0
- package/src/public/icons.js +19 -0
- package/src/public/index.html +10 -0
- package/src/server.js +387 -15
- package/src/utils/file-utils.js +219 -0
package/.github/workflows/ci.yml
CHANGED
|
@@ -52,7 +52,7 @@ jobs:
|
|
|
52
52
|
playwright-report/
|
|
53
53
|
retention-days: 14
|
|
54
54
|
|
|
55
|
-
test-browser-functional:
|
|
55
|
+
test-browser-functional-core:
|
|
56
56
|
runs-on: ${{ matrix.os }}
|
|
57
57
|
strategy:
|
|
58
58
|
matrix:
|
|
@@ -65,13 +65,38 @@ jobs:
|
|
|
65
65
|
- run: npm ci
|
|
66
66
|
- name: Install Playwright browsers
|
|
67
67
|
run: npx playwright install chromium --with-deps
|
|
68
|
-
- name: Run functional
|
|
69
|
-
run: npx playwright test --config e2e/playwright.config.js --project functional
|
|
68
|
+
- name: Run functional core tests
|
|
69
|
+
run: npx playwright test --config e2e/playwright.config.js --project functional-core
|
|
70
70
|
- name: Upload Playwright report
|
|
71
71
|
uses: actions/upload-artifact@v4
|
|
72
72
|
if: ${{ !cancelled() }}
|
|
73
73
|
with:
|
|
74
|
-
name: playwright-functional-${{ matrix.os }}
|
|
74
|
+
name: playwright-functional-core-${{ matrix.os }}
|
|
75
|
+
path: |
|
|
76
|
+
e2e/test-results/
|
|
77
|
+
playwright-report/
|
|
78
|
+
retention-days: 14
|
|
79
|
+
|
|
80
|
+
test-browser-functional-extended:
|
|
81
|
+
runs-on: ${{ matrix.os }}
|
|
82
|
+
strategy:
|
|
83
|
+
matrix:
|
|
84
|
+
os: [ubuntu-latest, windows-latest]
|
|
85
|
+
steps:
|
|
86
|
+
- uses: actions/checkout@v4
|
|
87
|
+
- uses: actions/setup-node@v4
|
|
88
|
+
with:
|
|
89
|
+
node-version: '22'
|
|
90
|
+
- run: npm ci
|
|
91
|
+
- name: Install Playwright browsers
|
|
92
|
+
run: npx playwright install chromium --with-deps
|
|
93
|
+
- name: Run functional extended tests
|
|
94
|
+
run: npx playwright test --config e2e/playwright.config.js --project functional-extended
|
|
95
|
+
- name: Upload Playwright report
|
|
96
|
+
uses: actions/upload-artifact@v4
|
|
97
|
+
if: ${{ !cancelled() }}
|
|
98
|
+
with:
|
|
99
|
+
name: playwright-functional-extended-${{ matrix.os }}
|
|
75
100
|
path: |
|
|
76
101
|
e2e/test-results/
|
|
77
102
|
playwright-report/
|
package/CLAUDE.md
CHANGED
|
@@ -19,17 +19,29 @@ Available agents: **Architect**, **Engineer**, **QA Reviewer**, **Troubleshooter
|
|
|
19
19
|
|
|
20
20
|
### Documentation-Driven Workflow
|
|
21
21
|
Before starting any task, consult the relevant documentation:
|
|
22
|
-
- `docs/agent-instructions/` --
|
|
22
|
+
- `docs/agent-instructions/` -- Agent workflow guides:
|
|
23
|
+
- `00-philosophy.md` -- Core principles
|
|
24
|
+
- `01-research-and-web.md` -- Research guidelines
|
|
25
|
+
- `02-testing-and-validation.md` -- Testing standards
|
|
26
|
+
- `03-tooling-and-pipelines.md` -- Tooling conventions
|
|
27
|
+
- `04-handoff-protocol.md` -- How to leave the repo clean for the next agent
|
|
28
|
+
- `05-defensive-coding.md` -- Error prevention, cross-platform traps
|
|
29
|
+
- `06-ci-first-testing.md` -- CI-only testing, E2E debugging, performance budget
|
|
30
|
+
- `07-docs-hygiene.md` -- Keeping documentation in sync
|
|
31
|
+
- `08-multi-agent-consultation.md` -- When and how to consult expert subagents
|
|
23
32
|
- `docs/adrs/` -- Architecture Decision Records (check before proposing new patterns)
|
|
24
33
|
- `docs/specs/` -- Component specifications (read before implementing, update after changing behavior)
|
|
25
34
|
- `docs/architecture/` -- System diagrams and component overviews
|
|
26
|
-
- `docs/history/` --
|
|
35
|
+
- `docs/history/` -- Solved problems and debugging notes (check before debugging any issue)
|
|
27
36
|
|
|
28
37
|
### Mandatory Rules
|
|
29
38
|
1. **Spec updates with code changes**: When code behavior changes, the corresponding spec in `docs/specs/` must be updated in the same commit or PR.
|
|
30
39
|
2. **ADR compliance**: Never contradict an accepted ADR. To change direction, write a new ADR that supersedes the old one.
|
|
31
40
|
3. **Cross-platform support**: All code must work on both Windows and Linux. Use `path.join()` for file paths, provide `.sh` and `.ps1` script variants, and test on both platforms in CI.
|
|
32
41
|
4. **Test coverage**: Every feature and bug fix requires tests. No exceptions.
|
|
42
|
+
5. **CI-only testing**: All testing happens on GitHub Actions runners. Never test locally. E2E tests are the only true validation. Push → draft PR → CI → iterate.
|
|
43
|
+
6. **Document what you solve**: Every solved problem goes in `docs/history/`. LLMs don't carry memories — written docs are the only institutional memory.
|
|
44
|
+
7. **Consult before committing**: For significant decisions, spawn expert subagents (architect, principal engineer, lead QA, PM, designer, user researcher) in parallel. See `docs/agent-instructions/08-multi-agent-consultation.md`.
|
|
33
45
|
|
|
34
46
|
## Common Commands
|
|
35
47
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# ADR-0008: E2E Test Parallelization Strategy
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
**Accepted**
|
|
6
|
+
|
|
7
|
+
## Date
|
|
8
|
+
|
|
9
|
+
2026-02-07
|
|
10
|
+
|
|
11
|
+
## Context
|
|
12
|
+
|
|
13
|
+
The E2E test suite has grown to 16 spec files across 6 Playwright projects. The `functional` project — containing tests 02-07, 09-image-paste, and 09-background-notifications — runs approximately 30 tests sequentially with `workers: 1`. On GitHub Actions runners, this takes 7-15 minutes per platform, exceeding the 7-minute performance budget for CI feedback loops.
|
|
14
|
+
|
|
15
|
+
Fast CI feedback is critical because all testing happens exclusively on GitHub runners (no local testing). The push → CI → fix → push cycle must be fast enough that agents can iterate efficiently.
|
|
16
|
+
|
|
17
|
+
## Decision
|
|
18
|
+
|
|
19
|
+
Split the functional test group into two sub-groups and enable parallel workers in CI:
|
|
20
|
+
|
|
21
|
+
### Test Split
|
|
22
|
+
- **`functional-core`**: Tests `02-terminal-io`, `03-clipboard`, `04-context-menu`, `05-tab-switching` (core terminal interaction features)
|
|
23
|
+
- **`functional-extended`**: Tests `06-large-paste`, `07-vim-and-session`, `09-image-paste`, `09-background-notifications` (extended features and cross-cutting concerns)
|
|
24
|
+
|
|
25
|
+
### Parallel Workers
|
|
26
|
+
- Set `workers: process.env.CI ? 2 : 1` in `e2e/playwright.config.js`
|
|
27
|
+
- CI runs 2 Playwright workers per job for parallel test execution
|
|
28
|
+
- Local development retains 1 worker for debugging simplicity (though local testing is not the primary workflow)
|
|
29
|
+
|
|
30
|
+
### CI Pipeline Changes
|
|
31
|
+
- Replace single `test-browser-functional` job with two: `test-browser-functional-core` and `test-browser-functional-extended`
|
|
32
|
+
- Each runs independently and in parallel with all other browser test jobs
|
|
33
|
+
- Each uploads artifacts with distinct names for failure diagnosis
|
|
34
|
+
|
|
35
|
+
### Why this works
|
|
36
|
+
- Each test already creates its own server instance via `createServer()` with an ephemeral port (port 0)
|
|
37
|
+
- Sessions are per-server, eliminating cross-test state contamination
|
|
38
|
+
- Playwright provides browser context isolation between parallel tests
|
|
39
|
+
- No shared filesystem resources detected in the test suite
|
|
40
|
+
|
|
41
|
+
## Consequences
|
|
42
|
+
|
|
43
|
+
### Positive
|
|
44
|
+
|
|
45
|
+
- No CI job exceeds 7 minutes — faster feedback for the push-fix-push workflow
|
|
46
|
+
- More granular job names in CI (functional-core vs functional-extended) aid debugging — agents can immediately see which category of tests failed
|
|
47
|
+
- Parallel workers within jobs further reduce wall-clock time
|
|
48
|
+
- Sets a pattern for future test group splits as the suite grows
|
|
49
|
+
|
|
50
|
+
### Negative
|
|
51
|
+
|
|
52
|
+
- More CI jobs to monitor (6 browser test job types instead of 5, plus unit tests and build-binary)
|
|
53
|
+
- Artifact names become longer and more numerous
|
|
54
|
+
- If test isolation assumptions prove wrong, parallel execution could introduce flakiness (mitigated by the existing ephemeral-port pattern)
|
|
55
|
+
|
|
56
|
+
### Neutral
|
|
57
|
+
|
|
58
|
+
- Existing test files require no code changes — only configuration and CI workflow updates
|
|
59
|
+
- The `workers: 2` setting is conservative and can be increased if runners have sufficient resources
|
|
60
|
+
|
|
61
|
+
## Notes
|
|
62
|
+
|
|
63
|
+
- When any job approaches 6 minutes consistently, split it further
|
|
64
|
+
- When the test suite exceeds 80 tests, re-evaluate the overall split strategy
|
|
65
|
+
- Monitor for flaky tests that may indicate parallel execution issues
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# ADR-0008: File Browser Architecture
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
**Accepted**
|
|
6
|
+
|
|
7
|
+
## Date
|
|
8
|
+
|
|
9
|
+
2026-02-07
|
|
10
|
+
|
|
11
|
+
## Context
|
|
12
|
+
|
|
13
|
+
Users access ai-or-die remotely over devtunnels and similar tunnel services. In this environment there is no way to view visual content (images, PDFs), edit files with syntax highlighting, or upload/download files without native desktop tools -- which are not available through the tunnel. The existing `/api/folders` endpoint only lists directories for the working-directory selector and does not expose file contents or metadata.
|
|
14
|
+
|
|
15
|
+
The application needs a web-based file manager that supports browsing, previewing, editing, uploading, and downloading files directly within the terminal UI, without requiring users to install additional software or leave the browser.
|
|
16
|
+
|
|
17
|
+
## Decision
|
|
18
|
+
|
|
19
|
+
We introduce a file browser feature built on these architectural choices:
|
|
20
|
+
|
|
21
|
+
### REST API (not WebSocket) for file operations
|
|
22
|
+
|
|
23
|
+
File operations (list, read, write, upload, download) follow a request-response pattern. REST is a natural fit: clients send a request, wait for the response, and render the result. WebSocket would add unnecessary complexity for operations that do not require real-time streaming. The six new endpoints are:
|
|
24
|
+
|
|
25
|
+
| Endpoint | Method | Purpose |
|
|
26
|
+
|----------|--------|---------|
|
|
27
|
+
| `/api/files` | GET | List directory contents (files + directories), paginated |
|
|
28
|
+
| `/api/files/stat` | GET | File metadata (size, modified, MIME category, hash) |
|
|
29
|
+
| `/api/files/content` | GET | Read text file content in a JSON envelope with hash |
|
|
30
|
+
| `/api/files/content` | PUT | Save text file content with optimistic concurrency (hash) |
|
|
31
|
+
| `/api/files/upload` | POST | Upload file as base64 JSON (10 MB limit) |
|
|
32
|
+
| `/api/files/download` | GET | Stream file for download or inline preview |
|
|
33
|
+
|
|
34
|
+
### Right-docked side panel (not modal)
|
|
35
|
+
|
|
36
|
+
The file browser opens as a docked panel on the right side of the viewport. The terminal remains visible and interactive alongside it. A modal would block terminal access, which defeats the purpose of a file browser in a terminal application. The panel auto-switches to a full-screen overlay on mobile viewports or when the terminal would be squeezed below 80 columns.
|
|
37
|
+
|
|
38
|
+
### Ace Editor from CDN (not bundled)
|
|
39
|
+
|
|
40
|
+
Text editing uses the Ace Editor loaded from cdnjs, matching the existing pattern of loading xterm.js from unpkg CDN. This avoids adding npm dependencies for a frontend-only library and keeps the server-side `node_modules` minimal. Ace is lazy-loaded on first editor open, with a loading spinner and a 5-second timeout with fallback error.
|
|
41
|
+
|
|
42
|
+
### Hash-based optimistic concurrency for file saves
|
|
43
|
+
|
|
44
|
+
Every text file response includes an MD5 hash computed via streaming (`crypto.createHash` + `fs.createReadStream`). When saving, the client sends the original hash; the server recomputes and returns 409 Conflict if the file was modified externally. This prevents silent overwrites in multi-user or multi-tab scenarios.
|
|
45
|
+
|
|
46
|
+
### Extension-based MIME detection with binary heuristic
|
|
47
|
+
|
|
48
|
+
File type detection uses a built-in extension-to-MIME map for known types, supplemented by a null-byte heuristic (reading the first 512 bytes) for unknown extensions. This avoids depending on system-level `file` commands or npm packages like `mime-types`.
|
|
49
|
+
|
|
50
|
+
### Enhanced validatePath() with symlink resolution
|
|
51
|
+
|
|
52
|
+
The existing `validatePath()` function is extended to resolve symlinks via `fs.realpathSync()` before the `startsWith` check. This eliminates TOCTOU (time-of-check-to-time-of-use) race conditions where a symlink could be swapped between validation and access.
|
|
53
|
+
|
|
54
|
+
### File utilities extracted to src/utils/file-utils.js
|
|
55
|
+
|
|
56
|
+
File-related utility functions (`getFileInfo`, `computeFileHash`, `isBinaryFile`, `sanitizeFileName`) are extracted into a dedicated module rather than being added inline to `server.js` (already 1507 lines). This keeps the server file focused on routing and makes the utilities independently testable.
|
|
57
|
+
|
|
58
|
+
### No delete or rename in initial release
|
|
59
|
+
|
|
60
|
+
Destructive file operations are excluded from the MVP to limit the security surface area. Users can still delete or rename files through the terminal. These operations may be added in a follow-up phase.
|
|
61
|
+
|
|
62
|
+
## Consequences
|
|
63
|
+
|
|
64
|
+
### Positive
|
|
65
|
+
|
|
66
|
+
- Visual file preview (images, PDFs, JSON, CSV) works over tunnels without native tools
|
|
67
|
+
- Code editing with syntax highlighting and auto-save directly in the browser
|
|
68
|
+
- Drag-drop, file picker, and clipboard paste upload for getting files onto the remote machine
|
|
69
|
+
- Hash-based conflict detection prevents accidental data loss
|
|
70
|
+
- Extracted file utilities are independently testable
|
|
71
|
+
|
|
72
|
+
### Negative
|
|
73
|
+
|
|
74
|
+
- No real-time file watching -- directory listings are point-in-time snapshots (would require WebSocket, deferred to Phase 2)
|
|
75
|
+
- Ace Editor introduces a CDN dependency for the editing feature (editing is degraded if CDN is unreachable)
|
|
76
|
+
- 10 MB upload limit may be insufficient for large assets (chunked upload deferred to Phase 2)
|
|
77
|
+
|
|
78
|
+
### Neutral
|
|
79
|
+
|
|
80
|
+
- `/api/folders` and `/api/files` coexist: `/api/folders` lists only directories for the working-directory selector, `/api/files` lists both files and directories for the file browser
|
|
81
|
+
- The same `validatePath()` function secures both old and new endpoints
|
|
82
|
+
- No new npm dependencies are introduced
|
|
@@ -26,14 +26,26 @@ Write tests alongside implementation, not after. The workflow:
|
|
|
26
26
|
- Use temp directories for file system tests (see `session-store.test.js` pattern)
|
|
27
27
|
- Test cross-platform behavior: path construction, command resolution, shell detection
|
|
28
28
|
|
|
29
|
+
## CI-Only Testing
|
|
30
|
+
|
|
31
|
+
All testing happens on GitHub Actions runners. No local test runs. Ever.
|
|
32
|
+
|
|
33
|
+
- Local environments are unreliable: missing native modules, stale state, platform differences
|
|
34
|
+
- CI provides fresh, reproducible, cross-platform results every time
|
|
35
|
+
- E2E tests are the only true validation — if they pass on CI, the feature works
|
|
36
|
+
|
|
37
|
+
The workflow: write code → push to branch → open draft PR → CI runs → read results → fix → push again.
|
|
38
|
+
|
|
39
|
+
See `docs/agent-instructions/06-ci-first-testing.md` for the complete CI workflow guide, job map, and debugging playbook.
|
|
40
|
+
|
|
29
41
|
## Self-Validation
|
|
30
42
|
|
|
31
43
|
Before committing, every agent must:
|
|
32
44
|
|
|
33
|
-
1.
|
|
34
|
-
2.
|
|
35
|
-
3.
|
|
36
|
-
4. Verify the change doesn't break existing functionality
|
|
45
|
+
1. Push to branch and open a draft PR to trigger CI
|
|
46
|
+
2. Verify all CI jobs pass on both ubuntu-latest and windows-latest
|
|
47
|
+
3. Check `docs/history/` for known issues if any job fails
|
|
48
|
+
4. Verify the change doesn't break existing functionality (CI confirms this)
|
|
37
49
|
|
|
38
50
|
## What to Test
|
|
39
51
|
|
|
@@ -50,9 +62,9 @@ Before committing, every agent must:
|
|
|
50
62
|
- Auth middleware behavior
|
|
51
63
|
|
|
52
64
|
### For Client Changes
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
65
|
+
- E2E tests via Playwright (verified on CI, never locally)
|
|
66
|
+
- Mobile viewport tests via mobile-iphone and mobile-pixel Playwright projects
|
|
67
|
+
- WebSocket reconnection covered by E2E functional tests
|
|
56
68
|
|
|
57
69
|
## When Tests Fail
|
|
58
70
|
|
|
@@ -14,14 +14,13 @@ If you perform a verification task twice, script it. All scripts live in the `sc
|
|
|
14
14
|
|
|
15
15
|
### GitHub Actions
|
|
16
16
|
|
|
17
|
-
The CI pipeline (`.github/workflows/ci.yml`) runs on every push and PR:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
6. **Docs Check**: Verify docs/ structure exists
|
|
17
|
+
The CI pipeline (`.github/workflows/ci.yml`) runs on every push and PR. It runs 8 job types in parallel across ubuntu-latest and windows-latest (16 total jobs):
|
|
18
|
+
|
|
19
|
+
- **Unit tests**: `npm test` + `npm audit`
|
|
20
|
+
- **Browser E2E tests**: 6 Playwright job types (golden-path, functional-core, functional-extended, mobile, visual-regression, new-features)
|
|
21
|
+
- **Binary build**: SEA binary compilation + smoke tests
|
|
22
|
+
|
|
23
|
+
See `06-ci-first-testing.md` for the full CI job map, artifact details, and debugging workflow. CI is the only authority on whether code works (see ADR-0008 for the parallelization strategy).
|
|
25
24
|
|
|
26
25
|
### Release Pipeline
|
|
27
26
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Handoff Protocol
|
|
2
|
+
|
|
3
|
+
## The Golden Rule
|
|
4
|
+
|
|
5
|
+
Every session ends with a cleaner repo than it started. If you touched it, you documented it. If you broke it, you fixed it. If you couldn't finish, you left a trail.
|
|
6
|
+
|
|
7
|
+
## Pre-Handoff Checklist
|
|
8
|
+
|
|
9
|
+
Before ending any work session, verify:
|
|
10
|
+
|
|
11
|
+
1. **All CI jobs pass.** Push to your branch and check GitHub Actions. Both `ubuntu-latest` and `windows-latest` must be green. Do not hand off a red build.
|
|
12
|
+
2. **Documentation is updated.** Specs in `docs/specs/` match the current code. ADRs are written for any architectural decisions made during the session.
|
|
13
|
+
3. **No orphaned work-in-progress.** No half-implemented features sitting uncommitted. Everything is either committed and pushed, or explicitly tracked in a GitHub issue.
|
|
14
|
+
4. **Commit messages explain "why", not just "what".** A future agent reading the git log should understand the reasoning without opening the diff.
|
|
15
|
+
5. **New patterns and conventions are documented.** If you introduced a new coding pattern, utility, or convention, write it down in the relevant spec or instruction doc.
|
|
16
|
+
|
|
17
|
+
## Work-in-Progress Protocol
|
|
18
|
+
|
|
19
|
+
When you cannot finish a task:
|
|
20
|
+
|
|
21
|
+
- Create a GitHub issue with full context: what was attempted, where it stopped, what blockers exist, and what the next steps are.
|
|
22
|
+
- Use `[WIP]` prefix in commit messages for incomplete work.
|
|
23
|
+
- List which files are mid-change and what state they are in.
|
|
24
|
+
- Reference relevant specs, ADRs, and CI run links.
|
|
25
|
+
- Never leave broken tests on main. If your work breaks tests, either fix them or revert before ending.
|
|
26
|
+
|
|
27
|
+
## Clean Commit Hygiene
|
|
28
|
+
|
|
29
|
+
- Follow Conventional Commits: `feat:`, `fix:`, `docs:`, `test:`, `chore:`, `refactor:`.
|
|
30
|
+
- One concern per commit. Do not mix a bug fix with a feature addition.
|
|
31
|
+
- Reference GitHub issues in the message: `fix: resolve WebSocket race in image upload (#42)`.
|
|
32
|
+
- Commit messages should be self-contained. Another agent reading the git log should understand what happened and why without reading the diff.
|
|
33
|
+
|
|
34
|
+
## Session Context Dump
|
|
35
|
+
|
|
36
|
+
What to leave behind for the next agent:
|
|
37
|
+
|
|
38
|
+
- Updated specs in `docs/specs/` reflecting any behavior changes.
|
|
39
|
+
- Research findings documented in the relevant ADR or spec.
|
|
40
|
+
- Error patterns discovered during debugging added to `docs/history/`.
|
|
41
|
+
- Decisions made and their rationale recorded in ADRs.
|
|
42
|
+
- If you modified the CI pipeline, document what changed and why.
|
|
43
|
+
|
|
44
|
+
## Log What You Solved
|
|
45
|
+
|
|
46
|
+
When you encounter and solve a problem, document it in `docs/history/`. LLMs do not carry memories between sessions -- written docs are the only institutional memory. Every solved problem that is not documented is a problem that will be solved again.
|
|
47
|
+
|
|
48
|
+
See `07-docs-hygiene.md` for the history entry format and full guidelines. Before debugging any issue, always check `docs/history/` first.
|
|
49
|
+
|
|
50
|
+
## Anti-Patterns
|
|
51
|
+
|
|
52
|
+
Do NOT do any of these:
|
|
53
|
+
|
|
54
|
+
- Leave vague commit messages like "Made some changes" or "Updated stuff".
|
|
55
|
+
- Push uncommitted or unstaged work.
|
|
56
|
+
- Leave broken tests and move on.
|
|
57
|
+
- Make architectural decisions without writing an ADR.
|
|
58
|
+
- Solve a problem without documenting the solution.
|
|
59
|
+
- Skip spec updates when behavior changes.
|
|
60
|
+
- Assume the next agent will "figure it out".
|
|
61
|
+
- Delete or disable tests to make CI pass.
|
|
62
|
+
- Commit secrets, API keys, tokens, or `.env` files. Check `git diff --staged` for sensitive data before every commit.
|
|
63
|
+
- Expand scope beyond what was asked. If you discover adjacent issues, file them as separate GitHub issues. Do not expand scope without explicit approval.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Defensive Coding
|
|
2
|
+
|
|
3
|
+
## Validate at Boundaries
|
|
4
|
+
|
|
5
|
+
Trust nothing that crosses a system boundary. Every REST endpoint, WebSocket handler, and bridge method should validate its inputs before processing.
|
|
6
|
+
|
|
7
|
+
Where boundaries exist in this codebase:
|
|
8
|
+
|
|
9
|
+
- REST API handlers in `src/server.js` -- validate request params, body, headers
|
|
10
|
+
- WebSocket message handlers -- validate `type` field, required fields per message type
|
|
11
|
+
- Bridge methods (`startSession`, `sendInput`, `resize`) -- validate sessionId exists, dimensions are positive integers
|
|
12
|
+
- Client-to-server messages -- validate session ownership, check session is active
|
|
13
|
+
|
|
14
|
+
Pattern:
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
// Bad
|
|
18
|
+
handleMessage(wsId, message) {
|
|
19
|
+
const session = this.sessions.get(message.sessionId);
|
|
20
|
+
session.bridge.sendInput(message.data); // crashes if session doesn't exist
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Good
|
|
24
|
+
handleMessage(wsId, message) {
|
|
25
|
+
if (!message.sessionId) {
|
|
26
|
+
return this.sendError(wsId, 'Missing sessionId');
|
|
27
|
+
}
|
|
28
|
+
const session = this.sessions.get(message.sessionId);
|
|
29
|
+
if (!session) {
|
|
30
|
+
return this.sendError(wsId, `Session '${message.sessionId}' not found`);
|
|
31
|
+
}
|
|
32
|
+
if (!session.active) {
|
|
33
|
+
return this.sendError(wsId, `Session '${message.sessionId}' is not active`);
|
|
34
|
+
}
|
|
35
|
+
session.bridge.sendInput(message.data);
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Error Messages Are UI
|
|
40
|
+
|
|
41
|
+
Error messages are read by other agents trying to debug. Make them actionable.
|
|
42
|
+
|
|
43
|
+
Every error message should answer three questions:
|
|
44
|
+
|
|
45
|
+
1. What went wrong?
|
|
46
|
+
2. What was expected?
|
|
47
|
+
3. What should be done about it?
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
// Bad
|
|
51
|
+
throw new Error('Invalid');
|
|
52
|
+
throw new Error('Not found');
|
|
53
|
+
throw new Error('Failed');
|
|
54
|
+
|
|
55
|
+
// Good
|
|
56
|
+
throw new Error(`Session '${sessionId}' not found. Available sessions: [${[...sessions.keys()].join(', ')}]`);
|
|
57
|
+
throw new Error(`Bridge '${toolId}' is not available. Run 'which ${command}' to verify installation. Searched paths: ${searchPaths.join(', ')}`);
|
|
58
|
+
throw new Error(`WebSocket message missing required field 'type'. Received: ${JSON.stringify(message)}`);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Cross-Platform Landmines
|
|
62
|
+
|
|
63
|
+
This codebase runs on both Windows and Linux. Every line of code that touches the filesystem, spawns a process, or handles paths must account for both.
|
|
64
|
+
|
|
65
|
+
### Paths
|
|
66
|
+
|
|
67
|
+
- ALWAYS use `path.join()`, never string concatenation with `/` or `\\`
|
|
68
|
+
- Use `os.homedir()`, never `process.env.HOME` (undefined on Windows)
|
|
69
|
+
- File paths are case-insensitive on Windows, case-sensitive on Linux
|
|
70
|
+
- Use `path.resolve()` to normalize paths before comparison
|
|
71
|
+
|
|
72
|
+
### Process Spawning
|
|
73
|
+
|
|
74
|
+
- `where` on Windows, `which` on Linux -- check `process.platform`
|
|
75
|
+
- Windows uses ConPTY, Linux uses standard PTY -- different buffering behavior
|
|
76
|
+
- Executable extensions: `.exe`, `.cmd` on Windows, none on Linux
|
|
77
|
+
- Shell: `cmd.exe` or `powershell.exe` on Windows, `bash` or `sh` on Linux
|
|
78
|
+
|
|
79
|
+
### Line Endings
|
|
80
|
+
|
|
81
|
+
- Never match output with exact strings -- use `.includes()` or `.trim()`
|
|
82
|
+
- Windows may inject `\r\n` where Linux gives `\n`
|
|
83
|
+
- PTY output may contain ANSI escape sequences -- strip them before comparing
|
|
84
|
+
|
|
85
|
+
### The ConPTY Quirks
|
|
86
|
+
|
|
87
|
+
- Writes larger than 4096 bytes can overflow the ConPTY buffer on Windows
|
|
88
|
+
- Solution: chunked writes with delays (see `base-bridge.js` chunked write pattern)
|
|
89
|
+
- ConPTY may echo input back -- don't assume output is only from the spawned process
|
|
90
|
+
|
|
91
|
+
## Async Safety
|
|
92
|
+
|
|
93
|
+
Node.js is async-first. Unhandled promise rejections crash the process.
|
|
94
|
+
|
|
95
|
+
Rules:
|
|
96
|
+
|
|
97
|
+
- Every `async` function must have try-catch at the top level
|
|
98
|
+
- Every `.then()` chain must have a `.catch()`
|
|
99
|
+
- Event handlers that call async code must wrap in try-catch
|
|
100
|
+
- Use the spawn watchdog pattern from `base-bridge.js`: set a timer when spawning a process, kill it if no output arrives within 30 seconds
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
// Bad -- unhandled rejection if startSession throws
|
|
104
|
+
ws.on('message', (data) => {
|
|
105
|
+
const msg = JSON.parse(data);
|
|
106
|
+
this.startSession(msg.sessionId);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Good
|
|
110
|
+
ws.on('message', (data) => {
|
|
111
|
+
try {
|
|
112
|
+
const msg = JSON.parse(data);
|
|
113
|
+
this.startSession(msg.sessionId).catch(err => {
|
|
114
|
+
console.error(`Failed to start session ${msg.sessionId}:`, err);
|
|
115
|
+
this.sendError(wsId, err.message);
|
|
116
|
+
});
|
|
117
|
+
} catch (err) {
|
|
118
|
+
console.error('Failed to parse WebSocket message:', err);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Fail Fast, Fail Loud
|
|
124
|
+
|
|
125
|
+
Silent failures are the worst kind. They create bugs that surface hours or sessions later, with no trail.
|
|
126
|
+
|
|
127
|
+
- Assert preconditions at function entry -- don't wait until line 50 to discover the input was invalid
|
|
128
|
+
- Log errors with full context before re-throwing: what function, what inputs, what state
|
|
129
|
+
- Never `catch` and silently swallow: `catch (err) { /* ignore */ }` -- this is forbidden
|
|
130
|
+
- If something "shouldn't happen," make it throw, not silently return null
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
// Bad -- silent null propagation
|
|
134
|
+
function getSession(id) {
|
|
135
|
+
return sessions.get(id); // returns undefined silently
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Good -- fail fast with context
|
|
139
|
+
function getSession(id) {
|
|
140
|
+
const session = sessions.get(id);
|
|
141
|
+
if (!session) {
|
|
142
|
+
throw new Error(`getSession: no session with id '${id}'. Active sessions: ${sessions.size}`);
|
|
143
|
+
}
|
|
144
|
+
return session;
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## The "Fresh Machine" Test
|
|
149
|
+
|
|
150
|
+
Before considering any code complete, ask yourself: "Would this work on a brand new GitHub Actions runner with nothing pre-installed except Node.js 22?"
|
|
151
|
+
|
|
152
|
+
This means:
|
|
153
|
+
|
|
154
|
+
- No reliance on globally installed tools (unless you check for them and give a clear error)
|
|
155
|
+
- No hardcoded paths that only exist on your dev machine
|
|
156
|
+
- No cached `node_modules` assumptions -- `npm ci` installs from scratch
|
|
157
|
+
- No file system state left over from previous runs
|
|
158
|
+
- No environment variables that aren't set in CI
|
|
159
|
+
|
|
160
|
+
If the answer is "maybe," add a runtime check:
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
const commandPath = await this.findCommandAsync();
|
|
164
|
+
if (!commandPath) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
`${this.toolName} CLI not found. Searched: ${this.searchPaths.join(', ')}. ` +
|
|
167
|
+
`Install ${this.toolName} or add it to PATH.`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
```
|