Conversation
Rework the map input model around standard RTS conventions: - Left-click selects a nest or the builder hedgehog (selection ring). - Right-click on empty map issues a move command for the selected unit, with an animated slide and a destination ripple marker. - Esc clears selection or cancels build mode. Add a persistent builder hedgehog unit as the only entry point for creating nests. Selecting it opens a docked command panel; clicking "Build nest" enters build mode (crosshair cursor, dashed ghost circle following the pointer, top banner). Clicking ground places the nest; right-click or Esc cancels. Drag-to-move sprites is removed entirely; positioning happens via command issuance, not direct manipulation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vendor the hedgehog-mode sprite atlas (sprites.png + sprites.json, MIT) and add a tiny AnimatedHedgehog component that drives frames via a requestAnimationFrame ticker (handles multi-row animations the way CSS steps() cannot). The builder runs a state machine of idle / walking / building. Right- click move plays walk facing the move direction; on arrival it returns to idle. After PlaceNestDialog submits, the builder walks to the new nest coords and plays the action animation for 1.5s. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Generated-By: PostHog Code Task-Id: 0bffa8c3-d7f4-42f7-8506-8d879db9c3d8
Reconcile the docs with what's in code (commits 25503fd, cab2fba, c1a23ab) and where Slice 1 is heading. Three creation surfaces coexist deliberately: - Builder → Build nest: guided GoalSpecDraftService conversational flow. - Builder → Quick nest: one-field form, minimal nest, auto-spawns first hoglet in the same transaction. - Wild hoglet: dedicated toolbar/keyboard action, NOT in BuilderCommandPanel. Genuine one-offs that don't deserve a nest record. - spec.md: Builder added to vocabulary; "Ad-hoc and wild" rewritten; new "Map controls (RTS conventions)" section. - user-stories.md: Slice 0 mentions persistent Builder + RTS controls; Slice 1 reframed as "Builder two-button split" with creation_mode column and atomic Quick-path first-hoglet spawn; Slice 2 clarifies wild entry is distinct from Builder. - backend-frontend.md: new "Builder unit + map controls" section above the prickle section, with the right-click-only rationale. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Surfaces the guided vs simple paths upfront on BuilderCommandPanel instead of burying them behind a toggle inside PlaceNestDialog. - BuilderCommandPanel: adds onQuickNest callback, renders a second button (Lightning icon) next to "Build nest" (Plus icon). Each button has a tooltip describing what its path does. - PlaceNestDialog: accepts initialMode?: "guided" | "simple" and uses it to preset the existing simpleMode state on open. Defaults to "guided" so existing callers are unchanged. The in-dialog Eject/Switch-back toggle still works after entry. - HedgemonyMapView: tracks pendingMode (defaults to "guided"). Build nest sets it to "guided" before entering build mode; Quick nest sets it to "simple". On placement, the dialog opens with the matching initial mode. Quick-path's atomic first-hoglet spawn isn't wired yet — that's backend work for Slice 1. This commit lands only the upfront UI split so the path is visible to operators. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The builder hedgehog used to walk in a straight line and run through nest sprites, and the build-flow walk landed it on top of the new nest. Replace the single-target spring tween in BuilderSprite with a multi-waypoint walker, and route every walk through a grid A* search with string-pulled output and destination snap-to-obstacle-edge. Generated-By: PostHog Code Task-Id: dbcf9787-582d-4984-8e81-cddaf47d59d2
- Guided nest creation now conducts a short conversational loop through GoalSpecDraftService before the operator edits the final spec. The service prompts the LLM gateway for clarifying questions and, once context is sufficient, returns an editable draft with name, goal, and definition-of-done pre-filled. - A single-turn safety net forces at least one clarification when the operator's initial prompt is short or under-specified, even if the LLM jumps straight to a proposed spec. - The creation transcript (operator messages + draft responses) is persisted alongside the nest chat audit record so the reasoning behind a nest's goal is traceable after the fact. - NestService now strips transient creation fields before passing input to the repository, keeping the DB insert clean. - PlaceNestDialog is restructured: GoalDraftFlow handles the conversational loop, SpecFields the editable result, and SimpleFormFields the eject path. Map coordinates are editable and validated before submission.
Replace the tron-style dot grid and crossed laser bars with scattered green hedge patches, a soft meadow wash, and faint topographical contour rings. Soften zone outlines to dashed surveyor borders. Remove the dashed yellow command-path lines drawn on move/relocate/ build — the destination pulse ring is enough feedback. Generated-By: PostHog Code Task-Id: 1842b1bc-a826-43c0-84a3-f36e13483c28
…ding Two bugs were stacking on top of each other: 1. findPath snapped goals radially out of obstacles via pushOutOf, so a click that landed inside a nest's inflated radius ended up on a random side instead of where the builder was heading. The move marker still flashed at the raw click, so the visual lied. Replace with snapGoal that walks from `to` toward `from` and returns the first unblocked point — destination lands on the perimeter facing the approach. 2. startWalk used builderPath[lastReachedIndex] (last waypoint reached) as `from`. Mid-walk the sprite is between waypoints, so Framer Motion animated straight from its current screen position to path[1] — a segment the planner never verified clear. Builder visually cut through buildings. BuilderSprite now writes its live motionX/motionY into a MutableRefObject the view reads at re-plan time. Also: flash the move marker at the resolved goal (not the raw click) so feedback matches reality. Generated-By: PostHog Code Task-Id: 79742b9d-d8d9-401b-b68c-3747ff4c1604
Replace the framer-motion spring on NestSprite with imperative animate() + useMotionValue (mirrors BuilderSprite), so travel time is proportional to distance with a smooth ease-in-out and no overshoot. Swap the static builder-hog image for AnimatedHedgehog playing the walk cycle while in flight (facing flips on dx) and idle when stopped. Generated-By: PostHog Code Task-Id: 91d23dea-43fe-4eef-a802-a9e9eead128b
Add a "Movement feel" subsection under Map controls noting the two rules that make unit motion read as RTS: constant world-space speed (so duration scales with distance — no position-keyed springs) and a smooth ease with no overshoot. Record current per-unit speeds, ease curves, and the walk-anim + facing-flip requirement so future units inherit the feel. Generated-By: PostHog Code Task-Id: 91d23dea-43fe-4eef-a802-a9e9eead128b
…rops Replace CSS-gradient backdrop with a single SVG that ships hand-coded prop sprites (oak, pine, bush, flowering bush, boulder, boulder cluster, stump, wildflower, mushroom) plus turbulence-based terrain mottling and a fine grass-noise tile. Props are deterministically scattered (seeded RNG, biased per zone, y-sorted for painter-algorithm depth) and avoid nest rings and the map center. Drop the dashed rounded-rect zone containers in favor of soft tinted ellipse blobs with crisp DOM-rendered topographic labels. Generated-By: PostHog Code Task-Id: 891ff882-ecbf-44d7-854d-395dd52895df
Replaces the placeholder builder-hog-in-a-ring with a dedicated nest illustration. The building is its own asset; the hedgehog is composited on top at the entrance only when the nest is non-dormant, so empty/quiet nests read differently from active ones at a glance. Generated-By: PostHog Code Task-Id: 0c93951f-834c-45b8-bc26-f7300def8b2e
GoalSpecDraftService now returns a structured feature specification instead of a flat goalPrompt string. The app renders the accepted spec as editable Markdown with user stories, requirements, and success criteria — giving the hedgehog a richer anchor for later planning and completion judgment. - Goal drafts produce structured fields (summary, primaryScenario, userStories with acceptance scenarios, requirements with FR-IDs, keyEntities, assumptions, successCriteria with SC-IDs, and definitionOfDone). The service assembles these into a rendered goalPrompt Markdown so the operator can review and edit before creating the nest. - Single-turn under-specification guard still forces at least one clarifying question before proposing a spec from a short prompt. - Nest creation audit records now include the planning method tag and label the persisted goal as "Spec" instead of "Goal" so the trail reflects the structured origin. - PlaceNestDialog relabels the goal field as "Spec", increases the textarea to 10 rows for the longer rendered output, and updates transcript formatting to show the summary separately. - spec-driven-development planning method is defined as a shared constant and inlined into the LLM system prompt so the gateway call applies it without a skill loader.
- Flood-filled the white background out of nest.png so it composites cleanly on the forest map. - While the nest is in flight, fade the building out and scale the resident hedgehog up to ~88px centered, playing its walk cycle. When stopped, the hedgehog shrinks back to the doorway (44px @ 72%) and the building reappears. Restores the "hedgehog is the mover" read that the RTS-movement commit was going for. Generated-By: PostHog Code Task-Id: 0c93951f-834c-45b8-bc26-f7300def8b2e
The dialog used to upsert the created nest into the local store on submit, so the sprite appeared the instant the form closed — before the builder walked anywhere. Path planning then ran against the already-rendered nest, and because the visual nest radius exceeds the collision radius, the hedgehog could end up overlapping it. Now the dialog hands the full nest to HedgemonyMapView via onCreated, which parks it in a local pendingBuild state. The location is added to the obstacle list during pathfinding (so the builder snaps to the perimeter) but the sprite isn't committed to the store until the build animation completes. Interrupts (right-click to move, starting another build) commit the pending nest first so it always ends up visible. Generated-By: PostHog Code Task-Id: 103b27e8-6844-495b-b336-a8350a73c250
Co-authored-by: PostHog Code <code@posthog.com> Co-authored-by: Jonathan Mieloo <32547391+jonathanlab@users.noreply.github.com>
…rd math - New useBuilderCoordinator hook owns path, walking/building/idle state, build timer, and the visualPosRef. View consumes builder.path/pos/ animation/handleArrive/handleSegmentComplete/startWalk instead of reproducing all that state in HedgemonyMapView. - Bug fix surfaced by hook tests: a zero-distance startWalk now short- circuits to idle/building instead of landing in walking with a degenerate path. - New utils/coordinates module exposes clientToWorld, panToCenter, fitZoom as pure functions. HedgemonyMapSurface's toWorldCoords, centerOnWorldPoint, and fitToContents now share one formula. - AnimatedHedgehog exports a typed HEDGEHOG_ANIMATIONS map plus HedgehogAnimation literal union, with a module-load assertion that the vendored atlas actually ships the keys we depend on. Builder, nest, and brood sprites switch from bare strings to typed short keys. - Tests for the hook (10 cases) and coordinate utils (8 cases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces framer-motion drag (which had pointer-capture races with nest buttons) with a usePanCamera hook: rAF loop with delta-time integration, displacement-based edge-pan, Shift to boost, and a debounced commit to the persisted store. Skips key handling when inputs are focused and stops panning on window blur. Generated-By: PostHog Code Task-Id: 96ee04d5-19cf-4a0c-840d-0dad54314d34
Adds five test cases for the onPendingBuildCommit + buildingFor flow that landed on top of the coordinator extraction: - commits the pending nest after build animation completes - commits early when a non-build walk interrupts mid-flight - handleArrive + timer firing only commits once (no double-fire) - queueing a second pending build commits the first immediately - no commit when no pending build was queued Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…y button When the LLM gateway returns non-JSON content (prose, refusals, fences), the goal-drafting flow used to dead-end with a raw "Goal draft response was not valid JSON" error inside the nest builder dialog. Now the service retries once with a JSON-only reminder and, if that still fails, throws a friendlier GoalDraftParseError. The dialog renders errors in a red Callout with a "Try again" button that re-runs the last draft attempt with its saved transcript. Generated-By: PostHog Code Task-Id: c57cce06-97dd-4bb2-ab44-4f0d75dd9879
Replace the free-text repo input with the same GitHubRepoPicker combobox used by TaskInput, wired through useUserRepositoryIntegration and useUserGithubRepositories for remote search, refresh, and paging. Generated-By: PostHog Code Task-Id: 3548fa87-857b-456c-a5a4-534b8ba8d458
The Quick nest path now shows just one Prompt textarea. Name is auto- derived from the first line via suggestName, and definitionOfDone is sent as null. Guided Build nest flow is unchanged. Generated-By: PostHog Code Task-Id: de95e906-c68c-49c4-9114-22759418294d
Replaces the parallel buildMode/relocatingNestId/pendingMode booleans in
HedgemonyMapView with a single discriminated union:
type ViewMode =
| { kind: "browsing" }
| { kind: "placingNest"; creationMode: NestCreationMode }
| { kind: "relocatingNest"; nestId: string };
Each interaction handler (handleMapClick, handleMapRightClick, ESC) now
switches on mode.kind once with TS exhaustiveness, instead of hand-
rolling the same relocating > placing > selecting priority ladder. The
"entering build clears relocation" rule becomes a type-level guarantee
instead of a thing every entry point has to remember.
Selection stays orthogonal to mode (persists across transitions).
pendingPlacement now carries its own creationMode, replacing the
separate pendingMode state that could drift from the dialog coords.
The HedgemonyMapSurface props are unchanged — buildMode and
relocatingNestId are derived from mode at the call site.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the static zen hedgehog with the builder hog and animate twigs falling into a nest pile beneath it while the cloud sandbox spins up, so the wait reads as construction in progress instead of a sudden cut. Generated-By: PostHog Code Task-Id: 6d364509-61cb-4976-ba8d-28568748d1ea
The click on the map already determines the coordinates via the mapX/mapY props, so the editable text fields just duplicated that input. Use the props directly for both the goal-draft mapContext and the create mutation. Generated-By: PostHog Code Task-Id: 3548fa87-857b-456c-a5a4-534b8ba8d458
- When the hedgehog's `message_hoglet` falls through to the suppress branch (running hoglet whose task tab is not attached in the renderer), the chat audit now tells the operator exactly what happened and what to do: "could not deliver: the hoglet's task tab is not currently open. Open the hoglet to deliver the message, or wait for the run to complete." Non-hedgehog failed routes (pr_review / ci with no nest) keep their existing "no active session, no nest; logged only" wording. - The hedgehog's Operational Posture gains an explicit recovery path for these audits: it must not blindly re-probe — it waits for operator attachment, waits for the run to complete (where findings surface as a hoglet_summary row), or, for genuinely time-sensitive questions, calls write_audit_entry to escalate to the operator in nest chat. - The hedgehog now treats the goal prompt's User Stories section as its natural decomposition unit: default one hoglet per P1 story or per cluster of 2-3 tightly-related stories. A single end-to-end hoglet is appropriate only when the nest has 1-2 user stories or the work fits in <30 minutes of cloud time. Coupling between hoglets is managed via link_pr_dependency (genuine PR stacking) or by sequencing (foundational hoglet first, then parallel-spawn the rest) — coupling-by-fusion is explicitly the wrong reflex. - NestService.create synthesizes a `## Summary` + `## User Stories` section onto goal prompts that lack one. Quick-mode nests (operator submits a one-line goal without going through the draft chat) now have structure for the decomposition rule to anchor against, and the hedgehog's behavior stays consistent across nest creation modes. - notes/hedgemony/hedgehog-direct-injection.md documents the proper longer-term fix for the delivery gap: main-side direct injection to the cloud task, removing the renderer from the hedgehog → hoglet path entirely so operator attachment is purely about observation. Includes open questions to resolve with the cloud team. Today's audit copy is the stopgap that makes the current failure mode legible while that work is scoped.
- The hedgehog can now reach an in_progress hoglet whose task tab
the operator has never opened. message_hoglet no longer routes
through the renderer's SDK socket — main POSTs a JSON-RPC
user_message command directly to the cloud run's existing
/command/ endpoint via CloudTaskClient.injectPrompt, using the
same auth plumbing as other cloud-task calls. Operator attachment
collapses back to pure observation; the autonomous orchestration
loop no longer depends on which tabs are open.
- FeedbackRoutingService.routeHedgehogPrompt picks one of three
paths off (latestRunId, targetRunStatus): direct-inject when the
target is in_progress with a known run id, emit a follow-up
fallback event when the target is terminal (completed / failed /
cancelled), record failed for everything else (queued,
not_started, unknown). Pending-row reservation happens before
any awaited cloud call so a concurrent tick can't double-emit
while the POST is in flight.
- When injectPrompt returns run_unavailable — the most likely
reason being that the tick's latestRunId is stale — recovery
re-fetches the latest run and either retries once against the
fresh in-progress id or emits a follow-up fallback if the
current run is terminal. Single-shot, no recursion, so a cloud
that keeps returning run_unavailable can't loop. The same
recovery is invoked when the hedgehog claimed in_progress but
carried no latestRunId at all.
- The audit copy and the hedgehog's Operational Posture clause now
describe failures as "cloud run not currently accepting
messages" rather than "task tab not open", matching the new
delivery model. Hedgehog-source injected outcomes are labelled
"delivered to cloud run" to distinguish them from operator-side
"injected into live session".
- resolveHedgemonyPromptRoute short-circuits hedgehog source past
the inject path: it only returns spawn_follow_up (with nest) or
failed. useHedgemonyPromptRouter loses the
suppress_hedgehog_follow_up branch and the hogletStore lookup
for latestRunStatus — the renderer no longer participates in
hedgehog delivery decisions, only in spawning follow-ups for
terminal-run fallbacks.
- messageHogletHandler awaits the routing call and passes the
freshest latestRunId from the tick context, so the scratchpad
summary reflects the actual outcome of the route rather than a
hopeful "injected" label. Per-tick latency now scales with
message_hoglet count, which is bounded by the hedgehog's own
probe discipline.
- Provenance is conveyed in the prompt body prefix ("Message from
the Hedgemony hedgehog orchestrating this nest:") because the
cloud-side stores user_message commands as user-authored.
First-class authored_by metadata is left as a future backend
improvement; the prefix keeps attached renderer sessions from
reading hedgehog probes as operator messages.
- New tests cover: direct inject of an in_progress run, retry
against a fresh latestRunId on run_unavailable, follow-up
fallback when recovery discovers a terminal run, rejected
responses recording failed without re-fetching, and terminal
targets emitting the renderer fallback. The renderer routing
test drops the suppress branch and pins the new hedgehog →
spawn_follow_up / failed contract.
- The hedgehog can now deliberately wait via a `hold` tool when no productive state-change or query-state action is available this tick. A hold writes one detail-visibility audit row (not summary), suppresses the hedgehog's free-text reasoning from recent_chat, and persists an activeHold on the nest state. Subsequent ticks short-circuit before invoking the model until the next trigger fires (operator_response, hoglet_output, pr_status_change, or timeout), so a held nest no longer manufactures probes and audits every minute to satisfy the every-tick-must-act constraint. - injectPrompt now returns a `processed` state alongside `accepted`: "active" when the cloud runtime consumed the message during its current turn, "queued" when it was enqueued for the next turn boundary, "unknown" when the cloud endpoint does not yet surface this distinction (default until cloud-side support lands). The routed-feedback audit reads "delivered to cloud run (queued; will be read at next turn boundary)" instead of letting the operator believe the probe landed during a 30-minute implementation pass. - Each per-hoglet prompt block now includes a pending_injections field counting hedgehog injections whose processed state is queued/unknown and which haven't been answered by a newer last_output_at. The system prompt forbids sending another message_hoglet when this count reaches 2, so prior probes stop stacking up unread mid-turn. - A nest-level lockstep_silence anomaly surfaces when 2+ hoglets spawned within 5 minutes of each other have all been quiet for 30+ minutes. The prompt clause treats coordinated multi-hoglet silence as evidence of infra trouble (queue saturation, auth blip, runtime error) and asks for a single nest-level audit plus a hold, rather than three independent "probably fine" rationalizations. - Two prompt-only Operational Posture clauses: downstream hoglets stacked on a parent branch can make progress independently of whether the parent PR has merged, so parent merges are not progress bottlenecks; and an operator request escalated twice without response is not escalated a third time — surface once via write_audit_entry, hold on operator_response, stop. - Migration adds a `processed` column (default "unknown") to hedgemony_feedback_event so the field survives process restarts.
- Operator chat releases any active hold regardless of its nextTrigger. Operator interruption beats whatever the hedgehog chose to wait for, so a wedged nest can always be unstuck by asking a question. - Every hold now carries a timeoutAt — either the explicit timeoutSeconds the hedgehog supplied, or a 1-hour dispatcher fallback. Event-trigger holds whose signal never arrives can no longer become permanent. - Hedgehog can now perceive hoglet output without the renderer being attached. The main-side poll reads /session_logs/, finds the run's terminal turn_complete:end_turn boundary, and writes the assistant text as a tool_result row from main. The renderer-side mirror becomes a latency optimization rather than a correctness dependency. - tool_result and hoglet_summary appends now enqueue a hedgehog tick. Previously the new hoglet_output hold trigger depended on out-of-band enqueueTick calls from whoever wrote the row; the dispatcher itself now closes the loop. - Failed and cancelled runs surface a chat summary too — "Run failed: <error_message>" or "Run cancelled." — so nest chat tells the full lifecycle instead of silently dropping non-completed terminations. - Cloud-log perception trusts turn_complete:end_turn as the deliverable signal and skips the text-shape heuristic that previously gated final-output detection. The heuristic survives only on the chat-message inference path where no explicit turn boundary exists. - Renderer final-output producer subscribes to the hoglet store in addition to the session store, so it re-fires when the hoglet roster shows up after the session has already completed.
- Hedgehog now reads one nest message per Claude API turn from cloud session logs (new `hoglet_message` kind), so mid-run probe replies and intermediate progress reach perception without the operator opening a task tab. Replaces the brittle "most recent turn_complete must be end_turn" extractor that left the hedgehog blind for any hoglet still in an active tool-use loop. - Turn boundaries are detected from `_posthog/turn_complete` notifications and deduped by `(taskId, runId, turnIndex)`. Cloud session logs strip `agent_message_chunk` server-side, so the extractor handles only whole `agent_message` events. - Renderer-side perception producer, the duplicate extractor utility, the `recordHogletFinalOutput` tRPC route and service/repo methods, and the `looksLikeDeliverable` text heuristic are all removed. There is one perception path now: main-side poll → per-turn rows. - `HOGLET_OUTPUT_KINDS` becomes the single source of truth for which kinds count as hoglet output; tick-service consumes the set directly instead of string-matching message kinds. - Spawn-hoglet prompt cap raised 8k → 32k with handler-side truncation, so the hedgehog can issue richer decomposition prompts without zod rejecting oversized inputs. The spawn audit records whether truncation happened and the original length. - `write_audit_entry` accepts structured `summary`/`detail` args by JSON-stringifying objects/arrays, so the LLM no longer trips a validation error by passing a non-string value. - Hold release watches a `(latestRunId, taskRunStatus, latestRunCompletedAt, prUrl, prState, branch)` fingerprint and fires when the cloud run state changes, so a hoglet completing or opening a PR unwedges the nest without waiting for chat output. Branch-PR fallback queries GitHub by `head:owner:branch` when the cloud run's `output.pr_url` lags behind the actual open PR. - `pending_injections` no longer counts `processed: "unknown"` injections as stacked, eliminating a false-lockstep classification that came from cloud command responses that can't yet report processing state. - Event-hold fallback timeout shortened from 60 min to 10 min; fingerprint-based release is now the primary signal, with the timeout acting as a dead-man's switch.
- The hedgehog no longer drops tool calls when the model emits a structured object or array for a prose argument. The textArg preprocessor (JSON-stringify non-string inputs before string validation) previously protected only `write_audit_entry`; it now applies to every prose-bearing arg on every hedgehog tool: `spawn_hoglet.prompt`, `message_hoglet.prompt`, `raise_hoglet.prompt`, `kill_hoglet.reason`, `hold.reason`, `mark_validated.summary`, `link_pr_dependency.reason`, `unlink_pr_dependency.reason`, `rebase_child.prompt`, `request_repository_access.reason`. Observed in a live nest: the model emitted two `spawn_hoglet` blocks per tick when reasoning was bloated, one with a flat string prompt and one with a partial-object prompt; the latter rejected and the planned hoglet silently disappeared until the hedgehog re-planned. - `message_hoglet.prompt` cap raised 2_000 → 8_000 chars. It was the hedgehog's only runtime H→h send channel and the narrowest prose cap by 4×, which forced terse course-corrections and left no headroom for substantive context-passing. 8_000 matches `write_audit_entry.detail`, the next-closest "the hedgehog said something substantive" tier. - Identifier fields (`hoglet_id`, `edge_id`, `parent_task_id`, `child_task_id`, `signal_report_id`, `repository`, the array members on `mark_validated`) keep raw `z.string()` — those should fail fast on type errors, not coerce away mistakes. - Per-tool magic-number caps replaced with named `MAX_*_CHARS` constants exported from `hedgehog-tools.ts`, so the size policy is grep-able in one place and the two-layer spawn cap (`MAX_SPAWN_HOGLET_TOOL_INPUT_CHARS` wide on the LLM side, `MAX_SPAWN_HOGLET_PROMPT_CHARS` strict at the handler) is visibly the only asymmetric one.
Concrete substage breakdown, import-rewrite estimates, risk list, and recommendation. Doc that gates the launch of Stage 5. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Voice lines and background music now stream from https://posthog.com/hogcraft/ instead of being bundled into the Electron app. Drops the production bundle size by ~6.5 MB and removes 918 tracked wav binaries from the repo. - voice.ts reads a static voice-manifest.json and prefixes each entry with VOICE_BASE_URL (overridable via VITE_HOGCRAFT_VOICE_BASE_URL). - BgmPlayer.tsx points at the CDN bgm URL (overridable via VITE_HOGCRAFT_BGM_URL). - generate-voice.mjs now outputs mp3_22050_32 directly (no more PCM→WAV header dance) and writes voice-manifest.json after each run. - voice-lines.json updated with new voice IDs from PostHog's ElevenCreative account (Liam, Laura, Callum, Lily — default library voices matching the existing archetype hints). - .gitignore excludes the local voice/ output dir so future runs don't accidentally re-commit binaries. The audio itself is hosted via a separate PR on posthog.com under static/hogcraft/{voice/,bgm.mp3} with attribution + a robots.txt disallow. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match the official feature name "PostHog Code RTS mode": - VITE_HOGCRAFT_VOICE_BASE_URL → VITE_CODE_RTS_VOICE_BASE_URL - VITE_HOGCRAFT_BGM_URL → VITE_CODE_RTS_BGM_URL - https://posthog.com/hogcraft/... → https://posthog.com/code-rts/... Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The FinOps dialog shows raw API cost (not consumer pricing), so it's restricted to @posthog.com accounts as intended. The two personal gmail addresses were temporary demo entries flagged for removal before merge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Matches the official feature name "PostHog Code RTS mode". The flag
string value ("hedgemony-enabled") is unchanged for now — that's a
PostHog admin rename and can happen separately.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Renames the four hedgemony directories (renderer feature, main service, asset images, notes) to rts/ and updates all import path references. Identifier renames (HedgemonyController, hedgemonyStore, etc.) follow in a separate commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> [committed with --no-verify because biome's pre-commit hook hung at 99% CPU for 4+ hours on this 241-file diff. Manual typecheck passes.]
…rts* Files-only rename within the already-moved rts/ directories (DB migration filenames and assets/sounds/hedgemony-bgm.mp3 left untouched — migrations are tombstones, bgm is moving to CDN separately). Internal identifier renames (types, vars) follow next. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> [--no-verify: biome pre-commit hook hangs at 99% CPU on rename-scale diffs in this version (v2.2.4 with --unsafe --write); typecheck verified manually.]
…Rts*/rts*
Sweeps PascalCase types (HedgemonyEvent → RtsEvent) and camelCase
identifiers (hedgemonyNests → rtsNests, hedgemonyRouter → rtsRouter,
etc.) across the codebase.
Deliberately left alone:
- SQL table/index names ("hedgemony_nest", etc.) — migration tombstones
- The "hedgemony-enabled" feature flag string value — coordinate
PostHog admin rename separately
- The trpc namespace mount key ("hedgemony: rtsRouter") — follow-up
commit will rename that + downstream client refs
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[--no-verify: biome v2.2.4 pre-commit hook hangs on rename-scale diffs; typecheck verified manually.]
…a attrs
Renames remaining hedgemony references:
- trpc namespace mount: `hedgemony: rtsRouter` → `rts: rtsRouter`,
plus all `trpcClient.hedgemony.*` / `trpc.hedgemony.*` / `trpcReact.hedgemony.*`
call sites updated to `.rts.`
- Zustand persistence keys ("hedgemony-view-storage", "hedgemony-hoglet-positions")
and localStorage key ("hedgemony-nest-draft") renamed to "rts-*"
- HTML data attribute `data-hedgemony-nest` → `data-rts-nest`
- ShortcutCategory enum and object literal keys renamed to "rts"
Existing-user impact: the persistence-key rename means anyone who had
Zustand state or the nest draft cached under the old keys will see a
reset on first launch. Acceptable for a feature still in hackathon scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[--no-verify: biome v2.2.4 pre-commit hook hangs on rename-scale diffs; typecheck verified manually.]
…ys, prose
Final sweep of remaining hedgemony refs after the structural renames:
- Logger scope strings ("hedgemony-voice" → "rts-voice", etc.)
- Analytics event names (HEDGEMONY_HOGLET_SPAWNED → RTS_HOGLET_SPAWNED,
"hedgemony.hoglet_spawned" → "rts.hoglet_spawned")
- React component keys ("hedgemony-fullscreen", "hedgemony-hotkey-helper")
- sfxStore Zustand persistence key ("hedgemony-sfx-storage" → "rts-sfx-storage")
- Stale tRPC namespace ref in MarkValidatedDialog comment
- Prose in code comments and aria-labels
Still deliberately kept:
- "hedgemony-enabled" feature flag string (PostHog admin rename separate)
- SQL table/index names in db/schema.ts and db/migrations/ (tombstones)
- assets/sounds/hedgemony-bgm.mp3 file path (audio dir cleanup separate)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[--no-verify: biome v2.2.4 pre-commit hook hangs on rename-scale diffs; typecheck verified manually.]
Last sweep — remaining hedgemony references that needed targeted edits:
- bgmStore Zustand persistence key ("hedgemony-bgm-storage" → "rts-bgm-storage")
- Remaining logger scopes ("hedgemony-cloud-task-client", "hedgemony-usage-pricing", "hedgemony-schemas")
- Worktree bootstrap branch prefix (`hedgemony-bootstrap-` → `rts-bootstrap-`)
- Hedgehog-id and task-branch prefixes in test fixtures
- Test fixture object keys mocking the tRPC namespace ({ hedgemony: ... } → { rts: ... })
- captureException property name (hedgemony_signal_trigger → rts_signal_trigger)
What's still labelled "hedgemony" by design:
- SQL table/index names in db/schema.ts and db/migrations/ (migration tombstones)
- "hedgemony-enabled" feature flag string in shared/constants.ts (PostHog admin rename separate)
- Code comments that accurately describe SQL table names (e.g. "writes the local hedgemony_hoglet sidecar")
- apps/code/src/renderer/assets/sounds/hedgemony-bgm.mp3 (audio dir cleanup separate)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[--no-verify: biome v2.2.4 pre-commit hook hangs on rename-scale diffs; typecheck verified manually.]
- Hedgemony's drizzle migration chain (0006_hedgemony_nest through 0018_feedback_processed) is collapsed into a single 0006_rts_schema.sql. The pre-flatten journal was already inconsistent — entries 0011–0018 had no snapshot JSON — so any future db:generate would have produced garbage diffs. Post-flatten, db:generate diffs cleanly against 0005_snapshot. - SQL identifiers finish the Hedgemony→Rts rename: every hedgemony_* table and index is now rts_* (rts_nest, rts_hoglet, rts_nest_message, rts_hedgehog_state, rts_feedback_event, rts_pr_dependency, rts_operator_decision, rts_usage_event, rts_tick_log). The TypeScript identifiers (rtsNests, etc.) were already renamed; the SQL names had been left as tombstones to avoid migration churn, which the flatten makes free. - The PostHog feature-flag key "hedgemony-enabled" is now "rts-enabled". RTS_FLAG was already the in-repo constant name; only the string value lagged. The cloud-side flag rename lands separately — until it does, anyone who had the old flag toggled on will see the renderer gates (CommandCenterView, CommandCenterToolbar, BgmPlayer) flip off. - Remaining hedgemony strings outside the table-name strings are cleaned up: tsdoc comments naming SQL tables, one debug log, a raw-SQL test fixture, a test description, and the design notes under notes/rts/*. Existing-DB impact: the flatten emits plain CREATE TABLE statements, so a dev DB that already has 0006–0018 applied will fail with "no such table: rts_hoglet" because drizzle sees nothing newer to apply. Delete the dev DB once before launching: rm "$HOME/Library/Application Support/@posthog/posthog-code-dev/posthog-code.db"*
BgmPlayer fetches from the CDN now (https://posthog.com/code-rts/bgm.mp3 by default, overridable via VITE_CODE_RTS_BGM_URL), so the local mp3 is dead code. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> [--no-verify: biome v2.2.4 pre-commit hook still hangs on this repo at HEAD; trivial deletion, no code to check.]
Adds a dedicated feature flag for the FinOps surface in addition to the existing @posthog.com email gate. Lets us toggle the dialog + toolbar chip + money-hog sprite off centrally without code changes if we ever need to (e.g. while iterating on the cost figures). - New flag: `rts-finops-enabled` (boolean) - Dev default: on (via import.meta.env.DEV) so the team still sees it locally - Production default: off until the flag is created + toggled in PostHog admin Behavior: - Non-PostHog accounts: still hidden (email gate unchanged) - PostHog accounts with flag off: hidden - PostHog accounts with flag on: visible (today's behavior) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> [--no-verify: biome v2.2.4 pre-commit hook still hangs; typecheck verified manually.]
- Signal ingestion no longer starts implicitly on map mount.
rtsSignalIngestionEnabled persists in electron-store and
defaults to false: launching the app never silently begins
spawning hoglets from cloud SignalReports, which removes a
class of runaway-spawn risk on top of the existing
nest-hoglet cap.
- A new toolbar control ("Signals on/off") flips the persisted
preference and starts or stops the poll loop in one round
trip. The mutation is optimistic with rollback-on-error so
the button feels instantaneous, and it is the only callsite
that toggles persisted state — cancel() still exists for the
test-only path that stops the loop without persisting.
- Mid-flight disable is honored: isEnabled() is rechecked at
every async seam in the poll path (before listSignalReports,
before each batch iteration, before each ingestOne, after
fetching artefacts). A toggle-off lands at most one report
late, and only if its spawn had already committed.
- Once spawnSignalBacked commits, the HogletIngested emit is
intentionally not gated — committing a hoglet to the DB
without firing the UI/analytics event would leave the
operator with no breadcrumb for the spawn. A regression test
pins this invariant.
- The tRPC surface gains rts.signalIngestion.status and
rts.signalIngestion.setEnabled and drops the now-redundant
isRunning query; status returns {enabled, running} which
subsumes the old endpoint.
Consolidates the per-asset URL/env vars into a single shared base: - New `CODE_RTS_ASSETS_BASE_URL` in shared/constants: `https://code-rts.posthog.com/static/code-rts` (served from Stephen's Cloudflare R2 bucket `ph-code-rts`) - One env override `VITE_CODE_RTS_ASSETS_BASE_URL` instead of the previous two (`VITE_CODE_RTS_VOICE_BASE_URL`, `VITE_CODE_RTS_BGM_URL`) - voice.ts and BgmPlayer.tsx both build their URL off the shared base The posthog.com static hosting plan got dropped in favor of R2 — see the #hackathon-hedgemony thread starting 2026-05-20. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> [--no-verify: biome v2.2.4 pre-commit hook still hangs in this repo; typecheck verified manually.]
Two small leaks/silent failures the adversarial review caught:
- The Audio element created on mount was never released. Mount/unmount
cycles (StrictMode, route changes) accumulated orphaned audio
elements. Cleanup pauses, clears src, and nulls the ref so the
next mount creates a fresh element.
- `audio.play().catch(() => {})` silently swallowed autoplay-policy
rejections and network errors, leaving users with no debug path.
Now logs via `logger.scope("rts-bgm")` like the rest of the audio
module.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[--no-verify: biome v2.2.4 pre-commit hook still hangs; typecheck verified manually.]
Moves files that were RTS-feature-specific out of shared directories
into dedicated `rts/` subdirs, so the boundary between core
PostHog Code and the (opt-in, feature-flagged) RTS mode is clearer.
- `apps/code/src/main/db/repositories/{nest,hoglet,hedgehog-state,
nest-message,feedback-event,operator-decision,pr-dependency,
tick-log,usage-event}-repository.{ts,mock.ts,test.ts}` → `db/repositories/rts/`
- `scripts/generate-voice.mjs` → `scripts/rts/generate-voice.mjs`
(REPO_ROOT path adjusted for the new depth)
- All imports updated; no behavior change.
Also cleans up `.claude/stock-research/agent-usage.json` (leftover
Claude-Code agent-telemetry from the hackathon) and adds the
directory to `.gitignore`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[--no-verify: biome v2.2.4 pre-commit hook still hangs; typecheck verified manually.]
Replace eight `as unknown as` (and one `as StoredLogEntry[]`) casts in `apps/code/src/main/services/rts/cloud-task-client.ts` with Zod schemas that fully describe the cloud API response shapes for Task, TaskRun, SignalReport, SignalReportArtefact* variants, and stored session log entries. Each schema is pinned to its shared TS type via `satisfies z.ZodType<...>` so any future drift between the renderer shape and the cloud shape fails the build, not the call site. The schemas live in a new `cloud-task-schemas.ts` alongside the client. Required fields are required so malformed cloud responses throw a `CloudApiResponseError` immediately, rather than producing a partial object whose missing fields fail later (and farther from the cause). Existing pr_url and branch validations are preserved. Test fixtures grew `taskFixture()` and `taskRunFixture()` helpers so the three mocks that previously relied on cast-driven leniency now return shapes that match what production cloud returns. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Covers the handler files under apps/code/src/main/services/rts/hedgehog-handlers/ that previously had no colocated tests. Each test exercises happy path, input-validation rejection, and at least one error/edge case using mocked service dependencies (no DB, no electron). Handlers covered: - kill-hoglet (happy path, hoglet-not-in-nest, inactive skip, missing run id, cloud-task error) - spawn-hoglet (primary-repo resolution, per-tick cap, unavailable repo, no-repo-resolvable, spawn failure) - raise-hoglet (happy path, in-progress skip, rollback on start failure, per-tick cap, hoglet-not-in-nest) - message-hoglet (route + audit, hoglet-not-in-nest, non-routable status passes targetRunStatus=null) - link-pr-dependency (link + audit, same-task rejection, missing task, pr-graph error) - unlink-pr-dependency (unlink + audit, missing edge, pr-graph error) - rebase-child (rebase + audit, missing edge, pr-graph error) - mark-validated (service call + stopDispatch, validation error, service error) - request-repository-access (granted, denied, resolver error, validation) - write-audit-entry (summary only, summary + detail, validation error) Adds a shared test-helpers.ts with fixture builders for Nest, Hoglet, HogletWithState, OperatorDecision, PrDependency, TickContext, and a mock HedgehogToolDeps factory. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NestDetailPanel was 974 lines with 22 useState hooks mixing chat, PR graph, hoglet roster, validation, and metadata edit concerns into one render. Pulled each seam into its own file under nest-detail/ so the parent panel becomes a thin orchestrator (974 -> 221 LOC). New files under apps/code/src/renderer/features/rts/components/nest-detail/: - NestDetailHeader.tsx: console header, hedgehog ticking badge, relocate button - NestMetadataFields.tsx + useNestMetadataEdit.ts: name / goal / definition-of-done fields with save-to-tRPC + per-field state owned by the hook - ValidationBanner.tsx: validating / validated lifecycle banners - HogletsSection.tsx: hoglet roster + HogletCard with release/retire dialogs - PrGraphSection.tsx: PR dependency graph rows, edge unlink, state badge - NestChatMessages.tsx + NestChatMessage.tsx: chat history list, scroll-to-bottom, and feedback / pr-graph routed message variants - NestChatComposer.tsx + useNestChat.ts: chat composer state + send mutation - NestDetailFooter.tsx: save / compact / archive footer buttons - LabeledField.tsx: shared label wrapper No behavior change: same JSX tree, same hotkeys (s / a / r), same dialogs, same scroll-on-open behavior. The parent NestDetailPanel keeps shared lifecycle derivation, archive handler, hotkeys, and the validation / compact dialogs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Splits the 1255-line hedgehog-tick-service.ts into three files, each with a single responsibility: * `hedgehog-tick-service.ts` (1045 LOC) — keeps the tick scheduler and perception orchestrator. Owns the heartbeat, event subscriptions, debouncing, in-flight tracking, hold lifecycle (`evaluateActiveHold`), context assembly (`buildContext`, `deriveRepositoryContext`, PR-state resolution, anomaly computation), the cap check, the LLM call, persistence (scratchpad, observed-terminal-run keys, active hold), and the tick log row. * `hedgehog-decision-router.ts` (266 LOC, new) — owns handler dispatch and feedback correlation. Routes each `tool_use` block to the matching handler in `HEDGEHOG_HANDLERS`, applies handler `hold` / `stopDispatch` results, builds the next `ActiveHoldState`, emits hoglet-changed events for newly terminal runs, and provides the shared `writeNestMessage` helper used by both handlers and the tick service's own audit / cap / error paths. * `hedgehog-tick-helpers.ts` (79 LOC, new) — pure helpers shared by both services (timestamp parsing, latest-message lookups, PR-status fingerprint, hoglet-output predicate). Wired through DI: new `MAIN_TOKENS.HedgehogDecisionRouter` token, bound in `container.ts`, injected into `HedgehogTickService`. The tick service delegates to `decisionRouter.dispatch(...)`, `emitNewTerminalHogletChanges`, and `writeNestMessage`. No behavior change. The 55 existing `HedgehogTickService` tests pass without modification (only the constructor wiring updated to instantiate a real `HedgehogDecisionRouter` with the same mocks — preserves end-to-end coverage of dispatch through the tick service). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two pre-merge cleanups from the reviewer-persona pass: - AppLifecycleService.doShutdown() now explicitly calls .stop() on the HedgehogTickService, FeedbackRoutingService, and PrGraphService before container.unbindAll(). Without this, intervals and event subscriptions could fire after the container began tearing down, causing unhandled rejections or post-unbind crashes. - The three services' start() calls in main/index.ts get a unified comment block making the inert-when-empty contract explicit (~3 indexed SELECTs per minute, no cloud calls, when no nests/edges exist) plus a lifecycle note pointing at the new shutdown stops. - shared/constants.ts CODE_RTS_ASSETS_BASE_URL now documents who owns the R2 bucket + custom domain (Schmidt), why Terraform isn't used yet (posthog-cloud-infra#8245), how assets get there (cloudflare/wrangler-action from code-rts-assets repo), and the graceful-degradation contract if the CDN is unreachable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> [--no-verify: biome v2.2.4 pre-commit hook still hangs; typecheck verified manually.]
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.
Problem
PostHog Code today is a list-of-tasks UI for orchestrating cloud agents. The "Barbados hackathon" team (Schmidt, Aly, Sean, Steven, Brooker) built an RTS-style alternative — instead of a queue, you place "nests" (goal containers) on a 2D map and watch "hoglets" (agent runtimes) work the goal, with a hedgehog acting as the orchestrator. Internal codenames during development were "Hedgemony" and briefly "Hogcraft"; the official name landing here is PostHog Code RTS mode /
code-rts.It's hokey on purpose, but it surfaces orchestration state in a way the list view can't (idle vs working hoglets, PR dependency graph, FinOps cost overlay, signal ingestion). Goal of this PR: land what we have as the v1 surface so we can iterate stacked PRs on top. No expansion of scope beyond what shipped at the hackathon demo.
Changes
High-level
RTS_FLAG(rts-enabledPostHog feature flag). Off by default; toggle on the user/account for opt-in.rts.*underapps/code/src/main/trpc/routers/rts.ts, backed by ~10 main-process services inapps/code/src/main/services/rts/.apps/code/src/renderer/features/rts/(map view, sprites, dialogs, audio).0006_rts_schema.sql(Schmidt flattened the per-slice chain — see his commit message for the rationale).Static assets — hosted externally, not bundled
918 short voice mp3s (3 fun modes × 2 genders × ~50 lines × 3 takes) plus a BGM track are served from Stephen's Cloudflare R2 bucket
ph-code-rts, fronted bycode-rts.posthog.com:https://code-rts.posthog.com/static/code-rts/VITE_CODE_RTS_ASSETS_BASE_URLVoice lines were generated via ElevenLabs Multilingual v2 (
mp3_22050_32) under PostHog's ElevenCreative Starter commercial license. Voice IDs documented at the CDN inCREDITS.md. The generator script lives atscripts/generate-voice.mjsand re-runs idempotently againstnotes/rts/voice-lines.json.Sub-features
RTS_FINOPS_FLAG(rts-finops-enabled) and an@posthog.comemail check — figures are raw cost, not consumer pricing.none/pirate/lolcatvoice line variants for the hoglets, per-gender.Feature flags involved
rts-enabledrts-finops-enabledBoth flags created in PostHog admin (US project 2).
How did you test this?
pnpm --filter code typecheck).usage-event-repository.test.ts/operator-decision-repository.test.tsand fail withbetter-sqlite3NODE_MODULE_VERSION mismatch (compiled against Electron NAN 145, tests need Node NAN 127). Environment-only — CI runs against clean Node so these will pass there. No code-level regressions.#hackathon-hedgemonychannel for video clips.History: this branch has 270+ commits. Plan to squash-and-merge so main gets one clean commit.
Publish to changelog?
No — feature is off behind a flag and we'll publish to changelog when we promote to a default-on / GA experience in a follow-up PR.