|
| 1 | +# Plan: Ask Feature 10x |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +Four improvements to the post-deployment `ask` feature: |
| 6 | + |
| 7 | +1. **LLM Synthesis** — replace deterministic `pickAnswer()` with Claude on the server side |
| 8 | +2. **Session Transcript at Deploy** — automatically capture and upload the Claude Code session context when deploying |
| 9 | +3. **Git/JJ Diff at Deploy** — capture actual VCS diff in the CLI and store it with the deployment |
| 10 | +4. **Real AST Parsing** — replace regex symbol extraction with `acorn` + `acorn-typescript` |
| 11 | + |
| 12 | +--- |
| 13 | + |
| 14 | +## Feature 1: LLM Synthesis |
| 15 | + |
| 16 | +Replace the heuristic `pickAnswer()` in `ask-project.ts` with a real Claude API call. |
| 17 | + |
| 18 | +### What changes |
| 19 | + |
| 20 | +**`apps/control-plane/src/types.ts`** |
| 21 | +- Add `ANTHROPIC_API_KEY?: string` to `Bindings` |
| 22 | + |
| 23 | +**`apps/control-plane/wrangler.toml`** |
| 24 | +- Add comment: `# - ANTHROPIC_API_KEY # Anthropic API key for ask_project synthesis` |
| 25 | + |
| 26 | +**`apps/control-plane/package.json`** |
| 27 | +- Add `@anthropic-ai/sdk` dependency |
| 28 | + |
| 29 | +**`apps/control-plane/src/ask-project.ts`** |
| 30 | +- Add `synthesizeWithLLM(evidence, question, opts?)` function: |
| 31 | + - Formats evidence as a structured markdown block for Claude |
| 32 | + - Includes session transcript excerpt (if available) and VCS diff stat (if available) |
| 33 | + - Calls `claude-haiku-4-5-20251001` via Anthropic SDK |
| 34 | + - Returns `{ answer: string, root_cause?: string, suggested_fix?: string, confidence: "high" | "medium" | "low" }` |
| 35 | + - Falls back to `pickAnswer()` if `ANTHROPIC_API_KEY` is not set or the call fails |
| 36 | +- Update `answerProjectQuestion()` to call `synthesizeWithLLM()` instead of `pickAnswer()` |
| 37 | +- Keep `pickAnswer()` as the fallback |
| 38 | +- Extend API response shape to include `root_cause?`, `suggested_fix?`, `confidence` |
| 39 | + |
| 40 | +### Prompt structure for Claude |
| 41 | + |
| 42 | +``` |
| 43 | +You are a debugging assistant for deployed Cloudflare Workers projects. |
| 44 | +Given evidence collected about a project, answer the user's question concisely. |
| 45 | +
|
| 46 | +Question: <question> |
| 47 | +
|
| 48 | +Evidence: |
| 49 | +<formatted evidence list — type, source, summary, relation> |
| 50 | +
|
| 51 | +[If session transcript available]: |
| 52 | +Context from deploy session (last 30 messages): |
| 53 | +<transcript excerpt> |
| 54 | +
|
| 55 | +[If VCS diff available]: |
| 56 | +Code changed in this deployment: |
| 57 | +<diff_stat> |
| 58 | +
|
| 59 | +Respond with JSON: |
| 60 | +{ |
| 61 | + "answer": "...", |
| 62 | + "root_cause": "..." | null, |
| 63 | + "suggested_fix": "..." | null, |
| 64 | + "confidence": "high" | "medium" | "low" |
| 65 | +} |
| 66 | +``` |
| 67 | + |
| 68 | +--- |
| 69 | + |
| 70 | +## Feature 2: Session Transcript at Deploy |
| 71 | + |
| 72 | +Automatically capture the Claude Code session transcript at deploy time and store it as a deployment artifact. The `ask` feature then uses it as context. |
| 73 | + |
| 74 | +### Mechanism |
| 75 | + |
| 76 | +Two paths, both automatic (no agent opt-in): |
| 77 | + |
| 78 | +**Path A — CLI deploy** (`jack deploy` run as a Bash tool by Claude Code): |
| 79 | +- Extend the existing `SessionStart` hook in `installClaudeCodeHooks()` to also export `CLAUDE_TRANSCRIPT_PATH` via `CLAUDE_ENV_FILE` |
| 80 | +- After a successful deploy, `jack deploy` checks for `CLAUDE_TRANSCRIPT_PATH` env var and uploads the transcript |
| 81 | + |
| 82 | +**Path B — MCP deploy** (`deploy_project` MCP tool called by Claude Code): |
| 83 | +- Add a `PostToolUse` hook in `installClaudeCodeHooks()` that fires when `tool_name === "deploy_project"` |
| 84 | +- Hook reads `transcript_path` and `tool_response` from stdin |
| 85 | +- Extracts `deploymentId` and `projectId` from tool response JSON |
| 86 | +- Calls `jack _internal upload-session-transcript --project <id> --deployment <id> --transcript-path <path>` |
| 87 | + |
| 88 | +### What changes |
| 89 | + |
| 90 | +**`apps/cli/src/lib/claude-hooks-installer.ts`** |
| 91 | +- Extend `installClaudeCodeHooks()` to also install: |
| 92 | + - Updated `SessionStart` hook command that exports `CLAUDE_TRANSCRIPT_PATH` and `CLAUDE_SESSION_ID` via `CLAUDE_ENV_FILE` |
| 93 | + - New `PostToolUse` hook entry with `matcher: "deploy_project"` that runs `jack _internal upload-session-transcript ...` |
| 94 | +- Keep existing hook deduplication logic |
| 95 | + |
| 96 | +**`apps/cli/src/lib/session-transcript.ts`** (new file) |
| 97 | +- `readAndTruncateTranscript(path: string): Promise<string | null>`: |
| 98 | + - Reads JSONL file from `transcript_path` |
| 99 | + - Keeps only `type: "user" | "assistant"` lines |
| 100 | + - Truncates to last 200 messages or 100KB, whichever is smaller |
| 101 | + - Returns truncated JSONL string |
| 102 | +- `uploadSessionTranscript(opts: { projectId, deploymentId, transcriptPath, authToken, baseUrl })`: |
| 103 | + - Calls `readAndTruncateTranscript()` |
| 104 | + - PUTs to `/v1/projects/:projectId/deployments/:deploymentId/session-transcript` |
| 105 | + |
| 106 | +**`apps/cli/src/commands/internal.ts`** (new command or extend existing) |
| 107 | +- Add `jack _internal upload-session-transcript` subcommand |
| 108 | +- Reads `--project-id`, `--deployment-id`, `--transcript-path` args |
| 109 | +- Calls `uploadSessionTranscript()` using saved auth token |
| 110 | +- Silent: exits 0 even on failure (hook errors must not block the user) |
| 111 | + |
| 112 | +**`apps/cli/src/lib/managed-deploy.ts`** (or wherever deploy completes) |
| 113 | +- After successful deploy: check `process.env.CLAUDE_TRANSCRIPT_PATH` |
| 114 | +- If set, call `uploadSessionTranscript()` asynchronously (fire-and-forget, silent) |
| 115 | + |
| 116 | +**`apps/control-plane/src/index.ts`** |
| 117 | +- New endpoint: `PUT /v1/projects/:projectId/deployments/:deploymentId/session-transcript` |
| 118 | + - Auth: same JWT org membership check |
| 119 | + - Body: raw text (JSONL) |
| 120 | + - Stores to R2: `projects/{projectId}/deployments/{deploymentId}/session-transcript.jsonl` |
| 121 | + - Updates `deployments SET has_session_transcript = 1 WHERE id = ?` |
| 122 | + |
| 123 | +**`apps/control-plane/migrations/0031_add_deployment_context.sql`** (new) |
| 124 | +```sql |
| 125 | +ALTER TABLE deployments ADD COLUMN has_session_transcript INTEGER DEFAULT 0; |
| 126 | +ALTER TABLE deployments ADD COLUMN vcs_type TEXT; |
| 127 | +ALTER TABLE deployments ADD COLUMN vcs_sha TEXT; |
| 128 | +ALTER TABLE deployments ADD COLUMN vcs_message TEXT; |
| 129 | +ALTER TABLE deployments ADD COLUMN vcs_diff_stat TEXT; |
| 130 | +ALTER TABLE deployments ADD COLUMN vcs_diff TEXT; |
| 131 | +``` |
| 132 | + |
| 133 | +**`apps/control-plane/src/ask-project.ts`** |
| 134 | +- Before calling `synthesizeWithLLM()`: if `deployment.has_session_transcript`, fetch from R2 and pass last 30 messages as context |
| 135 | +- Add `"session_transcript"` evidence type — summary says e.g. "Deploy session had 47 turns. Agent was working on: ..." |
| 136 | + |
| 137 | +--- |
| 138 | + |
| 139 | +## Feature 3: Git/JJ Diff at Deploy |
| 140 | + |
| 141 | +Capture the actual VCS diff in the CLI at deploy time and store it with the deployment. |
| 142 | + |
| 143 | +### What changes |
| 144 | + |
| 145 | +**`apps/cli/src/lib/vcs.ts`** (new file) |
| 146 | +```typescript |
| 147 | +interface VcsDiff { |
| 148 | + vcs: "git" | "jj"; |
| 149 | + sha: string; |
| 150 | + message: string; // commit/change description |
| 151 | + diff_stat: string; // max 2KB |
| 152 | + diff: string; // max 15KB, truncated with notice if over |
| 153 | +} |
| 154 | + |
| 155 | +async function captureVcsDiff(projectDir: string): Promise<VcsDiff | null> |
| 156 | +``` |
| 157 | + |
| 158 | +- Detects VCS by checking for `.git/` (git) or `.jj/` (jj) in `projectDir` and parents |
| 159 | +- Git commands: |
| 160 | + - `git -C <dir> rev-parse HEAD` → sha |
| 161 | + - `git -C <dir> log -1 --pretty=%s` → message |
| 162 | + - `git -C <dir> diff HEAD~1 --stat` → diff_stat (if no parent commit, diff against empty tree) |
| 163 | + - `git -C <dir> diff HEAD~1 --unified=3` → diff (truncated to 15KB) |
| 164 | +- JJ commands: |
| 165 | + - `jj --no-pager log -r @ --no-graph --template 'commit_id.short()'` → sha |
| 166 | + - `jj --no-pager log -r @ --no-graph --template 'description.first_line()'` → message |
| 167 | + - `jj --no-pager diff --stat` → diff_stat |
| 168 | + - `jj --no-pager diff` → diff (truncated to 15KB) |
| 169 | +- Silent fallback: catches all errors, returns `null` if anything fails or VCS not found |
| 170 | + |
| 171 | +**`apps/cli/src/lib/managed-deploy.ts`** (or deploy request builder) |
| 172 | +- Call `captureVcsDiff(projectDir)` before deploy request |
| 173 | +- Include `vcs` field in request body if non-null |
| 174 | + |
| 175 | +**`apps/control-plane/src/index.ts`** (deploy endpoint) |
| 176 | +- Accept optional `vcs?: VcsDiff` in deploy request body |
| 177 | +- Pass through to `createCodeDeployment()` |
| 178 | + |
| 179 | +**`apps/control-plane/src/deployment-service.ts`** |
| 180 | +- `createCodeDeployment()` accepts optional `vcs` field |
| 181 | +- Stores `vcs_type`, `vcs_sha`, `vcs_message`, `vcs_diff_stat`, `vcs_diff` on deployment record |
| 182 | + |
| 183 | +**`apps/control-plane/src/ask-project.ts`** |
| 184 | +- For "what changed" questions: pull `vcs_diff_stat` + `vcs_sha` + `vcs_message` from deployment |
| 185 | +- Add `"vcs_diff"` evidence type with summary like "Deployed from commit abc1234: 'fix auth middleware'. Changed 3 files, +47 -12 lines." |
| 186 | +- Pass `vcs_diff` to `synthesizeWithLLM()` for full diff context |
| 187 | + |
| 188 | +--- |
| 189 | + |
| 190 | +## Feature 4: Real AST Parsing |
| 191 | + |
| 192 | +Replace regex-based symbol extraction with a proper parser to get accurate symbols, imports, and a lightweight call graph. |
| 193 | + |
| 194 | +### Parser choice: `acorn` + `acorn-walk` + `acorn-typescript` |
| 195 | + |
| 196 | +- Pure JS, ~300KB total — no Wasm, works in Cloudflare Workers |
| 197 | +- Handles JS, JSX, TS, TSX (TypeScript stripped before parse via lightweight type stripping) |
| 198 | +- Acorn is battle-tested and used by many JS tools |
| 199 | + |
| 200 | +Alternative considered: `tree-sitter` Wasm (more accurate but 2-5MB Wasm bundle per language, cold start cost). Skip for now. |
| 201 | + |
| 202 | +### What changes |
| 203 | + |
| 204 | +**`apps/control-plane/package.json`** |
| 205 | +- Add `acorn`, `acorn-walk`, `acorn-typescript` |
| 206 | + |
| 207 | +**`apps/control-plane/src/ask-code-index.ts`** |
| 208 | +- Replace `jsTsAdapter` regex implementation with AST-based extraction |
| 209 | +- New/improved symbol extraction: |
| 210 | + |
| 211 | +| Kind | Before (regex) | After (AST) | |
| 212 | +|------|---------------|-------------| |
| 213 | +| `route` | basic `app.get("/x")` pattern | + object router `{ GET: handler }`, `pathname === "/x"` chained routes | |
| 214 | +| `function` | name only | name + param count + async/generator in signature | |
| 215 | +| `class` | name only | name + method names listed in signature | |
| 216 | +| `export` | any `export` keyword | distinguishes named/default/re-export | |
| 217 | +| `env_binding` | `env.UPPER_SNAKE` | same, but accurate (no false positives in strings/comments) | |
| 218 | +| `sql_ref` | SQL keyword match | same, more accurate (avoids matches in comments) | |
| 219 | +| `import` | not extracted | **new**: `from` module path + imported names | |
| 220 | +| `interface` | not extracted | **new**: TypeScript interface names | |
| 221 | +| `type_alias` | not extracted | **new**: TypeScript `type X = ...` | |
| 222 | + |
| 223 | +- New `meta` column on symbol rows (stored as JSON string) holds: |
| 224 | + - For `function`: `{ params: string[], async: bool, callees: string[] }` — lightweight call graph |
| 225 | + - For `import`: `{ from: string, names: string[] }` |
| 226 | + - For `class`: `{ methods: string[] }` |
| 227 | + |
| 228 | +**`apps/control-plane/migrations/0031_add_deployment_context.sql`** (same migration as above) |
| 229 | +```sql |
| 230 | +ALTER TABLE ask_code_symbols_latest ADD COLUMN meta TEXT; |
| 231 | +ALTER TABLE ask_code_index_runs ADD COLUMN parser_version TEXT; |
| 232 | +``` |
| 233 | + |
| 234 | +- Bump `PARSER_VERSION` constant in `ask-code-index.ts` to trigger re-index on next deploy |
| 235 | + |
| 236 | +--- |
| 237 | + |
| 238 | +## Migration summary |
| 239 | + |
| 240 | +One new migration file: `0031_add_deployment_context.sql` |
| 241 | + |
| 242 | +```sql |
| 243 | +-- Deployment context columns |
| 244 | +ALTER TABLE deployments ADD COLUMN has_session_transcript INTEGER DEFAULT 0; |
| 245 | +ALTER TABLE deployments ADD COLUMN vcs_type TEXT; |
| 246 | +ALTER TABLE deployments ADD COLUMN vcs_sha TEXT; |
| 247 | +ALTER TABLE deployments ADD COLUMN vcs_message TEXT; |
| 248 | +ALTER TABLE deployments ADD COLUMN vcs_diff_stat TEXT; |
| 249 | +ALTER TABLE deployments ADD COLUMN vcs_diff TEXT; |
| 250 | +
|
| 251 | +-- AST parser meta column |
| 252 | +ALTER TABLE ask_code_symbols_latest ADD COLUMN meta TEXT; |
| 253 | +``` |
| 254 | + |
| 255 | +--- |
| 256 | + |
| 257 | +## New files |
| 258 | + |
| 259 | +| File | Purpose | |
| 260 | +|------|---------| |
| 261 | +| `apps/cli/src/lib/vcs.ts` | Git/JJ diff capture | |
| 262 | +| `apps/cli/src/lib/session-transcript.ts` | Transcript read, truncate, upload | |
| 263 | +| `apps/control-plane/migrations/0031_add_deployment_context.sql` | Schema additions | |
| 264 | + |
| 265 | +--- |
| 266 | + |
| 267 | +## Modified files |
| 268 | + |
| 269 | +| File | Change | |
| 270 | +|------|--------| |
| 271 | +| `apps/control-plane/src/types.ts` | Add `ANTHROPIC_API_KEY?` to Bindings | |
| 272 | +| `apps/control-plane/wrangler.toml` | Add secret comment | |
| 273 | +| `apps/control-plane/package.json` | Add `@anthropic-ai/sdk`, `acorn`, `acorn-walk`, `acorn-typescript` | |
| 274 | +| `apps/control-plane/src/ask-project.ts` | Add `synthesizeWithLLM()`, load transcript + VCS diff context | |
| 275 | +| `apps/control-plane/src/ask-code-index.ts` | Replace regex with AST parser | |
| 276 | +| `apps/control-plane/src/index.ts` | New `PUT .../session-transcript` endpoint; accept `vcs` in deploy | |
| 277 | +| `apps/control-plane/src/deployment-service.ts` | Store VCS fields on deployment | |
| 278 | +| `apps/cli/src/lib/claude-hooks-installer.ts` | Add SessionStart env export + PostToolUse hook | |
| 279 | +| `apps/cli/src/lib/managed-deploy.ts` | Capture VCS diff + upload transcript after deploy | |
| 280 | +| `apps/cli/src/commands/` | Add `jack _internal upload-session-transcript` | |
| 281 | + |
| 282 | +--- |
| 283 | + |
| 284 | +## Open questions (no blockers) |
| 285 | + |
| 286 | +1. **Codex CLI**: no hooks API exists yet (open issue #2765). Transcript capture for Codex is deferred — the `vcs` diff path will still work for Codex users since it's CLI-side. |
| 287 | + |
| 288 | +2. **Model for LLM synthesis**: plan uses `claude-haiku-4-5-20251001`. If response quality is insufficient, swap to `claude-sonnet-4-5-20250929` — just a one-line constant change. |
| 289 | + |
| 290 | +3. **PostToolUse hook `tool_response` parsing**: the `deploy_project` MCP tool returns `{ deploymentId, projectId, ... }` wrapped in `formatSuccessResponse`. The hook script will parse the text content JSON. If the shape changes, the hook silently fails (no user impact — transcript upload is always best-effort). |
0 commit comments