Skip to content

Commit 0d08514

Browse files
hellnoclaude
andauthored
Add LLM synthesis and session transcript support to ask feature (#17)
* feat(ask-project): capture and upload Claude Code session transcript at deploy time After each managed deploy, the Claude Code session transcript is automatically uploaded to R2 as a deployment artifact. This gives the ask feature rich context about what was being worked on when the code shipped. Two paths capture the transcript: - CLI deploy (jack deploy/ship): reads CLAUDE_TRANSCRIPT_PATH from env, which is exported by a new SessionStart hook via CLAUDE_ENV_FILE - MCP deploy (deploy_project tool): a new PostToolUse hook fires after the tool call and uploads the transcript directly New pieces: - PUT /v1/projects/:projectId/deployments/:deploymentId/session-transcript endpoint - jack _internal session-start: SessionStart hook handler (exports transcript path) - jack _internal post-deploy: PostToolUse hook handler (uploads after MCP deploy) - installClaudeCodeHooks() now also installs the two new hooks - DB migration 0031: has_session_transcript column on deployments https://claude.ai/code/session_01Naw4TaqKjHM1m2HFpxs23y * docs: add implementation plan for ask feature improvements https://claude.ai/code/session_01Naw4TaqKjHM1m2HFpxs23y * feat(ask-project): LLM synthesis + session transcript evidence Replaces heuristic pickAnswer() with Claude (haiku) synthesis. Evidence is passed as structured context; session transcript excerpt from the deploy is included when available. Falls back to pickAnswer() if ANTHROPIC_API_KEY is not set or the API call fails. New pieces: - synthesizeAnswer(): calls claude-haiku-4-5-20251001, returns answer + root_cause + suggested_fix + confidence; returns null when key absent - fetchSessionTranscript(): reads session-transcript.jsonl from R2, parses Claude Code JSONL format (type/message/content), returns last 30 turns - AskProjectResponse extended with root_cause?, suggested_fix?, confidence? - AskProjectEvidence.type union gains "session_transcript" - ANTHROPIC_API_KEY? added to Bindings; @anthropic-ai/sdk added to deps - scripts/test-ask-e2e.sh: 7-step E2E test suite for transcript + ask endpoints - scripts/curl-ask-examples.sh: quick curl reference Run `wrangler secret put ANTHROPIC_API_KEY --cwd apps/control-plane` to enable LLM synthesis; feature degrades gracefully without it. https://claude.ai/code/session_01Naw4TaqKjHM1m2HFpxs23y * feat(ask-project): switch to Workers AI GLM-4.7-Flash, add session digest - Replace Anthropic SDK with Cloudflare Workers AI binding for LLM calls - Use @cf/glm4/glm-4.7-flash for both digest generation and ask synthesis - Generate prose session digest at transcript upload time via waitUntil - Store digest in session_digest column on deployments (migration 0032) - Remove @anthropic-ai/sdk dependency and ANTHROPIC_API_KEY references - Update @cloudflare/workers-types to 4.20260218.0 * fix(mcp): surface build stderr and include HOME/TMPDIR in env config MCP deploys failed with "Build failed" across projects because: 1. Claude Code/Desktop replaces the entire process env with only what's in the MCP config — missing HOME/TMPDIR broke wrangler subprocess resolution 2. The actual build error (stderr) was dropped by formatErrorResponse(), making failures undiagnosable Add HOME, TMPDIR, USER to MCP env config. Surface JackError.meta.stderr as error.details in MCP responses. Users need `jack mcp install` to pick up the new env config. * feat(docs): apply design system theme to vocs config Map oklch color tokens, JetBrains Mono, forced dark mode, and transparent shadows to match the landing page design system. * fix(mcp): use cloud-authoritative data for managed project name and URL Three hardening fixes for wrong project name/URL in MCP responses: - Fix E: sync wrangler config name with cloud slug after project creation (4 call sites in project-operations.ts). Adds updateWranglerConfigName() utility that preserves comments/formatting for JSONC and TOML. - Fix C: getProjectStatus() uses overview response (slug, url) instead of reading wrangler config name for managed projects. Falls back to local data if cloud is unreachable. - Fix D: reject deploy if wrangler name is literally "jack-template" — this is always a bug from un-rendered template files. --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6fc361b commit 0d08514

21 files changed

Lines changed: 1194 additions & 41 deletions

PLAN.md

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
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 totalno 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 deferredthe `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 impacttranscript upload is always best-effort).

0 commit comments

Comments
 (0)