@hienlh/ppm 0.1.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/.claude/agent-memory/tester/MEMORY.md +3 -0
- package/.claude/agent-memory/tester/project-ppm-test-conventions.md +32 -0
- package/.env.example +1 -0
- package/.github/workflows/release.yml +46 -0
- package/README.md +349 -0
- package/bun.lock +1217 -0
- package/components.json +21 -0
- package/docs/code-standards.md +574 -0
- package/docs/codebase-summary.md +294 -0
- package/docs/deployment-guide.md +631 -0
- package/docs/design-guidelines.md +661 -0
- package/docs/project-overview-pdr.md +142 -0
- package/docs/project-roadmap.md +400 -0
- package/docs/system-architecture.md +459 -0
- package/package.json +68 -0
- package/plans/260314-2009-ppm-implementation/phase-01-project-skeleton.md +81 -0
- package/plans/260314-2009-ppm-implementation/phase-02-backend-core.md +148 -0
- package/plans/260314-2009-ppm-implementation/phase-03-frontend-shell.md +256 -0
- package/plans/260314-2009-ppm-implementation/phase-04-file-explorer-editor.md +120 -0
- package/plans/260314-2009-ppm-implementation/phase-05-web-terminal.md +174 -0
- package/plans/260314-2009-ppm-implementation/phase-06-git-integration.md +244 -0
- package/plans/260314-2009-ppm-implementation/phase-07-ai-chat.md +242 -0
- package/plans/260314-2009-ppm-implementation/phase-08-cli-commands.md +143 -0
- package/plans/260314-2009-ppm-implementation/phase-09-pwa-build-deploy.md +209 -0
- package/plans/260314-2009-ppm-implementation/phase-10-testing.md +311 -0
- package/plans/260314-2009-ppm-implementation/plan.md +202 -0
- package/plans/260315-0356-project-scoped-api-refactor/phase-01-backend-project-router.md +145 -0
- package/plans/260315-0356-project-scoped-api-refactor/phase-02-frontend-api-migration.md +107 -0
- package/plans/260315-0356-project-scoped-api-refactor/phase-03-per-project-tabs.md +100 -0
- package/plans/260315-0356-project-scoped-api-refactor/phase-04-websocket-migration.md +66 -0
- package/plans/260315-0356-project-scoped-api-refactor/plan.md +87 -0
- package/plans/reports/brainstorm-260314-1938-final-techstack.md +342 -0
- package/plans/reports/docs-manager-260315-1314-documentation-creation.md +386 -0
- package/plans/reports/fullstack-developer-260314-2252-phase-02-backend-core.md +57 -0
- package/plans/reports/fullstack-developer-260314-2253-phase-03-frontend-shell.md +70 -0
- package/plans/reports/fullstack-developer-260314-2300-phase-04-05-file-api-terminal-ws.md +49 -0
- package/plans/reports/fullstack-developer-260314-2300-phase-04-05-file-explorer-editor-terminal.md +52 -0
- package/plans/reports/fullstack-developer-260314-2307-ai-chat-phase7.md +58 -0
- package/plans/reports/fullstack-developer-260314-2307-phase-06-git-integration.md +33 -0
- package/plans/reports/research-260314-1911-ppm-tech-stack.md +318 -0
- package/plans/reports/research-260314-1930-claude-code-integration.md +293 -0
- package/plans/reports/researcher-260314-2232-node-pty-bun-crash-analysis.md +305 -0
- package/plans/reports/researcher-260314-2232-ui-style.md +942 -0
- package/plans/reports/researcher-260315-0300-opcode-claude-interaction.md +745 -0
- package/plans/reports/researcher-260315-0303-opcode-deep-analysis.md +742 -0
- package/plans/reports/researcher-260315-0305-claude-agent-sdk-github-research.md +423 -0
- package/plans/reports/tester-260314-2053-initial-test-suite.md +81 -0
- package/ppm.example.yaml +14 -0
- package/repomix-output.xml +23745 -0
- package/scripts/build.ts +13 -0
- package/src/cli/commands/chat-cmd.ts +259 -0
- package/src/cli/commands/config-cmd.ts +121 -0
- package/src/cli/commands/git-cmd.ts +315 -0
- package/src/cli/commands/init.ts +57 -0
- package/src/cli/commands/open.ts +19 -0
- package/src/cli/commands/projects.ts +100 -0
- package/src/cli/commands/start.ts +3 -0
- package/src/cli/commands/stop.ts +33 -0
- package/src/cli/utils/project-resolver.ts +27 -0
- package/src/index.ts +59 -0
- package/src/providers/claude-agent-sdk.ts +499 -0
- package/src/providers/claude-binary-finder.ts +256 -0
- package/src/providers/claude-code-cli.ts +413 -0
- package/src/providers/claude-process-registry.ts +106 -0
- package/src/providers/mock-provider.ts +171 -0
- package/src/providers/provider.interface.ts +10 -0
- package/src/providers/registry.ts +45 -0
- package/src/server/helpers/resolve-project.ts +22 -0
- package/src/server/index.ts +181 -0
- package/src/server/middleware/auth.ts +30 -0
- package/src/server/routes/chat.ts +153 -0
- package/src/server/routes/files.ts +168 -0
- package/src/server/routes/git.ts +261 -0
- package/src/server/routes/project-scoped.ts +27 -0
- package/src/server/routes/projects.ts +57 -0
- package/src/server/routes/static.ts +26 -0
- package/src/server/ws/chat.ts +130 -0
- package/src/server/ws/terminal.ts +89 -0
- package/src/services/chat.service.ts +110 -0
- package/src/services/claude-usage.service.ts +113 -0
- package/src/services/config.service.ts +90 -0
- package/src/services/file.service.ts +261 -0
- package/src/services/git-dirs.service.ts +112 -0
- package/src/services/git.service.ts +372 -0
- package/src/services/project.service.ts +107 -0
- package/src/services/slash-items.service.ts +184 -0
- package/src/services/terminal.service.ts +212 -0
- package/src/types/api.ts +37 -0
- package/src/types/chat.ts +92 -0
- package/src/types/config.ts +41 -0
- package/src/types/git.ts +50 -0
- package/src/types/project.ts +18 -0
- package/src/types/terminal.ts +20 -0
- package/src/web/app.tsx +168 -0
- package/src/web/components/auth/login-screen.tsx +88 -0
- package/src/web/components/chat/attachment-chips.tsx +55 -0
- package/src/web/components/chat/chat-placeholder.tsx +10 -0
- package/src/web/components/chat/chat-tab.tsx +301 -0
- package/src/web/components/chat/file-picker.tsx +126 -0
- package/src/web/components/chat/message-input.tsx +420 -0
- package/src/web/components/chat/message-list.tsx +838 -0
- package/src/web/components/chat/session-picker.tsx +139 -0
- package/src/web/components/chat/slash-command-picker.tsx +135 -0
- package/src/web/components/chat/usage-badge.tsx +186 -0
- package/src/web/components/editor/code-editor.tsx +329 -0
- package/src/web/components/editor/diff-viewer.tsx +276 -0
- package/src/web/components/editor/editor-placeholder.tsx +10 -0
- package/src/web/components/explorer/file-actions.tsx +191 -0
- package/src/web/components/explorer/file-tree.tsx +298 -0
- package/src/web/components/git/git-graph.tsx +727 -0
- package/src/web/components/git/git-placeholder.tsx +55 -0
- package/src/web/components/git/git-status-panel.tsx +850 -0
- package/src/web/components/layout/mobile-drawer.tsx +137 -0
- package/src/web/components/layout/mobile-nav.tsx +103 -0
- package/src/web/components/layout/sidebar.tsx +90 -0
- package/src/web/components/layout/tab-bar.tsx +152 -0
- package/src/web/components/layout/tab-content.tsx +85 -0
- package/src/web/components/projects/dir-suggest.tsx +152 -0
- package/src/web/components/projects/project-list.tsx +187 -0
- package/src/web/components/settings/settings-tab.tsx +57 -0
- package/src/web/components/terminal/terminal-placeholder.tsx +10 -0
- package/src/web/components/terminal/terminal-tab.tsx +133 -0
- package/src/web/components/ui/button.tsx +64 -0
- package/src/web/components/ui/context-menu.tsx +250 -0
- package/src/web/components/ui/dialog.tsx +156 -0
- package/src/web/components/ui/dropdown-menu.tsx +257 -0
- package/src/web/components/ui/input.tsx +21 -0
- package/src/web/components/ui/scroll-area.tsx +56 -0
- package/src/web/components/ui/separator.tsx +26 -0
- package/src/web/components/ui/sonner.tsx +40 -0
- package/src/web/components/ui/tabs.tsx +91 -0
- package/src/web/components/ui/tooltip.tsx +57 -0
- package/src/web/hooks/use-chat.ts +420 -0
- package/src/web/hooks/use-terminal.ts +182 -0
- package/src/web/hooks/use-url-sync.ts +66 -0
- package/src/web/hooks/use-websocket.ts +48 -0
- package/src/web/index.html +16 -0
- package/src/web/lib/api-client.ts +90 -0
- package/src/web/lib/file-support.ts +68 -0
- package/src/web/lib/utils.ts +6 -0
- package/src/web/lib/ws-client.ts +100 -0
- package/src/web/main.tsx +10 -0
- package/src/web/public/icon-192.svg +5 -0
- package/src/web/public/icon-512.svg +5 -0
- package/src/web/stores/file-store.ts +81 -0
- package/src/web/stores/project-store.ts +50 -0
- package/src/web/stores/settings-store.ts +65 -0
- package/src/web/stores/tab-store.ts +187 -0
- package/src/web/styles/globals.css +227 -0
- package/src/web/vite-env.d.ts +1 -0
- package/tests/integration/api/chat-routes.test.ts +95 -0
- package/tests/integration/claude-agent-sdk-integration.test.ts +228 -0
- package/tests/integration/ws/chat-websocket.test.ts +312 -0
- package/tests/test-setup.ts +5 -0
- package/tests/unit/providers/claude-agent-sdk.test.ts +339 -0
- package/tests/unit/providers/mock-provider.test.ts +143 -0
- package/tests/unit/services/chat-service.test.ts +100 -0
- package/tsconfig.json +32 -0
- package/vite.config.ts +62 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Phase 8: CLI Commands
|
|
2
|
+
|
|
3
|
+
**Owner:** backend-dev
|
|
4
|
+
**Priority:** Medium
|
|
5
|
+
**Depends on:** Phase 2, Phase 6, Phase 7
|
|
6
|
+
**Effort:** Medium
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Implement remaining CLI commands that call Service Layer directly. All commands share project resolution logic (CWD auto-detect + `-p` flag).
|
|
11
|
+
|
|
12
|
+
## Files
|
|
13
|
+
```
|
|
14
|
+
src/cli/commands/projects.ts
|
|
15
|
+
src/cli/commands/config.ts
|
|
16
|
+
src/cli/commands/git.ts
|
|
17
|
+
src/cli/commands/chat.ts
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Commands
|
|
21
|
+
|
|
22
|
+
### ppm projects
|
|
23
|
+
```bash
|
|
24
|
+
ppm projects list # Table: name, path, branch, status
|
|
25
|
+
ppm projects add <path> [--name <n>] # Add project to config
|
|
26
|
+
ppm projects remove <name-or-path> # Remove from config
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### ppm config
|
|
30
|
+
```bash
|
|
31
|
+
ppm config get <key> # e.g., ppm config get port
|
|
32
|
+
ppm config set <key> <value> # e.g., ppm config set port 9090
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### ppm git
|
|
36
|
+
All git commands accept `-p <project>` flag. Default: CWD auto-detect.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
ppm git status [-p proj] # Show status (like git status --short)
|
|
40
|
+
ppm git log [-p proj] [-n 20] # Show recent commits
|
|
41
|
+
ppm git diff [-p proj] [ref1] [ref2] # Show diff
|
|
42
|
+
ppm git stage [-p proj] <files...> # Stage files (or "." for all)
|
|
43
|
+
ppm git unstage [-p proj] <files...> # Unstage files
|
|
44
|
+
ppm git commit [-p proj] -m "msg" # Commit staged changes
|
|
45
|
+
ppm git push [-p proj] # Push to remote
|
|
46
|
+
ppm git pull [-p proj] # Pull from remote
|
|
47
|
+
ppm git branch create [-p proj] <name> [--from <ref>]
|
|
48
|
+
ppm git branch checkout [-p proj] <name>
|
|
49
|
+
ppm git branch delete [-p proj] <name> [--force]
|
|
50
|
+
ppm git branch merge [-p proj] <source>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### ppm chat
|
|
54
|
+
```bash
|
|
55
|
+
ppm chat list [-p proj] # List sessions (table: id, provider, title, date)
|
|
56
|
+
ppm chat create [-p proj] [--provider claude] # Create session, print session ID
|
|
57
|
+
ppm chat send [-p proj] <session-id> "message" # Send message, stream response to stdout
|
|
58
|
+
ppm chat resume [-p proj] <session-id> # Interactive mode (stdin/stdout)
|
|
59
|
+
ppm chat delete [-p proj] <session-id> # Delete session
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`ppm chat send` streams response to stdout as it arrives. Useful for AI-to-AI orchestration:
|
|
63
|
+
```bash
|
|
64
|
+
# AI agent sends a task to PPM chat
|
|
65
|
+
RESPONSE=$(ppm chat send -p myapp abc123 "Fix the bug in auth.ts")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`ppm chat resume` enters interactive mode:
|
|
69
|
+
```
|
|
70
|
+
You: Fix the auth bug
|
|
71
|
+
Claude: I'll read the file...
|
|
72
|
+
[Tool: Read auth.ts] Allow? (y/n): y
|
|
73
|
+
Claude: Found the issue...
|
|
74
|
+
You:
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Implementation Pattern
|
|
78
|
+
|
|
79
|
+
All commands follow same pattern:
|
|
80
|
+
```typescript
|
|
81
|
+
// commands/git.ts
|
|
82
|
+
import { Command } from 'commander';
|
|
83
|
+
|
|
84
|
+
export function registerGitCommands(program: Command) {
|
|
85
|
+
const git = program.command('git').description('Git operations');
|
|
86
|
+
|
|
87
|
+
git.command('status')
|
|
88
|
+
.option('-p, --project <name>', 'Project name')
|
|
89
|
+
.action(async (options) => {
|
|
90
|
+
const project = resolveProject(options);
|
|
91
|
+
const gitService = new GitService();
|
|
92
|
+
const status = await gitService.status(project.path);
|
|
93
|
+
// Pretty print to terminal
|
|
94
|
+
printGitStatus(status);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// ... more subcommands
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Output Formatting
|
|
102
|
+
|
|
103
|
+
- Use colors (via `chalk` or Bun built-in ANSI) for terminal output
|
|
104
|
+
- Tables for list commands (projects list, chat list)
|
|
105
|
+
- Git status: colored M/A/D indicators like git
|
|
106
|
+
- Streaming output for chat send
|
|
107
|
+
|
|
108
|
+
## Success Criteria
|
|
109
|
+
|
|
110
|
+
**Project Resolution:**
|
|
111
|
+
- [ ] `-p myproject` flag resolves project by name
|
|
112
|
+
- [ ] No `-p` flag + CWD inside registered project → auto-detects
|
|
113
|
+
- [ ] No `-p` flag + CWD not in any project → clear error: "Not in a registered project. Use -p <name>"
|
|
114
|
+
|
|
115
|
+
**ppm projects:**
|
|
116
|
+
- [ ] `ppm projects list` → formatted table with columns: Name, Path, Branch, Status
|
|
117
|
+
- [ ] `ppm projects add /path/to/repo --name myrepo` → adds project, confirms with message
|
|
118
|
+
- [ ] `ppm projects add` with duplicate name → error message
|
|
119
|
+
- [ ] `ppm projects remove myrepo` → removes, confirms with message
|
|
120
|
+
|
|
121
|
+
**ppm config:**
|
|
122
|
+
- [ ] `ppm config get port` → prints current port value
|
|
123
|
+
- [ ] `ppm config set port 9090` → updates config file, confirms
|
|
124
|
+
- [ ] `ppm config get nonexistent` → error message
|
|
125
|
+
|
|
126
|
+
**ppm git:**
|
|
127
|
+
- [ ] `ppm git status` → colored output matching git status --short format (M=yellow, A=green, D=red)
|
|
128
|
+
- [ ] `ppm git log -n 5` → shows last 5 commits with hash, message, author, date
|
|
129
|
+
- [ ] `ppm git stage .` → stages all files, prints count
|
|
130
|
+
- [ ] `ppm git commit -m "test"` → creates commit, prints hash
|
|
131
|
+
- [ ] `ppm git commit` with nothing staged → "Nothing to commit" error
|
|
132
|
+
- [ ] `ppm git push` → pushes to remote, prints result
|
|
133
|
+
- [ ] `ppm git branch create feature-x` → creates branch, confirms
|
|
134
|
+
- [ ] `ppm git branch checkout feature-x` → switches branch, confirms
|
|
135
|
+
- [ ] `ppm git branch delete feature-x` → deletes branch, confirms
|
|
136
|
+
|
|
137
|
+
**ppm chat:**
|
|
138
|
+
- [ ] `ppm chat list` → table with columns: ID, Provider, Title, Date
|
|
139
|
+
- [ ] `ppm chat create` → creates session, prints session ID
|
|
140
|
+
- [ ] `ppm chat send <id> "fix the bug"` → streams response to stdout in real-time
|
|
141
|
+
- [ ] `ppm chat resume <id>` → interactive mode with `You:` / `Claude:` prompts
|
|
142
|
+
- [ ] Tool approval in interactive mode: `[Tool: Bash] Allow? (y/n):` prompt
|
|
143
|
+
- [ ] `ppm chat delete <id>` → deletes session, confirms
|
|
@@ -0,0 +1,209 @@
|
|
|
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
|
|
@@ -0,0 +1,311 @@
|
|
|
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)
|