@hienlh/ppm 0.1.0 → 0.1.2

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 (76) hide show
  1. package/dist/web/assets/api-client-Bnf9LAt4.js +1 -0
  2. package/dist/web/assets/arrow-up-from-line-BXL5dtbG.js +1 -0
  3. package/dist/web/assets/button-BxijdhtM.js +1 -0
  4. package/dist/web/assets/chat-tab-BZopEuub.js +61 -0
  5. package/dist/web/assets/code-editor-hbllHzj7.js +2 -0
  6. package/dist/web/assets/createLucideIcon-Dy1wlrF7.js +1 -0
  7. package/dist/web/assets/dialog-RczsXsmw.js +45 -0
  8. package/dist/web/assets/diff-viewer-D6ixPlNB.js +4 -0
  9. package/dist/web/assets/dist-CSp7ir0r.js +46 -0
  10. package/dist/web/assets/external-link-WSiY-639.js +1 -0
  11. package/dist/web/assets/git-graph-DXMB_DoT.js +1 -0
  12. package/dist/web/assets/git-status-panel-D8ZUQrRF.js +1 -0
  13. package/dist/web/assets/index-DGSLw2GE.js +10 -0
  14. package/dist/web/assets/index-DYd_2slk.css +2 -0
  15. package/dist/web/assets/jsx-runtime-BnxRlLMJ.js +1 -0
  16. package/dist/web/assets/project-list-DWVXEimw.js +1 -0
  17. package/dist/web/assets/react-Uzd0zARU.js +1 -0
  18. package/dist/web/assets/refresh-cw-DtopuYJf.js +1 -0
  19. package/dist/web/assets/settings-tab-DJRzIAuP.js +1 -0
  20. package/dist/web/assets/terminal-tab-BrP-ENHg.css +1 -0
  21. package/dist/web/assets/terminal-tab-CbwaI-oq.js +36 -0
  22. package/dist/web/assets/trash-2-CHLebaNh.js +1 -0
  23. package/dist/web/assets/utils-Cgi2TYRi.js +1 -0
  24. package/dist/web/assets/x-BISR7bpK.js +1 -0
  25. package/dist/web/icon-192.svg +5 -0
  26. package/dist/web/icon-512.svg +5 -0
  27. package/dist/web/index.html +25 -0
  28. package/dist/web/manifest.webmanifest +1 -0
  29. package/dist/web/registerSW.js +1 -0
  30. package/dist/web/sw.js +1 -0
  31. package/dist/web/workbox-3e722498.js +1 -0
  32. package/package.json +2 -1
  33. package/src/server/index.ts +17 -3
  34. package/.claude/agent-memory/tester/MEMORY.md +0 -3
  35. package/.claude/agent-memory/tester/project-ppm-test-conventions.md +0 -32
  36. package/.github/workflows/release.yml +0 -46
  37. package/plans/260314-2009-ppm-implementation/phase-01-project-skeleton.md +0 -81
  38. package/plans/260314-2009-ppm-implementation/phase-02-backend-core.md +0 -148
  39. package/plans/260314-2009-ppm-implementation/phase-03-frontend-shell.md +0 -256
  40. package/plans/260314-2009-ppm-implementation/phase-04-file-explorer-editor.md +0 -120
  41. package/plans/260314-2009-ppm-implementation/phase-05-web-terminal.md +0 -174
  42. package/plans/260314-2009-ppm-implementation/phase-06-git-integration.md +0 -244
  43. package/plans/260314-2009-ppm-implementation/phase-07-ai-chat.md +0 -242
  44. package/plans/260314-2009-ppm-implementation/phase-08-cli-commands.md +0 -143
  45. package/plans/260314-2009-ppm-implementation/phase-09-pwa-build-deploy.md +0 -209
  46. package/plans/260314-2009-ppm-implementation/phase-10-testing.md +0 -311
  47. package/plans/260314-2009-ppm-implementation/plan.md +0 -202
  48. package/plans/260315-0356-project-scoped-api-refactor/phase-01-backend-project-router.md +0 -145
  49. package/plans/260315-0356-project-scoped-api-refactor/phase-02-frontend-api-migration.md +0 -107
  50. package/plans/260315-0356-project-scoped-api-refactor/phase-03-per-project-tabs.md +0 -100
  51. package/plans/260315-0356-project-scoped-api-refactor/phase-04-websocket-migration.md +0 -66
  52. package/plans/260315-0356-project-scoped-api-refactor/plan.md +0 -87
  53. package/plans/reports/brainstorm-260314-1938-final-techstack.md +0 -342
  54. package/plans/reports/docs-manager-260315-1314-documentation-creation.md +0 -386
  55. package/plans/reports/fullstack-developer-260314-2252-phase-02-backend-core.md +0 -57
  56. package/plans/reports/fullstack-developer-260314-2253-phase-03-frontend-shell.md +0 -70
  57. package/plans/reports/fullstack-developer-260314-2300-phase-04-05-file-api-terminal-ws.md +0 -49
  58. package/plans/reports/fullstack-developer-260314-2300-phase-04-05-file-explorer-editor-terminal.md +0 -52
  59. package/plans/reports/fullstack-developer-260314-2307-ai-chat-phase7.md +0 -58
  60. package/plans/reports/fullstack-developer-260314-2307-phase-06-git-integration.md +0 -33
  61. package/plans/reports/research-260314-1911-ppm-tech-stack.md +0 -318
  62. package/plans/reports/research-260314-1930-claude-code-integration.md +0 -293
  63. package/plans/reports/researcher-260314-2232-node-pty-bun-crash-analysis.md +0 -305
  64. package/plans/reports/researcher-260314-2232-ui-style.md +0 -942
  65. package/plans/reports/researcher-260315-0300-opcode-claude-interaction.md +0 -745
  66. package/plans/reports/researcher-260315-0303-opcode-deep-analysis.md +0 -742
  67. package/plans/reports/researcher-260315-0305-claude-agent-sdk-github-research.md +0 -423
  68. package/plans/reports/tester-260314-2053-initial-test-suite.md +0 -81
  69. package/repomix-output.xml +0 -23745
  70. package/tests/integration/api/chat-routes.test.ts +0 -95
  71. package/tests/integration/claude-agent-sdk-integration.test.ts +0 -228
  72. package/tests/integration/ws/chat-websocket.test.ts +0 -312
  73. package/tests/test-setup.ts +0 -5
  74. package/tests/unit/providers/claude-agent-sdk.test.ts +0 -339
  75. package/tests/unit/providers/mock-provider.test.ts +0 -143
  76. package/tests/unit/services/chat-service.test.ts +0 -100
@@ -1,209 +0,0 @@
1
- # Phase 9: PWA + Build + Deploy
2
-
3
- **Owner:** Lead
4
- **Priority:** Medium
5
- **Depends on:** All previous phases
6
- **Effort:** Medium
7
-
8
- ## Overview
9
-
10
- PWA configuration, production build pipeline, single binary compilation, CI/CD for cross-platform releases.
11
-
12
- ## PWA Setup
13
-
14
- ### vite-plugin-pwa Config
15
- ```typescript
16
- // vite.config.ts
17
- import { VitePWA } from 'vite-plugin-pwa';
18
-
19
- export default defineConfig({
20
- plugins: [
21
- react(),
22
- VitePWA({
23
- registerType: 'autoUpdate',
24
- manifest: {
25
- name: 'PPM - Personal Project Manager',
26
- short_name: 'PPM',
27
- description: 'Mobile-first web IDE for managing code projects',
28
- theme_color: '#0f172a',
29
- background_color: '#0f172a',
30
- display: 'standalone',
31
- orientation: 'any',
32
- icons: [
33
- { src: '/icon-192.png', sizes: '192x192', type: 'image/png' },
34
- { src: '/icon-512.png', sizes: '512x512', type: 'image/png' },
35
- ],
36
- },
37
- workbox: {
38
- globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
39
- // Cache UI shell for offline. API calls always need network.
40
- runtimeCaching: [
41
- {
42
- urlPattern: /^https?:\/\/.*\/api\//,
43
- handler: 'NetworkOnly', // API = always online
44
- },
45
- ],
46
- },
47
- }),
48
- ],
49
- });
50
- ```
51
-
52
- ### PWA Features
53
- - Install prompt on mobile
54
- - Offline: UI shell loads, shows "No connection" for API features
55
- - App icon + splash screen
56
-
57
- ## Build Pipeline
58
-
59
- ### scripts/build.ts
60
- ```typescript
61
- import { $ } from 'bun';
62
-
63
- // 1. Build frontend (Vite)
64
- await $`bun run vite build --outDir dist/web`;
65
-
66
- // 2. Compile backend + embedded frontend into single binary
67
- await $`bun build src/index.ts --compile --outfile dist/ppm`;
68
-
69
- // For cross-platform:
70
- // bun build src/index.ts --compile --target=bun-linux-x64 --outfile dist/ppm-linux-x64
71
- // bun build src/index.ts --compile --target=bun-darwin-arm64 --outfile dist/ppm-darwin-arm64
72
- ```
73
-
74
- ### Static File Embedding
75
- ```typescript
76
- // server/routes/static.ts
77
- // In dev: proxy to Vite dev server
78
- // In prod: serve from embedded dist/web/ directory
79
-
80
- if (process.env.NODE_ENV === 'production') {
81
- // Serve built files
82
- app.use('/*', serveStatic({ root: './dist/web' }));
83
- // SPA fallback
84
- app.get('*', (c) => c.html(readFileSync('./dist/web/index.html', 'utf-8')));
85
- } else {
86
- // Proxy to Vite dev server
87
- // Or just run Vite separately
88
- }
89
- ```
90
-
91
- ### Package.json Scripts
92
- ```json
93
- {
94
- "scripts": {
95
- "dev": "concurrently \"bun run --hot src/index.ts start\" \"bun run vite\"",
96
- "build": "bun run scripts/build.ts",
97
- "build:linux": "bun build src/index.ts --compile --target=bun-linux-x64 --outfile dist/ppm-linux-x64",
98
- "build:mac-arm": "bun build src/index.ts --compile --target=bun-darwin-arm64 --outfile dist/ppm-darwin-arm64",
99
- "build:mac-x64": "bun build src/index.ts --compile --target=bun-darwin-x64 --outfile dist/ppm-darwin-x64"
100
- }
101
- }
102
- ```
103
-
104
- ## CI/CD (GitHub Actions)
105
-
106
- ### .github/workflows/release.yml
107
- ```yaml
108
- name: Release
109
- on:
110
- push:
111
- tags: ['v*']
112
-
113
- jobs:
114
- build:
115
- strategy:
116
- matrix:
117
- include:
118
- - os: ubuntu-latest
119
- target: bun-linux-x64
120
- artifact: ppm-linux-x64
121
- - os: macos-latest
122
- target: bun-darwin-arm64
123
- artifact: ppm-darwin-arm64
124
- - os: macos-13
125
- target: bun-darwin-x64
126
- artifact: ppm-darwin-x64
127
-
128
- runs-on: ${{ matrix.os }}
129
- steps:
130
- - uses: actions/checkout@v4
131
- - uses: oven-sh/setup-bun@v2
132
- - run: bun install
133
- - run: bun run vite build --outDir dist/web
134
- - run: bun build src/index.ts --compile --target=${{ matrix.target }} --outfile dist/${{ matrix.artifact }}
135
- - uses: actions/upload-artifact@v4
136
- with:
137
- name: ${{ matrix.artifact }}
138
- path: dist/${{ matrix.artifact }}
139
-
140
- release:
141
- needs: build
142
- runs-on: ubuntu-latest
143
- steps:
144
- - uses: actions/download-artifact@v4
145
- - uses: softprops/action-gh-release@v2
146
- with:
147
- files: |
148
- ppm-linux-x64/ppm-linux-x64
149
- ppm-darwin-arm64/ppm-darwin-arm64
150
- ppm-darwin-x64/ppm-darwin-x64
151
- ```
152
-
153
- ### Dockerfile (fallback)
154
- ```dockerfile
155
- FROM oven/bun:1.2-alpine
156
- WORKDIR /app
157
- COPY package.json bun.lock ./
158
- RUN bun install --production
159
- COPY dist/ ./dist/
160
- COPY src/ ./src/
161
- EXPOSE 8080
162
- CMD ["bun", "run", "src/index.ts", "start"]
163
- ```
164
-
165
- ## Deployment Docs
166
-
167
- ### Local
168
- ```bash
169
- # Install
170
- curl -fsSL https://github.com/user/ppm/releases/latest/download/ppm-$(uname -s | tr A-Z a-z)-$(uname -m) -o /usr/local/bin/ppm
171
- chmod +x /usr/local/bin/ppm
172
-
173
- # Setup
174
- ppm init
175
- ppm start
176
- ```
177
-
178
- ### VPS
179
- ```bash
180
- scp ppm-linux-x64 user@vps:/usr/local/bin/ppm
181
- scp ppm.yaml user@vps:/etc/ppm/config.yaml
182
- ssh user@vps "ppm start -c /etc/ppm/config.yaml -d"
183
- ```
184
-
185
- ## Static File Embedding (NEEDS INVESTIGATION)
186
-
187
- **Known issue:** `bun build --compile` bundles JS but does NOT auto-embed static files (HTML, CSS, images).
188
-
189
- **Options to investigate:**
190
- 1. Use `Bun.file()` with `import.meta.dir` to reference files relative to binary
191
- 2. Use `import with { type: "file" }` syntax to embed at build time
192
- 3. Inline frontend assets into a JS module at build time (custom build step)
193
- 4. Ship `dist/web/` alongside binary (not single-file, but simpler)
194
-
195
- **TODO:** Test each approach before Phase 9 implementation. Research `bun build --compile` static file embedding in Bun docs.
196
-
197
- ## Success Criteria
198
-
199
- - [ ] PWA installable on mobile: "Add to Home Screen" prompt appears
200
- - [ ] PWA offline: UI shell loads without network, shows "No connection" for API features
201
- - [ ] `bun run build` completes without errors, produces binary + web assets
202
- - [ ] Built binary starts server and serves frontend at `http://localhost:<port>/`
203
- - [ ] Frontend served by binary is fully functional (not blank page)
204
- - [ ] API routes work through built binary (not just dev mode)
205
- - [ ] Cross-platform binaries compile in CI (linux-x64, darwin-arm64, darwin-x64)
206
- - [ ] GitHub Release created with binaries on tag push (v* tags)
207
- - [ ] Docker image builds and runs: `docker run -p 8080:8080 ppm` serves app
208
- - [ ] Fresh install flow works: download binary → `ppm init` → `ppm start` → browser opens → app works
209
- - [ ] App icon shows correctly on mobile homescreen
@@ -1,311 +0,0 @@
1
- # Phase 10: Testing
2
-
3
- **Owner:** tester
4
- **Priority:** High
5
- **Depends on:** Runs continuously after each phase completes
6
- **Effort:** Large
7
-
8
- ## Overview
9
-
10
- Comprehensive unit + integration tests for both backend services AND frontend logic. E2E smoke tests for full flow.
11
-
12
- ## Test Framework
13
-
14
- - **Runner:** `bun test` (built-in, Jest-compatible)
15
- - **HTTP testing:** Hono test client (`app.request()`)
16
- - **WS testing:** Native WebSocket client in Bun
17
- - **Frontend logic testing:** `bun test` for store/lib/hook logic (no DOM needed for pure logic)
18
-
19
- ## Test Structure
20
- ```
21
- tests/
22
- ├── setup.ts # Global setup (test config, temp dirs)
23
- ├── unit/
24
- │ ├── services/
25
- │ │ ├── config.service.test.ts
26
- │ │ ├── project.service.test.ts
27
- │ │ ├── file.service.test.ts
28
- │ │ ├── git.service.test.ts
29
- │ │ ├── terminal.service.test.ts
30
- │ │ └── chat.service.test.ts
31
- │ ├── providers/
32
- │ │ ├── claude-agent-sdk.test.ts
33
- │ │ └── registry.test.ts
34
- │ ├── cli/
35
- │ │ └── project-resolver.test.ts
36
- │ ├── server/
37
- │ │ ├── resolve-project.test.ts
38
- │ │ └── auth-middleware.test.ts
39
- │ └── web/
40
- │ ├── stores/
41
- │ │ ├── tab.store.test.ts
42
- │ │ ├── project.store.test.ts
43
- │ │ └── settings.store.test.ts
44
- │ ├── lib/
45
- │ │ ├── api-client.test.ts
46
- │ │ ├── ws-client.test.ts
47
- │ │ └── git-graph-layout.test.ts
48
- │ └── hooks/
49
- │ └── use-websocket.test.ts
50
- ├── integration/
51
- │ ├── api/
52
- │ │ ├── auth.test.ts
53
- │ │ ├── projects.test.ts
54
- │ │ ├── files.test.ts
55
- │ │ └── git.test.ts
56
- │ └── ws/
57
- │ ├── terminal.test.ts
58
- │ └── chat.test.ts
59
- └── e2e/
60
- └── smoke.test.ts # Full flow: init → start → use → stop
61
- ```
62
-
63
- ## Test Strategy Per Phase
64
-
65
- ### Phase 2 Tests (Backend Core)
66
-
67
- **Config Service:**
68
- - [ ] Loads config from `./ppm.yaml` when present
69
- - [ ] Falls back to `~/.ppm/config.yaml` when no local config
70
- - [ ] Creates default config with generated auth token on first run
71
- - [ ] `get('port')` returns configured port
72
- - [ ] `set('port', 9090)` persists change to file
73
- - [ ] Invalid YAML file → throws descriptive error
74
-
75
- **Project Service:**
76
- - [ ] `add("/path/to/repo", "myrepo")` adds project to config
77
- - [ ] `add` with duplicate name → throws error
78
- - [ ] `remove("myrepo")` removes project from config
79
- - [ ] `list()` returns all registered projects
80
- - [ ] `resolve("myrepo")` returns project by name
81
- - [ ] `resolve` from CWD → auto-detects project when CWD is inside registered project path
82
- - [ ] `resolve("nonexistent")` → throws "Project not found" error
83
- - [ ] `scanForGitRepos(dir)` finds `.git` directories recursively
84
-
85
- **Auth Middleware:**
86
- - [ ] Valid Bearer token → passes, sets context
87
- - [ ] Missing Authorization header → 401 `{ ok: false, error: "Unauthorized" }`
88
- - [ ] Invalid token → 401
89
- - [ ] `auth.enabled: false` in config → all requests pass without token
90
- - [ ] Token from config matches what's checked
91
-
92
- **Server:**
93
- - [ ] Server starts on configured port
94
- - [ ] `GET /api/health` returns 200
95
- - [ ] SPA fallback: `GET /nonexistent` returns `index.html` (not 404)
96
- - [ ] API 404: `GET /api/nonexistent` returns 404 JSON
97
-
98
- **Resolve Project Helper:**
99
- - [ ] `resolveProjectPath("ppm")` → returns path from config
100
- - [ ] `resolveProjectPath("/absolute/path")` → validates path is within registered project
101
- - [ ] `resolveProjectPath("../escape")` → throws (path traversal)
102
-
103
- ### Phase 3 Tests (Frontend Logic)
104
-
105
- **Tab Store:**
106
- - [ ] `openTab({type: 'terminal', title: 'Terminal'})` adds tab and returns id
107
- - [ ] `openTab` with duplicate type+metadata → returns existing tab id (no duplicate)
108
- - [ ] `closeTab(id)` removes tab from list
109
- - [ ] `closeTab` on last tab → activeTabId becomes null
110
- - [ ] `setActiveTab(id)` updates activeTabId
111
- - [ ] `updateTab(id, {title: 'new'})` updates tab properties
112
- - [ ] Closing active tab → activates previous tab (not first, not null)
113
-
114
- **API Client:**
115
- - [ ] `get<T>('/api/projects')` unwraps `{ok: true, data: [...]}` → returns `[...]`
116
- - [ ] Server returns `{ok: false, error: "Not found"}` → throws Error with "Not found" message
117
- - [ ] Server returns HTTP 500 → throws Error
118
- - [ ] Bearer token header sent on every request when token is set
119
- - [ ] No Authorization header when token is null/undefined
120
-
121
- **WS Client:**
122
- - [ ] Connects to WebSocket URL
123
- - [ ] Auto-reconnects on close with exponential backoff (1s, 2s, 4s)
124
- - [ ] Max reconnect delay caps at 30s
125
- - [ ] `send()` queues messages if not connected, sends on reconnect
126
- - [ ] `onMessage` callback fires for incoming messages
127
- - [ ] `disconnect()` stops reconnect attempts
128
-
129
- **Git Graph Layout:**
130
- - [ ] Single branch → all commits in lane 0
131
- - [ ] Two branches → merge commit connects lanes correctly
132
- - [ ] Lane reuse: closed branch lane gets reused by next branch
133
- - [ ] Empty commits list → returns empty layout
134
-
135
- ### Phase 4 Tests (File Explorer)
136
-
137
- **File Service:**
138
- - [ ] `getTree(projectPath)` returns nested FileNode structure
139
- - [ ] `getTree` excludes `.git/`, `node_modules/`
140
- - [ ] `readFile(path)` returns content as string
141
- - [ ] `readFile` for binary file → returns base64 with encoding flag
142
- - [ ] `writeFile(path, content)` creates/updates file
143
- - [ ] `createFile(path, 'file')` creates empty file
144
- - [ ] `createFile(path, 'directory')` creates directory
145
- - [ ] `deleteFile(path)` removes file
146
- - [ ] `renameFile(old, new)` renames file
147
- - [ ] Path traversal: `readFile("../../etc/passwd")` → throws error
148
- - [ ] Access `.git/config` → throws error
149
- - [ ] Access `.env` → throws error
150
-
151
- **File API Integration:**
152
- - [ ] `GET /api/files/tree/myproject` returns file tree (project resolved by name)
153
- - [ ] `GET /api/files/read?path=src/index.ts` returns file content
154
- - [ ] `PUT /api/files/write` with `{path, content}` writes file
155
- - [ ] `DELETE /api/files/delete` with `{path}` removes file
156
- - [ ] Invalid project name → 404
157
- - [ ] Path outside project → 403
158
-
159
- ### Phase 5 Tests (Terminal)
160
-
161
- **Terminal Service:**
162
- - [ ] `create({projectPath})` spawns shell process and returns session
163
- - [ ] `get(id)` returns existing session
164
- - [ ] `get(nonexistent)` returns undefined
165
- - [ ] `write(id, "ls\n")` sends input to PTY
166
- - [ ] `onData(id, handler)` receives shell output
167
- - [ ] `kill(id)` terminates process and removes session
168
- - [ ] `list()` returns all active sessions
169
- - [ ] Output buffer: last 10KB of output stored per session
170
-
171
- **Terminal WS Integration:**
172
- - [ ] Connect to `/ws/terminal/:id` → creates new PTY if not exists
173
- - [ ] Send keystroke via WS → appears in PTY
174
- - [ ] PTY output → arrives via WS message
175
- - [ ] Send resize control message → PTY resizes (if supported)
176
- - [ ] Disconnect WS → PTY stays alive for 30s
177
- - [ ] Reconnect within 30s → receives buffered output
178
- - [ ] Reconnect after timeout → session dead, returns error
179
- - [ ] Multiple WS clients to same session → both receive output
180
-
181
- ### Phase 6 Tests (Git)
182
-
183
- **Git Service (using real temp repos):**
184
- - [ ] `status(path)` → returns modified/staged/untracked files
185
- - [ ] `stage(path, ["file.txt"])` → file appears in staged list
186
- - [ ] `unstage(path, ["file.txt"])` → file moves back to unstaged
187
- - [ ] `commit(path, "msg")` → creates commit, returns hash
188
- - [ ] `commit` with nothing staged → throws error
189
- - [ ] `branches(path)` → returns branch list with current marked
190
- - [ ] `createBranch(path, "feature")` → branch exists
191
- - [ ] `checkout(path, "feature")` → current branch changes
192
- - [ ] `deleteBranch(path, "feature")` → branch removed
193
- - [ ] `deleteBranch` on current branch → throws error
194
- - [ ] `graphData(path)` → returns commits with parents, refs, branch info
195
- - [ ] `graphData` uses simple-git `.log()` (not manual parse) → no garbled data on multi-line commit messages
196
- - [ ] `diff(path)` → returns unified diff string
197
- - [ ] `fileDiff(path, "file.txt")` → returns diff for specific file
198
- - [ ] `getCreatePrUrl(path, "feature")` → returns GitHub PR URL from remote
199
-
200
- **Git API Integration:**
201
- - [ ] `GET /api/git/status/myproject` → status JSON (project resolved by name)
202
- - [ ] `POST /api/git/commit` with `{project: "myproject", message: "test"}` → creates commit
203
- - [ ] `GET /api/git/graph/myproject?max=50` → returns graph data with ≤50 commits
204
- - [ ] All git routes resolve project by NAME, not path
205
-
206
- ### Phase 7 Tests (AI Chat)
207
-
208
- **Provider Registry:**
209
- - [ ] `register(provider)` adds provider
210
- - [ ] `get("claude")` returns Claude provider
211
- - [ ] `get("nonexistent")` returns undefined
212
- - [ ] `list()` returns all provider infos
213
- - [ ] `getDefault()` returns first registered provider
214
-
215
- **Chat Service (mock provider):**
216
- - [ ] `createSession("mock", config)` → returns session with id
217
- - [ ] `sendMessage("mock", sessionId, "hello")` → yields ChatEvent stream
218
- - [ ] `listSessions()` → returns session list
219
- - [ ] `deleteSession("mock", sessionId)` → session removed from list
220
-
221
- **Chat WS Integration:**
222
- - [ ] Connect to `/ws/chat/:sessionId` → WS opens
223
- - [ ] Send `{type: "message", content: "hello"}` → receives streamed response events
224
- - [ ] Receive `{type: "approval_request"}` → send `{type: "approval_response", approved: true}` → tool executes
225
- - [ ] Approval denied → AI receives denial message
226
- - [ ] `{type: "done"}` received at end of response
227
- - [ ] Error in provider → `{type: "error", message: "..."}` sent via WS
228
-
229
- **Chat REST API:**
230
- - [ ] `GET /api/chat/sessions` → list of all sessions
231
- - [ ] `GET /api/chat/sessions/:id/messages` → message history for session
232
- - [ ] Reconnect flow: disconnect WS → GET messages via REST → reconnect WS → no messages lost
233
-
234
- ### Phase 8 Tests (CLI)
235
-
236
- **Project Resolver:**
237
- - [ ] `-p myproject` flag → resolves to correct project
238
- - [ ] No flag, CWD inside project → auto-detects
239
- - [ ] No flag, CWD not in any project → throws descriptive error
240
-
241
- **CLI Commands (capture stdout):**
242
- - [ ] `ppm projects list` → outputs table with project names and paths
243
- - [ ] `ppm projects add /tmp/repo --name test` → adds project
244
- - [ ] `ppm git status -p myproject` → outputs colored status
245
- - [ ] `ppm git commit -p myproject -m "test"` → commits and prints hash
246
- - [ ] `ppm chat list -p myproject` → outputs session table
247
-
248
- ### Phase 9 Tests (Build)
249
- - [ ] `bun run build` completes without errors
250
- - [ ] Built binary serves frontend at `http://localhost:<port>/`
251
- - [ ] Built binary serves API at `/api/*`
252
- - [ ] PWA manifest accessible at expected path
253
-
254
- ## Test Utilities
255
-
256
- ```typescript
257
- // tests/setup.ts
258
- import { mkdtempSync } from 'fs';
259
- import { tmpdir } from 'os';
260
-
261
- // Create temp git repo for testing
262
- export function createTestRepo(): string {
263
- const dir = mkdtempSync(path.join(tmpdir(), 'ppm-test-'));
264
- execSync('git init', { cwd: dir });
265
- execSync('git commit --allow-empty -m "init"', { cwd: dir });
266
- return dir;
267
- }
268
-
269
- // Create test config
270
- export function createTestConfig(overrides?: Partial<PpmConfig>): PpmConfig {
271
- return { port: 0, host: '127.0.0.1', auth: { enabled: false }, projects: [], ...overrides };
272
- }
273
-
274
- // Create test Hono app with test config
275
- export function createTestApp(config?: Partial<PpmConfig>): Hono {
276
- // Wire up routes with test config, return app instance
277
- }
278
-
279
- // Wait for WS message matching predicate
280
- export async function waitForWsMessage(ws: WebSocket, predicate: (msg: any) => boolean, timeout = 5000): Promise<any> {
281
- // Returns first message matching predicate, throws on timeout
282
- }
283
- ```
284
-
285
- ## Mock Strategy
286
-
287
- - **Git operations:** Use real temp git repos (no mocking git)
288
- - **AI Provider:** Mock provider implementing AIProvider interface (no real API calls)
289
- - **File system:** Use temp directories (real FS, no mocking)
290
- - **WebSocket:** Use real WS connections to test server
291
- - **Auth:** Test with both `auth.enabled: true` (with token) and `auth.enabled: false`
292
-
293
- ## Coverage Target
294
-
295
- - Services: 80%+
296
- - API routes: 80%+
297
- - CLI commands: 70%+
298
- - Frontend stores/lib/hooks: 80%+
299
- - Frontend components: Manual testing (visual verification)
300
-
301
- ## Success Criteria
302
-
303
- - [ ] `bun test` runs all tests (unit + integration)
304
- - [ ] No tests use fake data/mocks that mask real behavior
305
- - [ ] Git tests use real temp repos with real git operations
306
- - [ ] API tests use real HTTP requests via Hono test client
307
- - [ ] WS tests verify full protocol correctness (connect, send, receive, reconnect)
308
- - [ ] Frontend store tests verify state transitions and edge cases
309
- - [ ] API client tests verify envelope unwrapping and error handling
310
- - [ ] All tests pass before merge — failing tests block PR
311
- - [ ] Test output includes file/line for failures (easy to locate)