@hienlh/ppm 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) 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/.claude/agent-memory/tester/MEMORY.md +0 -3
  34. package/.claude/agent-memory/tester/project-ppm-test-conventions.md +0 -32
  35. package/.github/workflows/release.yml +0 -46
  36. package/plans/260314-2009-ppm-implementation/phase-01-project-skeleton.md +0 -81
  37. package/plans/260314-2009-ppm-implementation/phase-02-backend-core.md +0 -148
  38. package/plans/260314-2009-ppm-implementation/phase-03-frontend-shell.md +0 -256
  39. package/plans/260314-2009-ppm-implementation/phase-04-file-explorer-editor.md +0 -120
  40. package/plans/260314-2009-ppm-implementation/phase-05-web-terminal.md +0 -174
  41. package/plans/260314-2009-ppm-implementation/phase-06-git-integration.md +0 -244
  42. package/plans/260314-2009-ppm-implementation/phase-07-ai-chat.md +0 -242
  43. package/plans/260314-2009-ppm-implementation/phase-08-cli-commands.md +0 -143
  44. package/plans/260314-2009-ppm-implementation/phase-09-pwa-build-deploy.md +0 -209
  45. package/plans/260314-2009-ppm-implementation/phase-10-testing.md +0 -311
  46. package/plans/260314-2009-ppm-implementation/plan.md +0 -202
  47. package/plans/260315-0356-project-scoped-api-refactor/phase-01-backend-project-router.md +0 -145
  48. package/plans/260315-0356-project-scoped-api-refactor/phase-02-frontend-api-migration.md +0 -107
  49. package/plans/260315-0356-project-scoped-api-refactor/phase-03-per-project-tabs.md +0 -100
  50. package/plans/260315-0356-project-scoped-api-refactor/phase-04-websocket-migration.md +0 -66
  51. package/plans/260315-0356-project-scoped-api-refactor/plan.md +0 -87
  52. package/plans/reports/brainstorm-260314-1938-final-techstack.md +0 -342
  53. package/plans/reports/docs-manager-260315-1314-documentation-creation.md +0 -386
  54. package/plans/reports/fullstack-developer-260314-2252-phase-02-backend-core.md +0 -57
  55. package/plans/reports/fullstack-developer-260314-2253-phase-03-frontend-shell.md +0 -70
  56. package/plans/reports/fullstack-developer-260314-2300-phase-04-05-file-api-terminal-ws.md +0 -49
  57. package/plans/reports/fullstack-developer-260314-2300-phase-04-05-file-explorer-editor-terminal.md +0 -52
  58. package/plans/reports/fullstack-developer-260314-2307-ai-chat-phase7.md +0 -58
  59. package/plans/reports/fullstack-developer-260314-2307-phase-06-git-integration.md +0 -33
  60. package/plans/reports/research-260314-1911-ppm-tech-stack.md +0 -318
  61. package/plans/reports/research-260314-1930-claude-code-integration.md +0 -293
  62. package/plans/reports/researcher-260314-2232-node-pty-bun-crash-analysis.md +0 -305
  63. package/plans/reports/researcher-260314-2232-ui-style.md +0 -942
  64. package/plans/reports/researcher-260315-0300-opcode-claude-interaction.md +0 -745
  65. package/plans/reports/researcher-260315-0303-opcode-deep-analysis.md +0 -742
  66. package/plans/reports/researcher-260315-0305-claude-agent-sdk-github-research.md +0 -423
  67. package/plans/reports/tester-260314-2053-initial-test-suite.md +0 -81
  68. package/repomix-output.xml +0 -23745
  69. package/tests/integration/api/chat-routes.test.ts +0 -95
  70. package/tests/integration/claude-agent-sdk-integration.test.ts +0 -228
  71. package/tests/integration/ws/chat-websocket.test.ts +0 -312
  72. package/tests/test-setup.ts +0 -5
  73. package/tests/unit/providers/claude-agent-sdk.test.ts +0 -339
  74. package/tests/unit/providers/mock-provider.test.ts +0 -143
  75. package/tests/unit/services/chat-service.test.ts +0 -100
@@ -1,244 +0,0 @@
1
- # Phase 6: Git Integration
2
-
3
- **Owner:** backend-dev (API) + frontend-dev (UI) — parallel
4
- **Priority:** High
5
- **Depends on:** Phase 4 (file explorer, diff viewer reuse)
6
- **Effort:** Large (git graph is complex)
7
-
8
- ## Overview
9
-
10
- Git status panel (stage/unstage/commit/push/pull), git diff viewer, git graph visualization (SVG, ported from vscode-git-graph approach).
11
-
12
- ## Backend (backend-dev)
13
-
14
- ### Files
15
- ```
16
- src/services/git.service.ts
17
- src/server/routes/git.ts
18
- ```
19
-
20
- ### Git Service
21
- ```typescript
22
- import simpleGit from 'simple-git';
23
-
24
- class GitService {
25
- // Status
26
- async status(projectPath: string): Promise<GitStatus>
27
- async diff(projectPath: string, ref1?: string, ref2?: string): Promise<string> // unified diff
28
- async fileDiff(projectPath: string, filePath: string): Promise<string>
29
-
30
- // Staging
31
- async stage(projectPath: string, files: string[]): Promise<void>
32
- async unstage(projectPath: string, files: string[]): Promise<void>
33
-
34
- // Commit + Push/Pull
35
- async commit(projectPath: string, message: string): Promise<string> // returns hash
36
- async push(projectPath: string, remote?: string, branch?: string): Promise<void>
37
- async pull(projectPath: string, remote?: string, branch?: string): Promise<void>
38
-
39
- // Branch ops
40
- async branches(projectPath: string): Promise<GitBranch[]>
41
- async createBranch(projectPath: string, name: string, from?: string): Promise<void>
42
- async checkout(projectPath: string, ref: string): Promise<void>
43
- async deleteBranch(projectPath: string, name: string, force?: boolean): Promise<void>
44
- async merge(projectPath: string, source: string): Promise<void>
45
-
46
- // Graph data
47
- async graphData(projectPath: string, maxCount?: number): Promise<GitGraphData>
48
-
49
- // Advanced (web-only, no CLI needed)
50
- async cherryPick(projectPath: string, hash: string): Promise<void>
51
- async revert(projectPath: string, hash: string): Promise<void>
52
- async createTag(projectPath: string, name: string, hash?: string): Promise<void>
53
-
54
- // PR URL
55
- getCreatePrUrl(projectPath: string, branch: string): string | null
56
- // Parse remote URL → GitHub/GitLab PR creation URL
57
- }
58
- ```
59
-
60
- ### Graph Data Extraction
61
-
62
- **[V2 FIX]** Do NOT parse `git log --format` manually with newline separators. Use simple-git's built-in `.log()` which returns correctly typed `LogResult`:
63
-
64
- ```typescript
65
- async graphData(projectPath: string, maxCount = 200): Promise<GitGraphData> {
66
- const git = simpleGit(projectPath);
67
-
68
- // Use simple-git's built-in log() — handles parsing correctly
69
- const log = await git.log({
70
- '--all': null,
71
- maxCount,
72
- });
73
-
74
- // log.all is already typed: { hash, date, message, author_name, author_email, refs, body, diff? }[]
75
- const commits: GitCommit[] = log.all.map(c => ({
76
- hash: c.hash,
77
- abbreviatedHash: c.hash.slice(0, 7),
78
- subject: c.message,
79
- body: c.body,
80
- authorName: c.author_name,
81
- authorEmail: c.author_email,
82
- authorDate: c.date,
83
- parents: [], // Need separate call or parse refs
84
- refs: c.refs ? c.refs.split(', ').filter(Boolean) : [],
85
- }));
86
-
87
- // Get parent hashes via raw format (safe: one field per line)
88
- const parentLog = await git.raw([
89
- 'log', '--all', `--max-count=${maxCount}`,
90
- '--format=%H %P' // hash + space-separated parents on ONE line
91
- ]);
92
- const parentMap = new Map<string, string[]>();
93
- for (const line of parentLog.trim().split('\n')) {
94
- const [hash, ...parents] = line.split(' ');
95
- if (hash) parentMap.set(hash, parents.filter(Boolean));
96
- }
97
- for (const c of commits) {
98
- c.parents = parentMap.get(c.hash) ?? [];
99
- }
100
-
101
- const branchSummary = await git.branch(['-a', '--no-color']);
102
- const branches: GitBranch[] = Object.entries(branchSummary.branches).map(([name, info]) => ({
103
- name,
104
- current: info.current,
105
- remote: name.startsWith('remotes/'),
106
- commitHash: info.commit,
107
- ahead: 0,
108
- behind: 0,
109
- }));
110
-
111
- return { commits, branches };
112
- }
113
- ```
114
-
115
- ### Lane Allocation Algorithm
116
- Port from vscode-git-graph `web/graph.ts`:
117
- - Each branch gets a lane (column index)
118
- - Merge/fork lines connect lanes
119
- - Color = `laneIndex % colorPalette.length`
120
- - Return: `Map<commitHash, { lane: number, lines: Line[] }>`
121
-
122
- ### API Routes
123
- ```
124
- GET /api/git/status/:project
125
- GET /api/git/diff/:project?ref1=&ref2=
126
- GET /api/git/file-diff/:project?file=&ref=
127
- GET /api/git/graph/:project?max=500
128
- GET /api/git/branches/:project
129
- POST /api/git/stage { project, files }
130
- POST /api/git/unstage { project, files }
131
- POST /api/git/commit { project, message }
132
- POST /api/git/push { project, remote?, branch? }
133
- POST /api/git/pull { project, remote?, branch? }
134
- POST /api/git/branch/create { project, name, from? }
135
- POST /api/git/checkout { project, ref }
136
- POST /api/git/branch/delete { project, name, force? }
137
- POST /api/git/merge { project, source }
138
- POST /api/git/cherry-pick { project, hash }
139
- POST /api/git/revert { project, hash }
140
- POST /api/git/tag { project, name, hash? }
141
- GET /api/git/pr-url/:project?branch= → { url }
142
- ```
143
-
144
- ## Frontend (frontend-dev)
145
-
146
- ### Files
147
- ```
148
- src/web/components/git/git-graph.tsx
149
- src/web/components/git/git-graph-renderer.tsx
150
- src/web/components/git/git-status-panel.tsx
151
- src/web/components/git/git-diff-tab.tsx
152
- src/web/components/git/commit-context-menu.tsx
153
- src/web/lib/git-graph-layout.ts
154
- ```
155
-
156
- ### Git Status Panel
157
- - Split into: Changes (unstaged) + Staged Changes
158
- - Each file: icon (M/A/D/R), filename, click → open diff
159
- - Buttons: Stage All, Unstage All
160
- - Individual file: click +/- to stage/unstage
161
- - Commit section: textarea input + "Commit" button
162
- - Push/Pull buttons with branch name display
163
- - Auto-refresh via WS `/ws/events` or polling
164
-
165
- ### Git Graph (SVG)
166
- ```typescript
167
- // git-graph-renderer.tsx
168
- // Receives: commits + lanes from backend API
169
-
170
- const GitGraphRenderer = ({ data }: { data: GitGraphData }) => {
171
- // SVG element with:
172
- // 1. Branch lines: <path> elements, curved or angular
173
- // 2. Commit nodes: <circle> elements
174
- // 3. Labels: branch names, tags as badges
175
- // 4. Click handlers on commits → expand details
176
- // 5. Context menu on commits and branches
177
-
178
- // Virtualization: only render visible rows
179
- // Each commit row height = GRID_Y (e.g., 24px)
180
- // Scroll container with virtual list
181
- };
182
- ```
183
-
184
- ### Git Graph Context Menu (shadcn/ui ContextMenu)
185
- **On commit node:**
186
- - Checkout this commit
187
- - Create branch here...
188
- - Cherry pick
189
- - Revert
190
- - Create tag...
191
- - Copy commit hash
192
- - View diff
193
-
194
- **On branch label:**
195
- - Checkout
196
- - Merge into current branch
197
- - Delete branch
198
- - Rename branch
199
- - Push
200
- - Pull
201
- - Rebase onto current
202
- - Create Pull Request → opens browser URL
203
-
204
- ### Git Diff Tab
205
- - Reuse `@codemirror/merge` diff viewer from Phase 4
206
- - Opened from: git status (click file), git graph (view diff), file explorer (compare)
207
- - Header: file path, ref1 vs ref2
208
-
209
- ## Success Criteria
210
-
211
- **Git Status Panel:**
212
- - [ ] Shows two sections: "Changes" (unstaged) and "Staged Changes" with file counts
213
- - [ ] Each file shows status icon (M=modified, A=added, D=deleted, R=renamed) + filename
214
- - [ ] Click file in either section → opens diff view in new tab
215
- - [ ] Click "+" on unstaged file → stages it (moves to Staged section)
216
- - [ ] Click "−" on staged file → unstages it (moves to Changes section)
217
- - [ ] "Stage All" button stages all changed files
218
- - [ ] "Unstage All" button unstages all staged files
219
- - [ ] Commit section: textarea for message + "Commit" button
220
- - [ ] Commit with empty message → button disabled / shows warning
221
- - [ ] Commit with nothing staged → shows "Nothing to commit" message
222
- - [ ] After successful commit → status panel refreshes, staged files clear
223
- - [ ] Push/Pull buttons show current branch name, disabled when no remote
224
-
225
- **Git Graph:**
226
- - [ ] Renders commit history as SVG with colored branch lanes
227
- - [ ] Each commit: circle node + abbreviated hash + subject + author + relative date
228
- - [ ] Branch labels rendered as colored badges on corresponding commits
229
- - [ ] Merge commits show lines connecting from parent lanes
230
- - [ ] Scroll through 200+ commits without performance issues (virtualized rendering)
231
- - [ ] Right-click commit → context menu: Checkout, Create branch, Cherry pick, Revert, Create tag, Copy hash, View diff
232
- - [ ] Right-click branch label → context menu: Checkout, Merge into current, Delete, Push, Create PR
233
- - [ ] "Create PR" → opens correct GitHub/GitLab URL (parsed from remote) in new browser tab
234
-
235
- **Git Diff:**
236
- - [ ] Diff tab shows file path + refs in header
237
- - [ ] Side-by-side diff using `@codemirror/merge` with syntax highlighting
238
- - [ ] Added lines highlighted green, removed lines highlighted red
239
- - [ ] Opened from: git status (click file), git graph (view diff), file explorer (compare)
240
-
241
- **Mobile:**
242
- - [ ] Git graph scrollable horizontally and vertically with touch
243
- - [ ] Context menu appears on long-press (commit node or branch label)
244
- - [ ] Status panel: swipe file row to stage/unstage (nice-to-have, buttons work as fallback)
@@ -1,242 +0,0 @@
1
- # Phase 7: AI Chat
2
-
3
- **Owner:** backend-dev (provider + WS) + frontend-dev (chat UI) — parallel
4
- **Priority:** High
5
- **Depends on:** Phase 2, Phase 3
6
- **Effort:** Large
7
-
8
- ## Overview
9
-
10
- AI chat with Claude Agent SDK as first provider. Generic AIProvider interface for multi-provider support. Chat UI with streaming, tool approvals, session management.
11
-
12
- ## Backend (backend-dev)
13
-
14
- ### Files
15
- ```
16
- src/providers/provider.interface.ts # Already in types, implement here
17
- src/providers/claude-agent-sdk.ts
18
- src/providers/cli-subprocess.ts # Stub for future
19
- src/providers/registry.ts
20
- src/services/chat.service.ts
21
- src/server/ws/chat.ts
22
- ```
23
-
24
- ### Claude Agent SDK Provider
25
- ```typescript
26
- import { query, listSessions, getSessionMessages } from '@anthropic-ai/claude-agent-sdk';
27
-
28
- class ClaudeAgentSdkProvider implements AIProvider {
29
- id = 'claude';
30
- name = 'Claude Code';
31
-
32
- async createSession(config: SessionConfig): Promise<Session> {
33
- // Start a new query() — capture session_id from init message
34
- // Return session handle
35
- }
36
-
37
- async resumeSession(sessionId: string): Promise<Session> {
38
- // Use { resume: sessionId } option
39
- }
40
-
41
- async *sendMessage(sessionId: string, message: string): AsyncIterable<ChatEvent> {
42
- const options = sessionId
43
- ? { resume: sessionId, allowedTools: [...] }
44
- : { allowedTools: [...] };
45
-
46
- for await (const msg of query({ prompt: message, options })) {
47
- // Map SDK messages → ChatEvent types
48
- if (msg.type === 'assistant') {
49
- for (const block of msg.content) {
50
- if (block.type === 'text') yield { type: 'text', content: block.text };
51
- if (block.type === 'tool_use') yield { type: 'tool_use', tool: block.name, input: block.input };
52
- }
53
- }
54
- if (msg.type === 'result') {
55
- yield { type: 'done', sessionId: msg.session_id };
56
- }
57
- }
58
- }
59
-
60
- async listSessions(): Promise<SessionInfo[]> {
61
- return await listSessions();
62
- }
63
-
64
- async deleteSession(sessionId: string): Promise<void> {
65
- // Delete session file from ~/.claude/projects/...
66
- }
67
- }
68
- ```
69
-
70
- ### Tool Approval Flow
71
- ```typescript
72
- // In claude-agent-sdk.ts
73
- // canUseTool callback → forward to frontend via WS
74
-
75
- const options = {
76
- canUseTool: async (toolName: string, input: any) => {
77
- // Send approval request to frontend via WS
78
- const response = await this.requestApproval(sessionId, toolName, input);
79
- if (response.approved) {
80
- return { behavior: 'allow', updatedInput: input };
81
- }
82
- return { behavior: 'deny', message: response.reason || 'User denied' };
83
- }
84
- };
85
- ```
86
-
87
- ### Provider Registry
88
- ```typescript
89
- class ProviderRegistry {
90
- private providers: Map<string, AIProvider> = new Map();
91
-
92
- register(provider: AIProvider): void
93
- get(id: string): AIProvider | undefined
94
- list(): AIProviderInfo[]
95
- getDefault(): AIProvider
96
- }
97
- ```
98
-
99
- ### Chat Service
100
- ```typescript
101
- class ChatService {
102
- constructor(private registry: ProviderRegistry)
103
-
104
- async createSession(providerId: string, config: SessionConfig): Promise<Session>
105
- async resumeSession(providerId: string, sessionId: string): Promise<Session>
106
- async listSessions(providerId?: string): Promise<SessionInfo[]>
107
- async deleteSession(providerId: string, sessionId: string): Promise<void>
108
- sendMessage(providerId: string, sessionId: string, message: string): AsyncIterable<ChatEvent>
109
- }
110
- ```
111
-
112
- ### WebSocket Handler
113
- ```
114
- WS /ws/chat/:sessionId
115
- ```
116
-
117
- Protocol (JSON messages):
118
- ```typescript
119
- // Client → Server
120
- { type: 'message', content: string }
121
- { type: 'approval_response', requestId: string, approved: boolean, reason?: string }
122
-
123
- // Server → Client
124
- { type: 'text', content: string }
125
- { type: 'tool_use', tool: string, input: any }
126
- { type: 'tool_result', output: string }
127
- { type: 'approval_request', requestId: string, tool: string, input: any }
128
- { type: 'done', sessionId: string }
129
- { type: 'error', message: string }
130
- ```
131
-
132
- ## Frontend (frontend-dev)
133
-
134
- ### Files
135
- ```
136
- src/web/components/chat/chat-tab.tsx
137
- src/web/components/chat/message-list.tsx
138
- src/web/components/chat/message-input.tsx
139
- src/web/components/chat/tool-approval.tsx
140
- src/web/components/chat/session-picker.tsx
141
- src/web/hooks/use-chat.ts
142
- ```
143
-
144
- ### Chat Tab Layout
145
- ```
146
- ┌─────────────────────────────────┐
147
- │ Claude Code ▼ [Session: abc] │ ← provider picker + session info
148
- ├─────────────────────────────────┤
149
- │ │
150
- │ User: Fix the bug in auth.ts │
151
- │ │
152
- │ Claude: I'll read the file... │
153
- │ 📄 Read auth.ts │
154
- │ ✅ Tool result: (content) │
155
- │ │
156
- │ ⚠️ Bash: rm -rf /tmp/test │
157
- │ [Allow] [Deny] │ ← tool approval dialog
158
- │ │
159
- ├─────────────────────────────────┤
160
- │ 📎 Type a message... [Send] │ ← input with file attach
161
- └─────────────────────────────────┘
162
- ```
163
-
164
- ### Message Types Rendering
165
- - **User message:** Right-aligned bubble (or left with avatar)
166
- - **Assistant text:** Markdown rendered (code blocks with syntax highlight)
167
- - **Tool use:** Collapsible card showing tool name + input
168
- - **Tool result:** Collapsible card showing output (truncated)
169
- - **Approval request:** Highlighted card with Allow/Deny buttons + tool details
170
- - **Error:** Red alert banner
171
-
172
- ### Session Picker
173
- - Dropdown or modal listing all sessions
174
- - Each session: provider icon, title (first message truncated), timestamp
175
- - Actions: Resume, Delete, Fork (nice-to-have)
176
- - "New Chat" button → create fresh session
177
-
178
- ### useChat Hook
179
- ```typescript
180
- const useChat = (sessionId?: string) => {
181
- const ws = useWebSocket(`/ws/chat/${sessionId}`);
182
- const [messages, setMessages] = useState<ChatMessage[]>([]);
183
- const [isStreaming, setIsStreaming] = useState(false);
184
- const [pendingApproval, setPendingApproval] = useState<ApprovalRequest | null>(null);
185
-
186
- const sendMessage = (content: string) => {
187
- ws.send(JSON.stringify({ type: 'message', content }));
188
- };
189
-
190
- const respondToApproval = (requestId: string, approved: boolean) => {
191
- ws.send(JSON.stringify({ type: 'approval_response', requestId, approved }));
192
- };
193
-
194
- return { messages, isStreaming, pendingApproval, sendMessage, respondToApproval };
195
- };
196
- ```
197
-
198
- ### Mobile Considerations
199
- - Chat input: sticky bottom, auto-resize textarea
200
- - Long messages: scrollable code blocks
201
- - Tool approval: full-width card, large touch targets for Allow/Deny
202
- - Keyboard: input should push content up, not cover it
203
-
204
- ## Chat History & Reconnect
205
-
206
- ### REST API for Message History
207
- - `GET /api/chat/sessions` → list all sessions (id, provider, title, createdAt)
208
- - `GET /api/chat/sessions/:id/messages` → full message history for a session
209
- - On WS reconnect: client loads history via REST, then subscribes to live stream
210
- - This avoids WS replay complexity — REST is simpler and more reliable
211
-
212
- ### Reconnect Flow
213
- ```
214
- Client disconnects
215
- → WS closes
216
- → Client: exponential backoff reconnect
217
- → Client reconnects to same sessionId
218
- → Client: GET /api/chat/sessions/:id/messages → render history
219
- → Server: resume streaming from where it left off (if AI still generating)
220
- ```
221
-
222
- ### Session Persistence
223
- - Claude Agent SDK sessions persist automatically (stored in ~/.claude/)
224
- - PPM stores session metadata in `~/.ppm/chat-sessions.json`: id, provider, title, projectName, createdAt
225
- - Session list fetched from PPM metadata file (fast), not from SDK (slow)
226
-
227
- ## Success Criteria
228
-
229
- - [ ] Can create new chat session → WS connects, session appears in session list
230
- - [ ] Sending message streams AI response text in real-time (character by character)
231
- - [ ] Tool use blocks render with tool name + collapsible input JSON
232
- - [ ] Tool result blocks render with collapsible output
233
- - [ ] Tool approval dialog: shows tool name + input, Allow/Deny buttons work, response sent via WS
234
- - [ ] After Allow → tool executes and result streams back; after Deny → AI acknowledges denial
235
- - [ ] Can resume existing session: select from picker → loads history via REST → WS reconnects
236
- - [ ] Session list shows all sessions with provider icon, truncated title, timestamp
237
- - [ ] "New Chat" button creates fresh session with current project context
238
- - [ ] Multiple chat tabs work simultaneously (each with own WS connection)
239
- - [ ] WS disconnect → reconnect → message history loaded from REST API
240
- - [ ] Works on mobile: sticky bottom input, keyboard pushes content up, large Allow/Deny buttons
241
- - [ ] Markdown rendering in AI messages: code blocks with syntax highlighting, lists, headers
242
- - [ ] Error messages from AI/server shown as red alert banner (not silent failure)
@@ -1,143 +0,0 @@
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