Commit graph

130 commits

Author SHA1 Message Date
Snider
507bf55eb5 test(agentic): add scan_test.go — Forge issue scanning with mock API
Tests scan tool with mockScanServer (org repos, issue listing, dedup),
listRepoIssues (assignee extraction, URL rewriting, error handling).
11 tests covering filtering, limits, labels, and deduplication.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 23:31:07 +00:00
Snider
0008e269e4 test(agentic): add ingest_test.go — findings ingestion and issue creation tests
Tests ingestFindings pipeline (completed/not-completed/no-log/quota-exhausted),
createIssueViaAPI with mock Brain API, and security-specific countFileRefs cases.
13 tests covering the full ingest flow and edge cases.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 23:30:58 +00:00
Snider
ce682e42fe test(agentic): add verify_test.go — PR merge, labels, and verification tests
Tests forgeMergePR, ensureLabel, getLabelID, runVerification, flagForReview,
autoVerifyAndMerge, fileExists, truncate via mock Forge API.
33 tests covering merge success/conflict/error, label CRUD, and project detection.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 23:30:50 +00:00
Snider
805be3bf32 test(agentic): add epic_test.go — Forge API integration tests with httptest
Tests createIssue, resolveLabelIDs, createLabel, createEpic via mock Forge.
Shared mockForgeServer and newTestSubsystem helpers for reuse.
19 tests covering success, validation, and error paths.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 23:30:44 +00:00
Snider
f2b4eeb0fa test(agentic): add mirror_test.go — git helper integration tests
Tests hasRemote, commitsAhead, filesChanged with real temp git repos.
Tests extractJSONField, DefaultBranch, listLocalRepos, GitHubOrg.
35 tests using _Good/_Bad/_Ugly naming convention.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 23:30:38 +00:00
Snider
bd2eadc271 test(monitor): add logic_test.go — 26 tests for uncovered functions
Covers SetCore, handleAgentStarted, handleAgentCompleted, checkIdleAfterDelay,
countLiveWorkspaces, pidAlive, OnStartup, OnShutdown, and Register using
_Good/_Bad/_Ugly naming convention. Coverage: 76.1% → 84.2%.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 23:12:32 +00:00
Snider
42e558ed38 test(agentic): add queue/status/plan/register test files
Adds four new test files covering previously untested functions:
- queue_logic_test.go: countRunningByModel, drainQueue, Poke, StartRunner, DefaultBranch, LocalFs
- status_logic_test.go: ReadStatus/writeStatus field coverage + WorkspaceStatus JSON round-trip
- plan_logic_test.go: planPath sanitisation + readPlan/writePlan round-trips with phases
- register_test.go: Register (service discovery, core wiring, config loading), OnStartup, OnShutdown

All 56 new tests follow _Good/_Bad/_Ugly convention and pass with go test ./pkg/agentic/...

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 23:07:02 +00:00
Snider
80b827b7c8 test(agentic): add logic_test.go — 66 tests for 10 pure functions
Covers agentCommand, containerCommand, buildAutoPRBody, emitEvent,
countFileRefs, modelVariant, baseAgent, resolveWorkspace,
findWorkspaceByPR, and extractPRNumber with _Good/_Bad/_Ugly cases.
All 66 pass. Uses t.TempDir() + t.Setenv("CORE_WORKSPACE") for
filesystem-dependent tests.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:59:42 +00:00
Snider
60b0b0b63b fix: test uses core.New() directly — no .Value unwrap
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 21:53:45 +00:00
Snider
67658ec90c fix: imports forge.lthn.ai/core/mcp → dappco.re/go/mcp
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 21:40:04 +00:00
Snider
909bb3bb6c feat: services own their commands — main.go is 132 lines
Commands moved to their owning services:
- agentic: run/task, run/orchestrator, prep, status, prompt, extract
- mcp: mcp, serve (in core/mcp OnStartup)
- main.go: version, check, env (app-level only)

ServiceStartup before Cli().Run() — services register commands in OnStartup.
ServiceShutdown on exit.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 21:24:29 +00:00
Snider
4803f396d3 feat: use mcp.Register — MCP is a Core service
- core.New() includes mcp.Register — auto-discovers subsystems
- mcp/serve commands use c.Service("mcp") for typed retrieval
- ServiceStartup called once before Cli().Run()
- run/task and run/orchestrator registered by agentic.OnStartup
- Removed ServiceFor generics — c.Service() returns instances directly

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 21:16:20 +00:00
Snider
919dcf963f feat: factories return instances — WithService handles registration
Register factories no longer call c.RegisterService() explicitly.
WithService auto-discovers name from package path and registers.
Eliminates double-registration error.

Uses WithOption("name", "core-agent") for Options struct.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 20:36:17 +00:00
Snider
adafee0f18 feat: use RegisterService + ServiceFor — proper instance lifecycle
- agentic.PrepSubsystem implements Startable/Stoppable
- monitor.Subsystem implements Startable/Stoppable (OnStartup/OnShutdown)
- Register factories use c.RegisterService() — auto-discovers interfaces
- Register factories return instances via Result.Value
- main.go uses ServiceFor[T]() instead of ConfigGet — typed retrieval
- No more c.Config().Set("x.instance") workaround

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 17:42:16 +00:00
Snider
d9e7fa092b feat: complete DI migration — IPC pipeline + Config + Locks
Phase 4 complete:
- Auto-PR handler emits PRCreated message
- Verify handler listens for PRCreated, emits PRMerged/PRNeedsReview
- findWorkspaceByPR() for workspace lookup from PR events
- Remove legacy inline fallback from dispatch goroutine

Phase 5 complete:
- agents.yaml loaded once at startup into c.Config()
- canDispatchAgent reads from c.Config() (no re-parsing)
- drainQueue uses c.Lock("drain") when Core available

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 16:44:19 +00:00
Snider
4e69daf2da feat: replace initServices() with core.New() service conclave
Services are now registered during Core construction:
  core.New(
      core.WithService(agentic.Register),
      core.WithService(monitor.Register),
      core.WithService(brain.Register),
  )

- Remove initServices() closure — services created once in conclave
- Commands use c.ServiceStartup()/c.ServiceShutdown() for lifecycle
- Service instances retrieved via c.Config() for MCP tool registration
- run/orchestrator reduced to ServiceStartup + block + ServiceShutdown
- run/task uses conclave's agentic instance

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 16:33:04 +00:00
Snider
9f8a63ae21 feat: Register() factories + consume new core.New() API
Each package exposes Register(c *Core) Result for core.WithService():
- agentic.Register: creates PrepSubsystem, wires IPC handlers, lifecycle
- monitor.Register: creates Subsystem, wires IPC handler, lifecycle
- brain.Register: creates Direct, registers service

main.go updated for core.New() returning Result.
Ready for core.New(WithService(agentic.Register)) pattern.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 16:23:44 +00:00
Snider
cdb29a2f75 feat(ipc): replace CompletionNotifier callbacks with Core IPC messages
Phase 3 of Core DI migration:
- Remove CompletionNotifier interface from pkg/agentic
- dispatch.go emits messages.AgentStarted/AgentCompleted via c.ACTION()
- monitor registers IPC handlers in SetCore() — handleAgentStarted/handleAgentCompleted
- Remove circular callback wiring (SetCompletionNotifier) from main.go
- Export ReadStatus for cross-package use
- Update run/orchestrator to use SetCore() instead of SetCompletionNotifier()

Services now communicate through typed messages, not direct references.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 14:46:59 +00:00
Snider
bb88604045 feat(core): wire Core framework into agentic + monitor subsystems
Phase 2 of Core DI migration:
- Add *core.Core field + SetCore() to PrepSubsystem and monitor.Subsystem
- Register agentic/monitor/brain as Core services with lifecycle hooks
- Mark SetCompletionNotifier and SetNotifier as deprecated (removed in Phase 3)
- Fix monitor test to match actual event names
- initServices() now wires Core refs before legacy callbacks

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 14:44:53 +00:00
Snider
781a5b414e feat(messages): define IPC message types for inter-service communication
12 message types covering: agent lifecycle (Started/Completed),
QA+PR pipeline (QAResult/PRCreated/PRMerged/PRNeedsReview),
queue orchestration (QueueDrained/PokeQueue/RateLimitDetected),
monitor events (HarvestComplete/HarvestRejected/InboxMessage).

These replace the CompletionNotifier and ChannelNotifier callback
interfaces with typed broadcast messages via c.ACTION().

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 14:44:23 +00:00
Snider
53acf4000d feat(concurrency): nested per-model limits under agent pools
Concurrency config now supports both flat and nested formats:

  claude: 1                    # flat — 1 total
  codex:                       # nested — 2 total, per-model caps
    total: 2
    gpt-5.4: 1
    gpt-5.3-codex-spark: 1

canDispatchAgent checks pool total first, then per-model limit.
countRunningByModel added for exact agent string matching.
ConcurrencyLimit custom YAML unmarshaler handles both int and map.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 13:05:41 +00:00
Snider
04e3d492e9 fix(monitor): emit agent.completed per task, verify PIDs for queue.drained
- Export ReadStatus (was readStatus) for cross-package use
- AgentCompleted now emits agent.completed with repo/agent/workspace/status
  for every finished task, not just failures
- queue.drained only fires when genuinely empty — verified by checking
  PIDs are alive via kill(0), not just trusting stale status files
- Fix Docker mount paths: /root/ → /home/dev/ for non-root container
- Update all callers and tests

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 13:02:41 +00:00
Snider
9bdd47d9d5 feat(agent): v0.3.0 — dispatch control, run task CLI, quiet notifications, spark pool
- Add agentic_dispatch_start / shutdown / shutdown_now MCP tools
- Queue frozen by default, CORE_AGENT_DISPATCH=1 to auto-start
- Add run task CLI command — single task e2e (prep → spawn → wait)
- Add DispatchSync for blocking dispatch without MCP
- Quiet notifications — only agent.failed and queue.drained events
- Remove duplicate notification paths (direct callback + polling loop)
- codex-spark gets separate concurrency pool (baseAgent routing)
- Rate-limit backoff detection (3 fast failures → 30min pause)
- Review agent uses exec with sandbox bypass (not codex review)
- Bump: core-agent 0.3.0, core plugin 0.15.0, devops plugin 0.2.0

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-23 16:08:08 +00:00
Snider
6d4b92737e feat(agent): background runner, slim status, Docker dispatch, stopwatch, CLI fixes
- Add background queue runner (runner.go) — 30s tick + poke on completion
- drainQueue now loops to fill all slots per tick
- Add run orchestrator command — standalone queue runner without MCP
- Slim agentic_status — stats only, blocked workspaces listed
- Docker containerised dispatch — all agents run in core-dev container
- Forge stopwatch start/stop on issue when agent starts/completes
- issue create supports --milestone, --assignee, --ref
- Auto-PR targets dev branch (not main)
- PR body includes Closes #N for issue-linked work
- CLI usage strings use spaces not slashes
- Review agent uses exec with sandbox bypass (not codex review subcommand)
- Local model support via codex --oss with socat Ollama proxy

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-23 12:53:33 +00:00
Snider
9b225af2f9 refactor(monitor): direct event push, no filesystem polling
CompletionNotifier interface now has AgentStarted() and
AgentCompleted() instead of Poke(). Dispatch pushes notifications
directly to monitor with agent/repo/status data. Monitor pushes
MCP channel events immediately — no scanning, no dedup maps,
no filesystem polling latency.

Events.jsonl kept as audit log only, not notification mechanism.
Timer-based scan kept for startup seeding and stale detection.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 16:19:13 +00:00
Snider
c04d3442dd fix(monitor): use full workspace path as dedup key
filepath.Base gave just "main" — every branch-based workspace
collided in seenCompleted map. Now uses relative path like
"core/go/main" for unique deduplication.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 16:11:38 +00:00
Snider
a3ca68e2bd fix(status): scan new workspace layout for agentic_status
Status MCP tool only scanned one level deep (*/status.json).
New workspaces at */*/*/status.json were invisible. Now scans
both layouts. Also removed unused os/filepath imports.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 16:03:11 +00:00
Snider
c484eab2fc fix(monitor): restore sync helpers lost in rebase
monitorAPIURL, monitorBrainKey, monitorHomeDir, monitorPath were
added by Codex on remote dev but lost when we force-pushed after
rebase conflict. Restored with simplified Core.Env() usage.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 15:52:10 +00:00
Snider
39255b1766 fix(queue): scan new workspace layout for concurrency counting
countRunningByAgent only scanned one level deep (*/status.json)
but new workspaces are at */*/*/status.json. Concurrency limits
weren't enforced — all agents dispatched as running instead of
queuing. Now scans both old and new layouts.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 15:45:32 +00:00
Snider
24fdaa3bad fix: extract default workspace template for go.work
QA gate failed because cloned repos weren't in a Go workspace.
Extract default template (including go.work with use ./repo)
into workspace dir before cloning. Fixes go test isolation.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 15:45:32 +00:00
Snider
4bcc04d890 feat(monitor): agent.started + agent.complete channel notifications
- emitStartEvent fires when agent spawns (dispatch.go)
- Monitor detects new "running" workspaces and pushes agent.started
  channel notification with repo and agent info
- agent.complete already included blocked/failed status — no change
- Both old and new workspace layouts supported

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 15:45:32 +00:00
Snider
abe5ef342a fix(monitor): scan new workspace layout for channel notifications
workspaceStatusPaths scans both old (*/status.json) and new
(*/*/*/status.json) workspace layouts. Fixes agent.complete
channel notifications not firing for org/repo/identifier paths.

Replaces filepath.Glob with core.PathGlob in harvest.go.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 15:45:32 +00:00
Snider
69f0acce0d fix(dispatch): model flag splice broke -o argument ordering
When model variant was specified, the splice inserted --model
between -o and its value, making Codex see -o with no file path.
Fixed by appending --model and prompt sequentially instead of
splicing into the middle of the args slice.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 15:45:16 +00:00
Snider
c73ea6ad7a fix(dispatch): strip model variant from log filename
Agent name like "codex:gpt-5.3-codex-spark" contains a colon which
breaks file paths. Use base name (before ":") for the log file.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 15:45:16 +00:00
Snider
d6864eb37c feat(dispatch): QA gate before auto-PR
After agent completes, run build + vet + test before creating PR.
If QA fails, mark workspace as failed with "QA check failed" —
bad code never gets PR'd.

Supports Go (build/vet/test), PHP (composer install/test),
and Node (npm install/test). Unknown languages pass through.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 15:45:16 +00:00
Snider
7f84ac8348 refactor(agentic): use go-forge library instead of raw HTTP
Replace raw http.Client calls with go-forge typed API:
- prep.go: getIssueBody via forge.Issues.Get, pullWikiContent
  via forge.Wiki.ListPages/GetPage
- pr.go: forgeCreatePR via forge.Pulls.Create, commentOnIssue
  via forge.Issues.CreateComment, listRepoPRs via forge.Pulls.ListAll
- scan.go: listOrgRepos via forge.Repos.ListOrgRepos

Eliminates manual JSON marshalling, auth headers, pagination loops,
and anonymous struct declarations. One Forge client, one auth,
type-safe responses.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 15:45:16 +00:00
Snider
6e03287178 refactor(agentic): workspace = clone, prompt replaces files
Major simplification of the dispatch model:
- Workspace dir: .core/workspace/{org}/{repo}/{pr|task|branch|tag}/
- Clone into repo/ (not src/), metadata in .meta/
- One of issue, pr, branch, or tag required for dispatch
- All context (brain, consumers, git log, wiki, plan) assembled
  into prompt string — no TODO.md, PROMPT.md, CONTEXT.md files
- Resume detection: skip clone if repo/.git exists
- Default agent changed to codex
- spawnAgent drops srcDir param, runs from repo/
- No --skip-git-repo-check (repo/ IS a git repo)
- All downstream files: srcDir → repoDir

Track PRs, not workspace iterations.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 15:45:16 +00:00
Snider
6e37bd22f0 feat: devops plugin, CLI commands, Codex dispatch fixes, AX sweep
DevOps plugin (5 skills):
- install-core-agent, repair-core-agent, merge-workspace,
  update-deps, clean-workspaces

CLI commands: version, check, extract for diagnostics.

Codex dispatch: --skip-git-repo-check, removed broken
--model-reasoning-effort, --sandbox workspace-write via
--full-auto. Workspace template extracts to wsDir not srcDir.

AX sweep (Codex-generated): sanitise.go extracted from prep/plan,
mirror.go JSON parsing via encoding/json, setup/config.go URL
parsing via net/url, strings/fmt imports eliminated from setup.

CODEX.md template updated with Env/Path patterns.
Review workspace template with audit-only PROMPT.md.
Marketplace updated with devops plugin.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 15:45:16 +00:00
Snider
98baa1b08f feat: inline tests + Fs zero-value fix + coverage 76.9% → 82.3%
Move all tests from tests/ to package root for proper coverage.
Fix Fs zero-value: path() and validatePath() default empty root
to "/" so &Fs{} works without New().

New tests: PathGlob, PathIsAbs, CleanPath, Cli.SetOutput,
ServiceShutdown, Core.Context, Fs zero-value, Fs protected
delete, Command lifecycle with implementation, error formatting
branches, PerformAsync completion/no-handler/after-shutdown,
Extract with templates, Embed path traversal.

Coverage: 76.9% → 82.3% (23 test files, 82 new test cases).

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 15:45:04 +00:00
7f87e4d52c Merge pull request '[agent/codex] Fix ALL high-severity AX findings in pkg/monitor/ (monitor.g...' (#10) from agent/fix-all-high-severity-ax-findings-in-pkg into dev 2026-03-22 13:41:29 +00:00
Snider
9aac4c19dd refactor(monitor): adopt core helpers
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 13:40:14 +00:00
Snider
ed842122a2 fix(brain): resolve direct AX findings
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 13:02:37 +00:00
Snider
a422eb1b6b feat(lib): add go.work.tmpl to workspace template
Codex agents are sandboxed to src/ and don't get ~/Code/go.work.
This template creates a go.work with `use .` so the Go toolchain
works in workspace mode inside the sandbox.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 10:18:57 +00:00
Snider
6393bfe4da refactor(agentic): adopt core.Env() + core.Path() across package
Replace all os.UserHomeDir/os.Getenv/os.Hostname with core.Env().
Replace all filepath.Base/Dir/Glob/IsAbs with core.PathBase/PathDir/
PathGlob/PathIsAbs.

10 files migrated: paths, prep, review_queue, remote, dispatch,
ingest, mirror, plan, verify, watch.

Imports eliminated: 5x os, 7x filepath. All file I/O and path
construction now routes through Core primitives.

Bumps dappco.re/go/core to v0.6.0.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 10:15:15 +00:00
Snider
3022f05fb8 refactor(agentic): route file I/O through core.Fs
Replace raw os.* file operations with Core Fs equivalents:
- os.Stat → fs.Exists/fs.IsFile/fs.IsDir (resume, pr, plan, mirror, prep)
- os.ReadDir → fs.List (queue, status, plan, mirror, review_queue)
- os.Remove → fs.Delete (dispatch)
- os.OpenFile(append) → fs.Append (events, review_queue)
- strings.Replace → core.Replace (scan)

Eliminates os import from resume.go, pr.go. Eliminates strings
import from scan.go. Trades os for io in events.go.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:45 +00:00
Snider
ede5d6f561 feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
  List functions, ExtractWorkspace (Good/Bad patterns)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
Snider
3c5e6d6498 fix(lib): ExtractWorkspace now recurses into subdirectories
Was skipping directories entirely (`if entry.IsDir() { continue }`),
so .core/reference/ and its contents were never extracted.
Replaced fs.ReadDir loop with fs.WalkDir to handle nested dirs.

Added tests: CreatesFiles, CreatesSubdirectories, TemplateSubstitution.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 08:10:56 +00:00
Snider
e8ca0d856f feat(lib): embed Core documentation in workspace template
18 doc files from core/go/docs — getting-started, primitives,
services, commands, configuration, errors, lifecycle, messaging,
subsystems, testing. Agents can read full Core documentation
without network access.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 07:19:20 +00:00
Snider
5417f547ca feat(lib): add Core struct reference to CODEX.md template
Documents all subsystem accessors (App, Fs, Config, Data, Drive, Log,
Error, Cli, IPC, I18n, Options, Context), service lifecycle with
Startable/Stoppable interfaces, error logging (LogError/LogWarn/Must),
and async tasks (Perform/PerformAsync/Progress).

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 07:17:27 +00:00
Snider
71ee6124b7 feat(lib): complete CODEX.md with all Core patterns
Added: Embed/Mount/Extract, error wrapping/codes/introspection,
Print, Array[T], Config, IPC (Action/Query/Perform), Services,
Commands, Drive, I18n. Every Core primitive documented with
WRONG/CORRECT examples.

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