<feed xmlns='http://www.w3.org/2005/Atom'>
<title>claudomator.git/internal/api, branch main</title>
<subtitle>claudomator — task automation server
</subtitle>
<id>https://git.terst.org/claudomator.git/atom?h=main</id>
<link rel='self' href='https://git.terst.org/claudomator.git/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/'/>
<updated>2026-05-13T04:02:20+00:00</updated>
<entry>
<title>merge: integrate github/main — LocalRunner, real GeminiRunner, llm client</title>
<updated>2026-05-13T04:02:20+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-05-13T04:02:20+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=68399a598924775a3ec22a39c2336ae497fb07f3'/>
<id>urn:sha1:68399a598924775a3ec22a39c2336ae497fb07f3</id>
<content type='text'>
Merges 12 commits from github/main (formerly master) that were developed
independently. Key additions:
- LocalRunner: OpenAI-compatible local LLM execution (Ollama, LM Studio)
- Real GeminiRunner with full sandbox parity to ClaudeRunner
- llm.Client for enriching CI failures and elaboration via local model
- retry.ParseRetryAfter moved to shared package
- tokens_in/tokens_out columns in executions table

Conflict resolutions:
- Kept local main's VAPID/push, stories, projects, agent events schema
- Merged both sets of Config fields (local + LocalModel from github/main)
- Unified activePerAgent accounting (decActiveAgent helper)
- Removed duplicate helpers from claude.go (now in helpers.go)
- Fixed double-decrement bug in handleRunResult vs decActiveAgent

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix: prevent SHIPPABLE stories and wrong READY state on failed tasks</title>
<updated>2026-05-03T17:58:25+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-04-04T21:59:58+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=25cf4c9d4d6f3c18ee7565bf8e6172896fff00c3'/>
<id>urn:sha1:25cf4c9d4d6f3c18ee7565bf8e6172896fff00c3</id>
<content type='text'>
Three related bugs fixed:

1. maybeUnblockParent: guard against promoting QUEUED leaf tasks (no
   subtasks) to READY. The vacuously-true 'all subtasks done' check was
   advancing tasks that stalled in QUEUED (due to a prior SQLite lock
   error) to READY on server restart via RecoverStaleBlocked, despite
   having only failed executions and no commits.

2. checkStoryCompletion: require COMPLETED (not just READY) for all
   top-level tasks before advancing a story to SHIPPABLE. READY means
   the checker agent is still pending or the task awaits human review;
   a story with READY tasks is not ready to ship.

3. handleAcceptTask: call CheckStoryCompletion after a task is accepted
   so stories with parent tasks (whose subtasks are all done and then
   the parent is manually accepted) can auto-advance to SHIPPABLE.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>chore: close deferred work — real GeminiRunner, Local UI option, db.go cleanup</title>
<updated>2026-05-03T08:00:20+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-05-03T08:00:20+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=e7b382bf177cbe518af3d86c3ee6c49344d225f4'/>
<id>urn:sha1:e7b382bf177cbe518af3d86c3ee6c49344d225f4</id>
<content type='text'>
Closes the three items left on the deferred queue after the post-epic
cleanup.

GeminiRunner.execOnce now actually executes the gemini binary instead
of writing hardcoded stream data. Mirrors ClaudeRunner.execOnce:
- exec.CommandContext with the same env vars (CLAUDOMATOR_API_URL etc.)
- process group SIGKILL on context cancel
- stdout piped through parseGeminiStream → stdoutFile
- stderr to file
- exit codes captured, stderr tail surfaced on failure

Test infrastructure bug uncovered in passing: testServerWithGeminiMockRunner's
mock script used double-quoted echo with literal triple-backticks, which
bash interpreted as command substitution. The script always produced
empty output. The bug was invisible until now because GeminiRunner
ignored the script entirely. Switched to a single-quoted heredoc.

Frontend: index.html dropdown gains a "Local" option. No JS branching
needed — the value flows through to agent.type verbatim and downstream
display reads the type string as-is.

storage/db.go: removed stale debug-comment scaffolding (the "TODO:
Replace with proper logger" block) that was tracking a dead
`fmt.Printf` call. The path it commented on is fine without logging —
unmarshal errors are returned wrapped.

Test status: `go test -race ./...` green across every package, zero
skips, zero excluded tests.

https://claude.ai/code/session_017Edeq947TpSm1vQTxMhi1J
</content>
</entry>
<entry>
<title>chore: post-epic cleanup — green test suite, no skips</title>
<updated>2026-05-03T03:58:19+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-05-03T03:58:19+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=85c3bf4d28b0903a2005356339e6ea56855b8c80'/>
<id>urn:sha1:85c3bf4d28b0903a2005356339e6ea56855b8c80</id>
<content type='text'>
Addresses the cleanup queue captured in docs/plans/local-oss-runner.md
after the local-OSS-models epic landed. After this commit
`go test -race ./...` is green across every package with zero `t.Skip`
calls and no excluded tests.

Real bugs fixed:
- claude.go setupSandbox callsites used `sandboxDir, err := ...` which
  shadowed the outer variable, so BlockedError.SandboxDir was always
  empty. Resume-after-block was broken for both new and stale-sandbox
  paths. TestBlockedError_IncludesSandboxDir now exercises the right
  invariant.
- TestPool_ActivePerAgent_DeletesZeroEntries flake under -race: the
  cleanup defer in execute()/executeResume() runs AFTER
  handleRunResult sends on resultCh, so consumers observing a result
  could see a still-counted activePerAgent entry. Extracted
  decActiveAgent(agentType, *cleaned) helper; called explicitly before
  every resultCh send, defer becomes a no-op via the cleaned flag.
  Verified clean over `go test -race -count=10`.

Test infrastructure made hermetic:
- gitSafe now also passes -c commit.gpgsign=false / -c tag.gpgsign=false
  so sandbox tests pass on hosts whose global config requires signing.
- Bare repos in tests initialized with `-b main` (HEAD symbolic ref
  matched to the branch we push) so `git log` after push works.
- TestSandboxCloneSource_FallsBackToOrigin uses a local-FS origin URL,
  matching sandboxCloneSource's intentional filter against network URLs.
- TestGeminiLogs_ParsedCorrectly URL fixed to the actual log route
  (/api/executions/{id}/log).

GeminiRunner gap closed (partial):
- parseGeminiStream now walks lines for `result` events, surfacing
  is_error as an error and total_cost_usd as the float return value.
- GeminiRunner.Run propagates parsed cost to Execution.CostUSD.
- TestParseGeminiStream_ParsesStructuredOutput unskipped.

Notes:
- GeminiRunner is still simulated end-to-end (Run writes hardcoded
  stream data instead of execing the binary). The result/cost parser
  now exists; finishing the runner is a smaller, contained follow-up.
  Kept on the deferred queue.
- Frontend "Local" agent option and a minor storage.db.go logger TODO
  remain on the deferred queue, both intentionally — neither blocks
  anything in flight.

https://claude.ai/code/session_017Edeq947TpSm1vQTxMhi1J
</content>
</entry>
<entry>
<title>feat(api): enrich CI failure task instructions via local LLM</title>
<updated>2026-05-02T07:54:51+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-05-02T07:54:51+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=6c5762848f4f3114a6ece9ce0bc70a84fca040ce'/>
<id>urn:sha1:6c5762848f4f3114a6ece9ce0bc70a84fca040ce</id>
<content type='text'>
Phase 3 of "local OSS models as agents" plan. When the webhook handler
creates a task for a failed CI run AND a local LLM is configured on
the server, the hardcoded 4-step investigation template is replaced
with a project-aware investigation plan generated by the LLM.

Scope adjustment from the original sketch: the original plan said
"summarize fetched workflow logs", but fetching logs requires GitHub
API auth that isn't wired. Narrowed to project-context triage —
recent git log + CLAUDE.md content + webhook metadata, fed to the
LLM with a system prompt asking for 6-12 lines of concrete next
steps. Deferred GitHub log fetching to post-epic cleanup.

Implementation:
- New internal/api/webhook_llm.go holds enrichCIInstructions and its
  helpers (readRecentCommits via `git log`, readProjectDoc).
- enrichCIInstructions is truly additive: any failure mode (no client,
  HTTP error, empty body, 10s timeout) returns the original fallback
  template unchanged. Existing webhook tests pass byte-for-byte.
- Always preserves a metadata header (repo/branch/SHA/check/URL)
  ahead of the LLM body so investigators don't lose context if the
  LLM is terse.
- Reuses s.llm (set via Server.SetLLM in Phase 2) — no new config
  knob, no per-feature gating. Asymmetric opt-out (yes-elaborate,
  no-CI-triage) deferred until there's actual demand.

Tests:
- enrichCIInstructions: nil client, LLM 500, empty body all return
  fallback unchanged.
- enrichCIInstructions: success path produces enriched body with
  metadata header preserved; user prompt contains repo/branch/SHA.
- enrichCIInstructions: real git repo (init + 2 commits) → recent
  commits appear in user prompt.
- Webhook handler regression guard: no-LLM path produces the exact
  legacy template substrings.
- Webhook handler with LLM stubbed: task instructions contain LLM
  body + metadata header.

Plan: docs/plans/local-oss-runner.md.

https://claude.ai/code/session_017Edeq947TpSm1vQTxMhi1J
</content>
</entry>
<entry>
<title>feat(api): route elaboration through local LLM when configured</title>
<updated>2026-04-28T17:10:27+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-04-28T17:10:27+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=ae833b2765c7c8086bf8e1ea8e8ec8ee9b73e656'/>
<id>urn:sha1:ae833b2765c7c8086bf8e1ea8e8ec8ee9b73e656</id>
<content type='text'>
Phase 2 of "local OSS models as agents" plan. Adds a third elaboration
path that calls the local OpenAI-compatible LLM via the internal/llm
client, and reorders dispatch so the cheap path is tried first:
local → claude → gemini, with each next attempt only on hard failure
of the prior.

Wiring is opt-out, not opt-in: when [local_model].endpoint is set,
elaboration prefers local by default. Users with a slow or low-quality
local model can disable just elaboration via:

    [local_model]
    endpoint = "..."
    prefer_for_elaborate = false

without giving up the runner or the classifier path.

Implementation:
- Server gains an optional *llm.Client field via SetLLM (matches the
  existing SetNotifier/SetWorkspaceRoot setter pattern, no NewServer
  signature break).
- elaborateWithLocal() reuses buildElaboratePrompt verbatim and asks
  for response_format=json_object so we skip markdown-fence cleanup.
- handleElaborateTask reorders try chain; existing Claude-first
  behavior is preserved exactly when SetLLM is not called.
- LocalModel.UseForElaborate() encapsulates the default-true gating
  with a *bool so explicit-false survives TOML parse.

Tests:
- elaborateWithLocal: parses valid response, errors on nil client,
  errors on bad JSON.
- handler: local preferred when wired; falls back to claude when
  local fails; unchanged behavior when no LLM is configured.
- config: UseForElaborate gating across empty/default/explicit-true/
  explicit-false cases.

Pre-existing test failures noted in docs/plans/local-oss-runner.md
(post-epic cleanup): TestGeminiLogs_ParsedCorrectly returns 404 for
gemini execution log fetch — predates this change.

Plan: docs/plans/local-oss-runner.md.

https://claude.ai/code/session_017Edeq947TpSm1vQTxMhi1J
</content>
</entry>
<entry>
<title>fix: tie pool submissions to server lifecycle context</title>
<updated>2026-04-11T18:26:42+00:00</updated>
<author>
<name>Claude Agent</name>
<email>agent@claudomator</email>
</author>
<published>2026-04-11T18:26:42+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=49cdcd70f275c858b0511a2e88ab30a48b157fa3'/>
<id>urn:sha1:49cdcd70f275c858b0511a2e88ab30a48b157fa3</id>
<content type='text'>
Fix 1 (server.go): Replace context.Background() with s.ctx in
handleAnswerQuestion, handleResumeTimedOutTask, and handleRunTask.
Add a ctx field to Server (defaulting to context.Background()) and
a SetContext method so the serve command can wire in the signal-
cancellable lifecycle context.

Fix 2 (serve.go): Call srv.SetContext(ctx) before StartHub so all
pool submissions use the server's root context (already cancelled
on SIGTERM/SIGINT). Pool.Shutdown and its wiring were already present.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>cleanup: remove dead code (QuestionRegistry, changestats wrappers, scanner.Err)</title>
<updated>2026-04-11T18:10:32+00:00</updated>
<author>
<name>Claudomator Agent</name>
<email>agent@claudomator</email>
</author>
<published>2026-04-11T18:10:32+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=e94573bb84874eda7d233cafc36f3a21688c0568'/>
<id>urn:sha1:e94573bb84874eda7d233cafc36f3a21688c0568</id>
<content type='text'>
Fix 1: Remove QuestionRegistry and related types (QuestionHandler, PendingQuestion)
from question.go -- nothing reads Pool.Questions or uses the registry. Remove
NewQuestionRegistry() call from NewPool and the Questions field from Pool.
Remove the now-superfluous registry tests; keep stream/parse helpers which are
still used by the claude runner.

Fix 2: Check scanner.Err() after the parseStream loop so I/O errors from the
scanner are not silently swallowed when streamErr is still nil.

Fix 3: Delete internal/api/changestats.go -- the parseChangestatFromFile and
parseChangestatFromOutput wrappers were only needed to support processResult(),
which no longer calls them; they are unreachable dead code.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat: acceptance_criteria per story task in elaboration and approval</title>
<updated>2026-04-04T09:36:56+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-04-04T09:36:56+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=70e90275c0a08649c314cae5280521bcd29272e6'/>
<id>urn:sha1:70e90275c0a08649c314cae5280521bcd29272e6</id>
<content type='text'>
Add AcceptanceCriteria field to elaboratedStoryTask struct, update
buildStoryElaboratePrompt schema and rules, pass the value through
handleApproveStory into task.Task, and add a test verifying the
field is persisted on approved story tasks.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat: story ship gate — explicit POST /api/stories/{id}/ship; remove auto-deploy</title>
<updated>2026-04-04T09:30:13+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-04-04T09:30:13+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=940a5bab031bfe81cea9c90d64e6ebc804c366f9'/>
<id>urn:sha1:940a5bab031bfe81cea9c90d64e6ebc804c366f9</id>
<content type='text'>
- checkStoryCompletion now guards against re-running on already-SHIPPABLE stories
  and no longer auto-triggers triggerStoryDeploy on completion
- New Pool.ShipStory method validates SHIPPABLE state then fires triggerStoryDeploy
- POST /api/stories/{id}/ship route registered and handleShipStory handler added
- Two new tests: 202 for SHIPPABLE story, 409 for non-SHIPPABLE story

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
