Commit graph

1092 commits

Author SHA1 Message Date
Snider
b130309c3d feat(rfc): resolve Issues 10+11 — Array[T] and ConfigVar[T] as guardrail primitives
Issue 10 (Resolved): Array[T] is a guardrail primitive, not speculative.
Same role as string helpers — forces single codepath, model-proof,
scannable. Ordered counterpart to Registry[T].

Issue 11 (Resolved): ConfigVar[T] promoted to documented primitive.
Solves "was this explicitly set?" tracking for layered config.
Typed counterpart to Option (which is any-typed).

Both follow the guardrail pattern: the primitive exists not because
Go can't do it, but because weaker models (Gemini, Codex) will
reinvent it inline every time — badly. One import, one pattern.

Added primitive taxonomy table showing the full picture:
strings→core.Contains, paths→core.JoinPath, errors→core.E,
maps→Registry[T], slices→Array[T], config→ConfigVar[T]

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 12:22:10 +00:00
Snider
79fd8c4760 feat(rfc): Section 20 — c.Registry() universal collection primitive
Registry[T] is the brick that all named collections build on:
- map[string]T + mutex + optional locking
- Set/Get/Has/Names/List/Each/Lock/Delete
- c.Registry("services"), c.Registry("actions"), c.Registry("drives")

Resolves Issues 6 + 12:
- serviceRegistry/commandRegistry become exported, embed Registry[T]
- IPC is safe to expose — reads from registry, doesn't own write path
- Registration goes through c.Action(), locking through c.Registry().Lock()

Typed accessors (c.Service, c.Action, c.Drive) are sugar over
c.Registry(name).Get(). Universal query layer on top.

Replaces 5 separate map+mutex+lock implementations with one primitive.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 12:13:19 +00:00
Snider
5211d97d66 feat(rfc): resolve Issue 16 — task.go splits into ipc.go + action.go
task.go has 6 functions mixing registration and execution:
- RegisterAction/RegisterActions/RegisterTask → ipc.go (registry)
- Perform/PerformAsync/Progress → action.go (execution)

contract.go message types (ActionTaskStarted etc) stay — naming
already correct per Issue 1 convention.

Added AX principles 8-9:
8. Naming encodes architecture (CamelCase=brick, UPPERCASE=sugar)
9. File = concern (one file, one job)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 12:01:39 +00:00
Snider
68b7530072 feat(rfc): resolve Issues 1+12 — naming convention + IPC as registry owner
Issue 1 (Resolved): UPPERCASE vs CamelCase naming convention:
- CamelCase = primitive (the brick): c.Action(), c.Service(), c.Config()
- UPPERCASE = consumer convenience (sugar): c.ACTION(), c.QUERY(), c.PERFORM()
- Current code has this backwards — ACTION is mapped to raw dispatch

Issue 12 (Resolved): IPC owns the Action registry:
- c.IPC() = owns the data (registry, handlers, task flows)
- c.Action() = primitive API for register/invoke/inspect
- c.ACTION() = convenience shortcut for broadcast
- Three layers, one registry — same pattern as Drive/API

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 11:58:27 +00:00
Snider
7a9f9dfbd1 feat(rfc): Section 19 — c.API() remote stream primitive
HTTP/WebSocket/SSE/MCP are all streams. The transport is irrelevant.

- c.IPC() = local conclave (in-process)
- c.API() = remote streams (cross-machine)
- c.Drive() = connection config (WHERE), c.API() = transport (HOW)
- Protocol handlers register like Actions (permission by registration)
- Remote Action dispatch: "charon:agentic.status" → transparent cross-machine
- Maps current manual HTTP/SSE/MCP code in core/agent to single-line calls
- Full 13-subsystem map documented

Proved by: PHP5 stream lib that replaced curl when certs were broken.
Same principle — depend on stream primitive, not transport library.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 11:46:36 +00:00
Snider
773e9ee015 feat(rfc): Issue 9 — three-layer CLI architecture (Cli/cli/go-process)
CommandLifecycle replaced with Managed field on Command struct.
Three layers read the same declaration:
- core.Cli() — primitive: basic parsing, runs Action
- core/cli — extension: rich help, completion, daemon management UI
- go-process — extension: PID, health, signals, registry

Command struct is data not behaviour. Services declare,
packages consume. Lifecycle verbs become process Actions.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 11:40:35 +00:00
Snider
8f7a1223ef feat(rfc): Known Issues 9-16 — recovered ADHD brain dumps
9.  CommandLifecycle — daemon skeleton, never wired to go-process
10. Array[T] — generic collection used nowhere, speculative
11. ConfigVar[T] — typed config var, only used internally
12. Ipc data-only struct — no methods, misleading accessor
13. Lock() allocates wrapper struct on every call
14. Startables/Stoppables return Result instead of []*Service
15. contract.go comment says New() returns Result (stale)
16. task.go mixes execution + registration concerns

Each issue has: what it is, what the intent was, how it relates
to Sections 17-18, and a proposed resolution. These are the
sliding-context ideas saved in quick implementations that need
the care they deserve.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 11:33:16 +00:00
Snider
76714fa292 feat(rfc): Section 18 — Action and Task execution primitives
Action = named, registered callable. The atomic unit of work.
Task = composition of Actions (chain, parallel, conditional, scheduled).

Key design:
- c.Action("name", handler) registers, c.Action("name").Run() invokes
- Services register actions during OnStartup (same as commands)
- Namespace IS capability map (process.*, agentic.*, brain.*)
- Registration IS permission — no action = no capability
- Current IPC verbs (ACTION/QUERY/PERFORM) become invocation modes
- c.Process() is sugar over process.* actions
- Tasks compose Actions into flows (sequential, parallel, conditional, cron)
- Action registry is queryable — agents inspect capabilities before using

Sections: 18.1-18.10 covering concept, API, registration, permission,
task composition (chain/parallel/conditional/scheduled), IPC mapping,
process integration, and registry inspection.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 11:24:44 +00:00
Snider
ec17e3da07 feat(rfc): Section 17 — c.Process() primitive spec
Full design spec for process management as Core subsystem:
- 17.1: Primitive concept (interface in core/go, impl in go-process)
- 17.2: Process struct + accessor
- 17.3: Sync execution (Run, RunIn, RunWithEnv)
- 17.4: Async execution (Start + ProcessOptions)
- 17.5: ProcessHandle (IsRunning, Kill, Done, Wait, Info)
- 17.6: IPC messages (ProcessRun/Start/Kill + Started/Output/Exited/Killed)
- 17.7: Permission by registration (no handler = no capability)
- 17.8: Per-package convenience helpers
- 17.9: go-process implementation (IPC handler registration)
- 17.10: Migration path (current → target)

Key insight: registration IS permission. Sandboxed Core without
go-process cannot execute external commands. No config, no tokens,
no capability files — the service exists or it doesn't.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 11:17:48 +00:00
Snider
f65884075b feat(rfc): add Design Philosophy + Known Issues to API spec
Design Philosophy:
- Core is Lego Bricks — export primitives, reduce downstream LOC
- Export rules: struct fields yes, mutexes no
- Why core/go is minimal (stdlib-only, layers import downward)

Known Issues (8):
1. Dual IPC naming (ACTION vs Action)
2. MustServiceFor uses panic (contradicts Result pattern)
3. Embed() legacy accessor (dead code)
4. Package-level vs Core-level logging (document boundary)
5. RegisterAction in wrong file (task.go vs ipc.go)
6. serviceRegistry unexported (should be Lego brick)
7. No c.Process() accessor (planned)
8. NewRuntime/NewWithFactories legacy (verify usage)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 11:13:35 +00:00
Snider
1455764e3c feat: add docs/RFC.md — CoreGO API contract specification
Full API spec covering all 16 subsystems: Core container, primitives
(Option/Options/Result), service system, IPC (ACTION/QUERY/PERFORM),
Config, Data, Drive, Fs, CLI, error handling, logging, strings, paths,
utils, locks, ServiceRuntime.

An agent can write a Core service from this document alone.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 11:01:58 +00:00
Snider
e7c3b3a69c feat: add llm.txt — agent entry point for CoreGO framework
Standard llm.txt with package layout, key types, service pattern.
Points to CLAUDE.md and docs/RFC.md for full specs.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 10:52:55 +00:00
f6ed40dfdc Merge pull request 'test: _Bad/_Ugly tests + per-Core lock isolation' (#37) from feat/test-coverage into dev 2026-03-24 22:46:43 +00:00
Snider
d982193ed3 test: add _Bad/_Ugly tests + fix per-Core lock isolation
Tests: Run, RegisterService, ServiceFor, MustServiceFor _Bad/_Ugly variants.
Fix: Lock map is now per-Core instance, not package-level global.
This prevents deadlocks when multiple Core instances exist (e.g. tests).

Coverage: 82.4% → 83.6%

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:44:48 +00:00
5855a6136d Merge pull request 'fix: shutdown context + double IPC registration' (#36) from fix/codex-review-findings into dev 2026-03-24 22:28:42 +00:00
Snider
95076be4b3 fix: shutdown context, double IPC registration
- Run() uses context.Background() for shutdown (c.context is cancelled)
- Stoppable closure uses context.Background() for OnShutdown
- WithService delegates HandleIPCEvents to RegisterService only

Fixes Codex review findings 1, 2, 3.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:28:15 +00:00
f72c5782fd Merge pull request 'feat: restore functional option pattern for New()' (#28) from feat/service-options into dev 2026-03-24 22:09:19 +00:00
Snider
5362a9965c feat: New() returns *Core directly — no Result wrapper needed
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
af1cee244a feat: Core.Run() handles os.Exit on error
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
7608808bb0 feat: Core.Run() — ServiceStartup → Cli → ServiceShutdown lifecycle
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
7f4c4348c0 fix: Service() returns instance, ServiceFor uses type assertion directly
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
9c5cc6ea00 feat: New() constructors for Config, Fs + simplify contract.go init
Config.New() initialises ConfigOptions.
Fs.New(root) sets sandbox root.
ErrorLog uses Default() fallback — no explicit init needed.
contract.go uses constructors instead of struct literals.

All tests green.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
94e1f405fc fix: Result.New handles (value, error) pairs correctly + embed test fixes
Root cause: Result.New didn't mark single-value results as OK=true,
breaking Mount/ReadDir/fs helpers that used Result{}.New(value, err).

Also: data_test.go and embed_test.go updated for Options struct,
doc comments updated across data.go, drive.go, command.go, contract.go.

All tests green. Coverage 82.2%.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
ae4825426f wip: v0.3.3 parity — Tasks 1-7 complete, data/embed tests need fixing
WithService: full name discovery + IPC handler auto-registration via reflect
WithName: explicit service naming
RegisterService: Startable/Stoppable/HandleIPCEvents auto-discovery
MustServiceFor[T]: panics if not found
WithServiceLock: enable/apply split (v0.3.3 parity)
Cli: registered as service via CliRegister, accessed via ServiceFor

@TODO Codex: Fix data_test.go and embed_test.go — embed path resolution
after Options changed from []Option to struct. Mount paths need updating.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
2303c27df0 feat: MustServiceFor[T] + fix service names test for auto-registered cli
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
05d0a64b08 fix: WithServiceLock enables, New() applies after all opts — v0.3.3 parity
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
d1579f678f test: lifecycle + HandleIPCEvents end-to-end via WithService
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
001e90ed13 feat: WithName for explicit service naming
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
b03c1a3a3c feat: WithService with v0.3.3 name discovery + IPC handler auto-registration
WithService now: calls factory, discovers service name from instance's
package path via reflect.TypeOf, discovers HandleIPCEvents method,
calls RegisterService. If factory returns nil Value, assumes self-registered.

Also fixes: Cli() accessor uses ServiceFor, test files updated for Options struct.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
177f73cc99 feat: WithService with v0.3.3 name discovery + IPC handler auto-registration
- WithService now calls factory, discovers service name from package path via
  reflect/runtime (last path segment, _test suffix stripped, lowercased), and
  calls RegisterService — which handles Startable/Stoppable/HandleIPCEvents
- If factory returns nil Value (self-registered), WithService returns OK without
  a second registration
- Add contract_test.go with _Good/_Bad tests covering all three code paths
- Fix core.go Cli() accessor: use ServiceFor[*Cli](c, "cli") (was cli.New())
- Fix pre-existing })) → }}) syntax errors in command_test, service_test, lock_test
- Fix pre-existing Options{...} → NewOptions(...) in core_test, data_test,
  drive_test, i18n_test (Options is a struct, not a slice)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
198ab839a8 wip: checkpoint before v0.3.3 parity rewrite
Cli as service with ServiceRuntime, incomplete.
Need to properly port v0.3.3 service_manager, message_bus,
WithService with full name/IPC discovery.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
f69be963bc feat: Cli.New(c) constructor — Core uses it during construction
Cli{}.New(c) replaces &Cli{core: c} in contract.go.
9 tests passing.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
85faedf6c0 fix: update Cli doc comment + tests for new Options contract
Cli struct unchanged — already conforms.
Tests use WithOption() convenience. 9 tests passing.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
2a81b4f576 feat: App struct with New(Options) + Find() as method
App.New() creates from Options. App.Find() locates programs on PATH.
Both are struct methods — no package-level functions.
8 tests passing.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
a49bc46bc7 feat: Options struct + Result methods + WithOption convenience
Options is now a proper struct with New(), Set(), Get(), typed accessors.
Result gains New(), Result(), Get() methods on the struct.
WithOption("key", value) convenience for core.New().

options_test.go: 22 tests passing against the new contract.
Other test files mechanically updated for compilation.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
74f78c83a2 feat: RegisterService with instance storage + interface discovery
Restores v0.3.3 service manager capabilities:
- RegisterService(name, instance) stores the raw instance
- Auto-discovers Startable/Stoppable interfaces → wires lifecycle
- Auto-discovers HandleIPCEvents → wires to IPC bus
- ServiceFor[T](c, name) for typed instance retrieval
- Service DTO gains Instance field for instance tracking

WithService is a simple factory call — no reflect, no magic.
discoverHandlers removed — RegisterService handles it inline.
No double-registration: IPC wired once at registration time.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
64e6a26ea8 fix: move HandleIPCEvents discovery to New() post-construction
WithService is now a simple factory call — no reflect, no auto-registration.
New() calls discoverHandlers() after all opts run, scanning Config for
service instances that implement HandleIPCEvents.

This eliminates both double-registration and empty-placeholder issues:
- Factories wire their own lifecycle via c.Service()
- HandleIPCEvents discovered once, after all services are registered
- No tension between factory-registered and auto-discovered paths

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
9b5f6df6da fix: prevent double IPC registration + empty service placeholder
- HandleIPCEvents only auto-registered for services the factory didn't
  register itself (prevents double handler registration)
- Auto-discovery only creates Service{} placeholder when factory didn't
  call c.Service() — factories that register themselves keep full lifecycle

Addresses Codex review findings 1 and 2 from third pass.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
2d017980dd fix: address Codex review findings on PR #28
- WithOptions copies the Options slice (constructor isolation regression)
- WithService auto-discovers service name from package path via reflect
- WithService auto-registers HandleIPCEvents if present (v0.3.3 parity)
- Add test for failing option short-circuit in New()

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
9f6caa3c90 Merge pull request '[agent/codex] Review PR #28. Read CLAUDE.md first. Check: 1) API contract ...' (#29) from agent/review-pr--28--read-claude-md-first--che into dev 2026-03-24 16:53:52 +00:00
Snider
c45b22849f feat: restore functional option pattern for New()
New() returns Result, accepts CoreOption functionals.
Restores v0.3.3 service registration contract:
- WithService(factory func(*Core) Result) — service factory receives Core
- WithOptions(Options) — key-value configuration
- WithServiceLock() — immutable after construction

Services registered in New() form the application conclave with
shared IPC access. Each Core instance has its own bus scope.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 16:23:33 +00:00
Snider
927f830be4 merge: resolve main→dev conflict in path_test.go
Keep dev's additional tests (Glob, IsAbs, CleanPath, TrailingSlash)
alongside main's Env/Path helpers.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-23 17:58:05 +00:00
Snider
e0c190ca8f 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).

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 13:30:01 +00:00
fb04b28419 Merge pull request 'fix: CodeRabbit review findings for Env/Path' (#22) from dev into main
Some checks failed
CI / test (push) Failing after 2s
2026-03-22 10:13:15 +00:00
Snider
2312801d43 fix: address CodeRabbit review findings
- TestEnv_DIR_HOME checks CORE_HOME override first
- Path tests use Env("DS") instead of hardcoded "/"
- Path() falls back to "." when DIR_HOME is empty
- Doc comment no longer claims "zero filepath import"

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 10:12:52 +00:00
ce597be0d3 Merge pull request 'feat: core.Env() + core.Path() — system info and OS-aware paths' (#21) from dev into main
Some checks failed
CI / test (push) Failing after 2s
2026-03-22 10:03:26 +00:00
Snider
7e2783dcf5 feat: add core.Path() + core.Env() fallthrough + PathGlob/PathIsAbs/CleanPath
Path() builds OS-aware absolute paths using Env("DS") — single point
of responsibility for filesystem paths. Relative paths anchor to
DIR_HOME. cleanPath resolves .. and double separators.

Env() now falls through to os.Getenv for unknown keys — universal
replacement for os.Getenv. Core keys (OS, DIR_HOME, etc.) take
precedence, arbitrary env vars pass through.

New exports: Path, PathBase, PathDir, PathExt, PathIsAbs, PathGlob,
CleanPath. Info init moved to init() so Path() can be used during
population without init cycle. DIR_HOME respects CORE_HOME env var
override for agent workspace sandboxing.

13 path tests, 17 env tests — all passing.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:50:50 +00:00
Snider
8c2b9c2506 feat: add core.Env() — read-only system information registry
Env is environment, Config is ours. Provides centralised access to
system facts (OS, ARCH, hostname, user, directories, timestamps)
via string key lookup, populated once at package init.

Keys: OS, ARCH, GO, DS, PS, HOSTNAME, USER, PID, NUM_CPU,
DIR_HOME, DIR_CONFIG, DIR_CACHE, DIR_DATA, DIR_TMP, DIR_CWD,
DIR_DOWNLOADS, DIR_CODE, CORE_START.

17 tests covering all keys + unknown key + Core instance accessor.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:26 +00:00
a06b779e3c Merge pull request '[agent/claude] Review the README.md and docs/ directory. Verify all code ex...' (#20) from agent/review-the-readme-md-and-docs--directory into main
Some checks failed
CI / test (push) Failing after 5s
2026-03-21 11:10:43 +00:00
Snider
77780812cf docs: rewrite CLAUDE.md for current API, remove stale AGENTS.md
CLAUDE.md now documents the DTO/Options/Result pattern.
AGENTS.md was a copy of old CLAUDE.md with wrong API patterns.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-21 10:07:05 +00:00