feat(analyzer): add PHP support to content extractor (closes #15)#67
Merged
feat(analyzer): add PHP support to content extractor (closes #15)#67
Conversation
Adds a PHP extractor block inside extractExports() mirroring the existing Python, Java, and Ruby extractors. Detects top-level and public class functions (with parameter signatures), classes, interfaces, traits (mapped to 'class' kind), and constants. All names and signatures pass through sanitizeExportName / sanitizeExportSignature, matching the injection-hardening posture of the other extractors. Adds 7 new tests in content-extractor.test.ts covering each PHP construct plus a IgnoreAllPreviousInstructions.php injection fixture that must come out as [flagged]. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
smorchj
added a commit
that referenced
this pull request
Apr 15, 2026
Found while dogfooding Klonode on itself: opening the Workstation UI,
typing any prompt in the chat panel, and clicking send failed
immediately with "Feil: get is not defined" and the request never
reached the /api/chat/stream endpoint. Two independent bugs chained
together to make the UI unusable out of the box.
## Bug 1: missing `get` import in agents.ts
The self-hosting-survival helpers getCliSessionId / setCliSessionId /
clearCliSessionId call svelte/store's `get(sessionsStore)` but the
file only imported `{ writable, derived }`. Every chat send threw
ReferenceError at JSON.stringify(body) time because the fetch body
built `sessionId: getCliSessionId(...)`. Caught by the catch block
and rendered as "Feil: get is not defined" in the assistant bubble,
with no fetch ever being made.
Fix: import { writable, derived, get } from 'svelte/store'.
## Bug 2: demo graph ships with a placeholder repoPath
static/demo-graph.json had `"repoPath": "/path/to/your/project"` —
a literal placeholder. The ChatPanel passes graphStore.repoPath as
the cwd to /api/chat/stream, which passes it to spawn() as the child
working directory. On a fresh install a first-time user's chat send
would try to spawn Claude CLI in a nonexistent directory.
Fix:
1. static/demo-graph.json now uses "" for repoPath so the intent is
unambiguous.
2. /api/chat/stream now validates body.repoPath exists on disk and
falls back to process.cwd() with a console.warn if it doesn't.
That gives a sane default instead of an opaque spawn failure when
the user hasn't configured their project yet.
## How these were found
Sent a short "Reply with just READY" test through the chat textarea
via the preview tools, got "Feil: get is not defined". Stepped
through the send path, monkey-patched window.fetch to see the body,
found the fetch was never called. Traced backwards from JSON.stringify
and found getCliSessionId → get() → missing import. After fixing that,
repeated the test, got a working response, then validated end-to-end
by having Claude inside the chat panel write PR #67 (PHP extractor
support). Both fixes verified by the full "type in textarea, click
send" human path.
## Test plan
- pnpm --filter @klonode/core test — 78/78 passing
- Manual: Klonode Workstation UI, fresh page load, type a short
prompt, click send. Before: "Feil: get is not defined" in the
assistant bubble within 100ms. After: streaming Claude CLI
response with tool calls visible.
This was referenced Apr 15, 2026
smorchj
added a commit
that referenced
this pull request
Apr 15, 2026
Two separate issues combined to make long coding tasks inside the
Workstation chat panel hit a dead end with the generic fallback message
\"Claude brukte alle steg. Prøv et mer spesifikt spørsmål.\"
## Issue 1: max-turns was 50 for bypass mode
A realistic coding task — \"add a language extractor and 5-9 unit tests,
run the suite, commit on a branch\" — routinely needs 80-150 tool calls.
Bypass mode was capped at 50, which hit the ceiling mid-task and caused
Claude to return an empty result with num_turns = 50. The UI then
rendered the fallback \"alle steg\" message, making the session appear
dead.
Raise bypass and CO to 500. question (1) and plan (15) stay tight
because those modes exist precisely to cap turn spend.
## Issue 2: -p mode session IDs are not reliably resumable
Klonode captured the session_id from Claude CLI's first `init` stream
event and stored it as the tab's `cliSessionIds` entry. On the next
send, ChatPanel re-included it as `--resume <id>` so Claude would pick
up the conversation.
Problem: a session spawned with `-p` emits that session_id but does
NOT stay in Claude CLI's resumable session cache the same way
interactive sessions do. The next `--resume` spawn returns:
No conversation found with session ID: <uuid>
Claude then emits a result of type `error_during_execution` with empty
text, and the UI falls back to \"Claude brukte alle steg.\" The session
appears unresponsive forever until the user clicks + for a new tab.
Fix: drop `--resume` entirely from the stream endpoint. Every send is a
fresh spawn, always with the system prompt prepended. Continuity on
the user side lives in the persisted `chatStore.messages`, and Claude
re-routes against the generated CLAUDE.md + CONTEXT.md on every
message, which matches Klonode's per-query routing philosophy.
## How these were found
Ran `klonode init` on the repo, saw the full contextualized tree in
the Workstation TreeView, sent a realistic task (\"add Go extractor
support with 9 tests\") through the chat textarea + send button. Got
the \"alle steg\" fallback. Direct CLI test with the stored sessionId
reproduced the \"No conversation found\" error. Sent again without
--resume, hit a separate cap at exactly 50 tool calls. Raised to 500
and dropped --resume in one change.
After this fix, the same task completes in ~6 tool calls and produces
a branch + tests + passing suite.
## Sibling PRs
- #68 fixes the `get is not defined` crash + demo graph repoPath + auto
loading the real project graph at boot
- #67 and #69 are the \"this PR was written by Klonode itself\" PRs
(PHP and Go extractors) that these blockers were preventing
smorchj
added a commit
that referenced
this pull request
Apr 15, 2026
…68) * Fix two blockers that crashed every first-time chat send Found while dogfooding Klonode on itself: opening the Workstation UI, typing any prompt in the chat panel, and clicking send failed immediately with "Feil: get is not defined" and the request never reached the /api/chat/stream endpoint. Two independent bugs chained together to make the UI unusable out of the box. ## Bug 1: missing `get` import in agents.ts The self-hosting-survival helpers getCliSessionId / setCliSessionId / clearCliSessionId call svelte/store's `get(sessionsStore)` but the file only imported `{ writable, derived }`. Every chat send threw ReferenceError at JSON.stringify(body) time because the fetch body built `sessionId: getCliSessionId(...)`. Caught by the catch block and rendered as "Feil: get is not defined" in the assistant bubble, with no fetch ever being made. Fix: import { writable, derived, get } from 'svelte/store'. ## Bug 2: demo graph ships with a placeholder repoPath static/demo-graph.json had `"repoPath": "/path/to/your/project"` — a literal placeholder. The ChatPanel passes graphStore.repoPath as the cwd to /api/chat/stream, which passes it to spawn() as the child working directory. On a fresh install a first-time user's chat send would try to spawn Claude CLI in a nonexistent directory. Fix: 1. static/demo-graph.json now uses "" for repoPath so the intent is unambiguous. 2. /api/chat/stream now validates body.repoPath exists on disk and falls back to process.cwd() with a console.warn if it doesn't. That gives a sane default instead of an opaque spawn failure when the user hasn't configured their project yet. ## How these were found Sent a short "Reply with just READY" test through the chat textarea via the preview tools, got "Feil: get is not defined". Stepped through the send path, monkey-patched window.fetch to see the body, found the fetch was never called. Traced backwards from JSON.stringify and found getCliSessionId → get() → missing import. After fixing that, repeated the test, got a working response, then validated end-to-end by having Claude inside the chat panel write PR #67 (PHP extractor support). Both fixes verified by the full "type in textarea, click send" human path. ## Test plan - pnpm --filter @klonode/core test — 78/78 passing - Manual: Klonode Workstation UI, fresh page load, type a short prompt, click send. Before: "Feil: get is not defined" in the assistant bubble within 100ms. After: streaming Claude CLI response with tool calls visible. * Auto-load the real project graph at boot instead of the demo fixture Third blocker found while dogfooding: even after fixing the chat send path, the TreeView and GraphView still showed a fake `demo-project` tree with `app / lib / tests` folders. The Workstation hard-coded \`loadGraphFromUrl('/demo-graph.json')\` in +page.svelte onMount, so every user — regardless of which real repo they had Klonode running in — saw the same bundled fixture. ## Fix - **New endpoint** GET /api/graph/current. Walks up from the server's cwd looking for \`.klonode/graph.json\` and returns it if found. Responds 404 when no initialized project is found so the client can fall back. Also supports ?repoPath=<abs> for explicit targeting. - **New loader helper** loadGraphForCurrentProject() in loader.ts. Tries /api/graph/current first, falls back to the bundled /demo-graph.json if the project isn't initialized. - **+page.svelte** now calls loadGraphForCurrentProject() at boot. Tracks whether it loaded 'real' vs 'demo' so the UI can signal this in a future toolbar indicator. ## The walk-up matters The Workstation dev server is typically started inside packages/ui/ (where launch.json's cwd points), not the repo root. A naive \`process.cwd() + .klonode/graph.json\` check would 404 on every monorepo. The new endpoint walks up parent directories (with a guard against infinite loops) until it finds an initialized project or hits the filesystem root. ## Verified Dev server up, page loaded. Store now reports: - rootName: "KlonodeV2" (was "demo-project") - nodeCount: 69 (was 4) - topLevelNames: [".github", ".marketing", "packages"] (was ["app", "lib", "tests"]) TreeView renders .github/ISSUE_TEMPLATE, .github/workflows, .marketing, and packages/{cli, core, desktop, ui}.
smorchj
added a commit
that referenced
this pull request
Apr 15, 2026
Two separate issues combined to make long coding tasks inside the
Workstation chat panel hit a dead end with the generic fallback message
\"Claude brukte alle steg. Prøv et mer spesifikt spørsmål.\"
## Issue 1: max-turns was 50 for bypass mode
A realistic coding task — \"add a language extractor and 5-9 unit tests,
run the suite, commit on a branch\" — routinely needs 80-150 tool calls.
Bypass mode was capped at 50, which hit the ceiling mid-task and caused
Claude to return an empty result with num_turns = 50. The UI then
rendered the fallback \"alle steg\" message, making the session appear
dead.
Raise bypass and CO to 500. question (1) and plan (15) stay tight
because those modes exist precisely to cap turn spend.
## Issue 2: -p mode session IDs are not reliably resumable
Klonode captured the session_id from Claude CLI's first `init` stream
event and stored it as the tab's `cliSessionIds` entry. On the next
send, ChatPanel re-included it as `--resume <id>` so Claude would pick
up the conversation.
Problem: a session spawned with `-p` emits that session_id but does
NOT stay in Claude CLI's resumable session cache the same way
interactive sessions do. The next `--resume` spawn returns:
No conversation found with session ID: <uuid>
Claude then emits a result of type `error_during_execution` with empty
text, and the UI falls back to \"Claude brukte alle steg.\" The session
appears unresponsive forever until the user clicks + for a new tab.
Fix: drop `--resume` entirely from the stream endpoint. Every send is a
fresh spawn, always with the system prompt prepended. Continuity on
the user side lives in the persisted `chatStore.messages`, and Claude
re-routes against the generated CLAUDE.md + CONTEXT.md on every
message, which matches Klonode's per-query routing philosophy.
## How these were found
Ran `klonode init` on the repo, saw the full contextualized tree in
the Workstation TreeView, sent a realistic task (\"add Go extractor
support with 9 tests\") through the chat textarea + send button. Got
the \"alle steg\" fallback. Direct CLI test with the stored sessionId
reproduced the \"No conversation found\" error. Sent again without
--resume, hit a separate cap at exactly 50 tool calls. Raised to 500
and dropped --resume in one change.
After this fix, the same task completes in ~6 tool calls and produces
a branch + tests + passing suite.
## Sibling PRs
- #68 fixes the `get is not defined` crash + demo graph repoPath + auto
loading the real project graph at boot
- #67 and #69 are the \"this PR was written by Klonode itself\" PRs
(PHP and Go extractors) that these blockers were preventing
This was referenced Apr 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds PHP (`.php`) support to the content extractor, mirroring the existing Python, Java, and Ruby blocks inside `extractExports()`.
Detects and emits `FileExport` entries for:
All names and signatures pass through `sanitizeExportName` / `sanitizeExportSignature` to maintain the same injection-hardening posture as the other extractors.
Tests
7 new tests in `content-extractor.test.ts` inside a `describe('extractDirectoryContent — PHP')` block:
Test count: 71 → 78 (35 extractor tests total, was 28).
How this PR was produced
This PR was written by Klonode itself — opened by Claude running inside the Klonode Workstation chat panel, using the same UI flow a human would. The task description was pasted into the chat textarea, the send button was clicked, and Claude worked through the existing Python/Java/Ruby pattern as the template. Two real Klonode bugs were found along the way and are being fixed in a separate PR.
Closes
Closes #15.