go-scm/docs/history.md
Snider 4558504499 docs: graduate TODO/FINDINGS into production documentation
Replace internal task tracking (TODO.md, FINDINGS.md) with structured
documentation in docs/. Trim CLAUDE.md to agent instructions only.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-20 15:02:22 +00:00

9.4 KiB

go-scm Project History


Origin

go-scm was extracted from forge.lthn.ai/core/go on 19 February 2026 as part of a broader split of the monolithic core/go module into focused, independently versionable packages. The extraction commit is 3e883f69769c4be7401819b2ae3e2122324a666c.

At extraction, the module contained six packages totalling approximately 9,000 LOC with the following test coverage state:

Package LOC (approx) Tests Coverage
forge/ 900 0 0%
gitea/ 500 0 0%
git/ 400 0 0%
agentci/ 300 1 partial (~56%)
jobrunner/ 1,500 several partial
collect/ 5,000 8 unknown (~57%)

The delegation model for development uses a fleet of AI agents (Virgil for architecture/orchestration, Charon for implementation). Initial task queues were written to TODO.md and FINDINGS.md at the time of extraction (bedcb4e65292fb3e2436a7254eca8012ccd5dd7b).


Phase 1: Test Coverage

Commit: 9db37c6fb3628a94727dae61c2fb9704f1924eb4 Additional coverage pushes: b4e3d055, 2505aff4, f50fc405, b3e3ef2e, 8d1d7fce

The critical gap at extraction was zero tests in the three foundational packages. Phase 1 addressed this by bringing all packages to meaningful coverage thresholds.

forge/ — 0% to 91.2% (8 test files)

All SDK wrapper functions were tested via net/http/httptest mock servers. Key findings during test construction:

  • The Forgejo SDK v2 makes an HTTP GET to /api/v1/version during NewClient(). Every test requires a mock server — there is no zero-cost unit test instantiation.
  • SDK route patterns differ from the public API documentation in at least one case: CreateOrgRepo uses the singular /api/v1/org/{name}/repos while ListOrgRepos uses the plural /api/v1/orgs/{name}/repos.
  • SetPRDraft is implemented as a raw HTTP PATCH (not an SDK method) and was testable via the mock server by registering the endpoint directly.

gitea/ — 0% to 89.2% (5 test files)

Mirror of the forge/ approach. Additional finding: CreateMirrorRepo with Service: GitServiceGithub requires a non-empty AuthToken or the SDK rejects the request before sending to the server.

git/ — 0% to 96.7% (4 test files across two rounds)

Required real temporary git repositories (t.TempDir() + git init + git commit). The getAheadBehind function required a bare remote plus a clone to test correctly. Context cancellation was verified by constructing a pre-cancelled context. IsNonFastForward was verified against the specific error string patterns returned by git.

The git.Service struct and its framework integration methods (OnStartup, handleQuery, handleTask) depend on framework.Core and were not unit-tested; they are covered by integration context.

agentci/ — 56% to 94.5%

Tests added for Spinner methods (DeterminePlan, GetVerifierModel, FindByForgejoUser, Weave) and all security functions (SanitizePath, EscapeShellArg, SecureSSHCommand, MaskToken). Notable finding: SanitizePath("../secret") returns "secret" — this is correct behaviour (strip via filepath.Base), not a bug.

collect/ — 57.3% to 83.0% (additional test files)

HTTP-dependent Collect methods were tested using httptest mock servers for BitcoinTalk, CoinGecko, IACR, and arXiv. The gap at extraction was that all HTTP-dependent paths were untested; these were filled in Phase 1 and supplemented in later coverage pushes.

jobrunner/ — 86.4% to 95.0% (forgejo/ source)

The forgejo signal source was already above the 60% floor at extraction. Phase 1 audited but did not change it. Phase 3 added 20 new tests covering signal parsing, epic body edge cases, and Poll error paths.


Phase 2: Hardening

Commit: 3ba8fbb8fba5785fd64b08d5e3673cd502f4ef9f

Error wrapping audit

All 15 bare fmt.Errorf calls (those without a package.Function: prefix) were converted to the project standard. Affected files:

File Errors fixed
jobrunner/journal.go 8
agentci/config.go 3
agentci/security.go 2
jobrunner/handlers/dispatch.go 1
forge/labels.go 1

Existing log.E() and core.E() call sites already followed the pattern and required no changes.

Context propagation audit

The audit confirmed a fundamental SDK limitation: neither the Forgejo SDK v2 nor the Gitea SDK accept context.Context on any API method. Adding context parameters to the 66 forge/ and gitea/ wrapper functions would be ceremony without real propagation. The wrappers accept contexts at their boundaries but cannot pass them through.

Where context propagation was added:

  • agentci/security.goSecureSSHCommandContext(ctx, host, cmd) added; SecureSSHCommand deprecated and delegates to it.
  • jobrunner/handlers/dispatch.gosecureTransfer, runRemote, and ticketExists updated to pass their existing ctx argument through to SecureSSHCommandContext.
  • collect/ratelimit.goCheckGitHubRateLimitCtx(ctx) added alongside the deprecated CheckGitHubRateLimit.

Both git/ and the existing collect/ HTTP collectors already propagated context correctly (exec.CommandContext and http.NewRequestWithContext respectively).

Rate limiting review

The existing collect/ratelimit.go implementation was reviewed and found complete. It handles all current edge cases: context cancellation during Wait, unknown sources (default 500 ms), concurrent access (mutex-protected), and adaptive throttling (auto-pause at 75% GitHub API usage). No changes were required to the rate limiter logic.


Phase 3: AgentCI Pipeline

Commit: 0fe2978b9d1c300a44ac8cf4cbb4ad51e101f6f1

Phase 3 added integration-level test coverage across the three components that form the dispatch pipeline: the Forgejo signal source, the journal, and the handlers.

Forgejo signal source (source_phase3_test.go — 20 tests)

Covered signal parsing with empty bodies, mixed content, and large issue numbers; findLinkedPR edge cases (nil head, empty SHA, matching by issue reference); Poll with combined status failures; unassigned child issues; child fetch failures; multiple epics in one repo; mixed label sets; and the Report method format.

Journal replay (journal_replay_test.go — 8 tests)

Write-then-read-back round-trip verified JSON marshalling and file partitioning. Filter tests covered action name, repo full name, time range, and combined filters. Concurrent write safety was verified with 20 goroutines writing simultaneously. Empty journal reads were verified to return zero entries without error.

Handler integration (integration_test.go — 14 tests)

Full signal-to-result flow tested for all five handlers via a mock Forgejo server and real Poller. Tests verified: tick_parent epic checkbox update and child issue closure; enable_auto_merge on passing/mergeable PRs; publish_draft on resolved threads; send_fix_command on failing checks; completion handler recording. Handler priority matching (first match wins) was also verified.


Known Limitations

SDK context support

The Forgejo SDK v2 and Gitea SDK do not accept context.Context. All Forgejo/Gitea API calls are blocking with no cancellation path. When the SDK is updated to support context (v3 or later), a follow-up task should thread ctx through all forge/ and gitea/ wrapper signatures.

Clotho Weave — byte-equal only

Spinner.Weave(ctx, primary, signed) currently returns string(primaryOutput) == string(signedOutput). This is a placeholder. Meaningful dual-run verification requires semantic diff logic (e.g., normalised AST comparison, embedding cosine similarity, or LLM-assisted diffing). The interface signature is stable; the implementation is not production-ready for divergent outputs.

collect/ HTTP collectors — no retry

None of the HTTP-dependent collectors (bitcointalk.go, github.go, market.go, papers.go) implement retry on transient failures. A single HTTP error causes the collector to return an error and increment the Errors count in the result. The Excavator continues to the next collector. For long-running collection runs, transient network errors cause silent data gaps.

Journal replay — no public API

The journal can be replayed by scanning the JSONL files directly, but there is no exported Query or Filter function. Replay filtering patterns exist only in tests. A future phase should export a query interface.

git.Service framework integration

git.Service.OnStartup, handleQuery, and handleTask are not covered by unit tests because they depend on framework.Core. These code paths are exercised in integration contexts only.

ForgejoSource — no webhook support

ForgejoSource polls on a configurable interval. It does not support webhook push delivery for lower-latency signalling. Webhook delivery would require a separate HTTP server and is tracked as a future phase.


Phase 4: Forge-to-Gitea Sync (Planned)

The following tasks have been identified but not yet implemented:

  • Mirror sync — automated repository mirroring from forge.lthn.ai (private, source of truth) to git.lthn.ai (public mirror). Requires stripping sensitive data (research documents, internal references, credentials).
  • Issue sync — selective issue sync from Forge to Gitea (public issues only).
  • PR status sync — update Gitea mirror PRs when corresponding Forge PRs are merged.

These tasks were deferred from Phase 3 to allow the dispatch pipeline to stabilise first.