Commit graph

1054 commits

Author SHA1 Message Date
de6fa038d1
Dev (#13)
* 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>

* 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>

* 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>

* 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>

* 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>

* 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>

* 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>

* 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>

* 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>

* 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>

* 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>

* 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>

* feat: WithName for explicit service naming

Co-Authored-By: Virgil <virgil@lethean.io>

* test: lifecycle + HandleIPCEvents end-to-end via WithService

Co-Authored-By: Virgil <virgil@lethean.io>

* fix: WithServiceLock enables, New() applies after all opts — v0.3.3 parity

Co-Authored-By: Virgil <virgil@lethean.io>

* feat: MustServiceFor[T] + fix service names test for auto-registered cli

Co-Authored-By: Virgil <virgil@lethean.io>

* 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>

* 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>

* 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>

* fix: Service() returns instance, ServiceFor uses type assertion directly

Co-Authored-By: Virgil <virgil@lethean.io>

* feat: Core.Run() — ServiceStartup → Cli → ServiceShutdown lifecycle

Co-Authored-By: Virgil <virgil@lethean.io>

* feat: Core.Run() handles os.Exit on error

Co-Authored-By: Virgil <virgil@lethean.io>

* feat: New() returns *Core directly — no Result wrapper needed

Co-Authored-By: Virgil <virgil@lethean.io>

* 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>

* 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>

---------

Co-authored-by: Virgil <virgil@lethean.io>
Co-authored-by: Virgil <virgil@lthn.ai>
2026-03-24 23:54:08 +00:00
a6be0df3ea dev (#38)
Some checks failed
CI / test (push) Failing after 2s
Co-authored-by: Snider <snider@host.uk.com>
Co-authored-by: Virgil <virgil@lthn.ai>
Reviewed-on: #38
2026-03-24 23:26:43 +00:00
1450179b9c Merge pull request 'feat: inline tests + Fs zero-value fix + coverage 76.9% → 82.3%' (#27) from dev into main
Some checks failed
CI / test (push) Failing after 3s
Reviewed-on: #27
2026-03-23 18:03:56 +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
Snider
2d52b83f60 docs: rewrite documentation suite against AX spec
Codex-authored docs covering primitives, commands, messaging,
lifecycle, subsystems, and getting started — all using the current
DTO/Options/Result API with concrete usage examples.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-21 10:05:04 +00:00
df1576b101
Merge pull request #11 from dAppCore/dev
fix: strip module prefix from coverage paths for Codecov
2026-03-21 09:17:10 +00:00
Snider
954cd714a1 fix: strip module prefix from coverage paths for Codecov
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-21 09:16:24 +00:00
76f8ae41b9
Merge pull request #10 from dAppCore/dev
fix: add Codecov token to CI workflow
2026-03-21 09:13:42 +00:00
Snider
b01b7f4d88 fix: add Codecov token to CI workflow
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-21 09:12:39 +00:00
397ec2cec5
Merge pull request #9 from dAppCore/dev
docs: rewrite README + add CI/Codecov
2026-03-21 09:00:56 +00:00
Snider
01135ac8bd docs: rewrite README + add CI workflow with Codecov
README reflects current API — DI framework, not the old CLI/GUI app.
CI runs tests with coverage on push to main.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-21 09:00:13 +00:00
3bf175b3d1
Merge pull request #8 from dAppCore/dev
refactor: flatten polyglot layout to standard Go module
2026-03-21 08:27:52 +00:00
Snider
fbd646456a refactor: flatten polyglot layout to standard Go module
Move source from go/core/ to root, tests from go/tests/ to tests/.
Module path dappco.re/go/core resolves cleanly — builds and tests pass.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-21 08:27:14 +00:00
3642a819f1
Merge pull request #7 from dAppCore/dev
go update
2026-03-21 06:26:52 +00:00
Snider
2fa8b32db2 go update 2026-03-21 06:25:59 +00:00
8de5e20ab5
Merge pull request #6 from dAppCore/dev
go update
2026-03-20 21:36:22 +00:00
Snider
3e507c9813 go update 2026-03-20 21:35:22 +00:00
6942a019cb
Merge pull request #5 from dAppCore/dev
go update
2026-03-20 21:23:40 +00:00
Snider
104416676b go update 2026-03-20 21:14:38 +00:00
dcf677309d
Merge pull request #4 from dAppCore/dev
go update
2026-03-20 21:06:05 +00:00
Snider
b34899ca00 go update 2026-03-20 21:00:48 +00:00
d6dada1461
Merge pull request #3 from dAppCore/dev
chore: module path update
2026-03-20 20:43:16 +00:00
Snider
1728c2930c refactor: update imports from forge.lthn.ai/core/go to dappco.re/go/core
All .go imports, test fixtures, and embed.go code generation updated
to match the new module path.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 20:16:38 +00:00
Snider
41c50da68b go update 2026-03-20 19:58:41 +00:00
cee07f05dd
Merge pull request #2 from dAppCore/dev
feat: AX audit + Codex review — polish pass
2026-03-20 18:52:43 +00:00
Snider
73eed891ca fix: CodeRabbit re-review — 3 findings resolved
- cli: dispatch through Start for lifecycle-backed commands
- command: reject empty/malformed path segments
- error: fix typo CauseorJoin → ErrorJoin in doc comment

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 18:48:02 +00:00
Snider
af6b618196 fix: CodeRabbit review — 7 findings resolved
- cli: preserve explicit empty flag values (--name=)
- cli: skip placeholder commands in help output
- command: fail fast on non-executable placeholder Run
- command: lifecycle-backed commands count as registered
- runtime: wrap non-error OnStop payloads in error
- fs: error on protected path deletion (was silent Result{})
- error: log crash report I/O failures instead of swallowing

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 18:36:30 +00:00
Snider
e17217a630 refactor: camelCase — waitgroup → waitGroup
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 18:03:31 +00:00
Snider
d5f295cb7d refactor: AX naming — wg → waitgroup, ctx → context
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 18:02:07 +00:00
Snider
bde8d4c7cc feat: lifecycle context — Core.Context() for cooperative shutdown
- Core holds context.Context + CancelFunc
- New() creates background context
- ServiceStartup creates fresh context from caller's ctx (restart safe)
- ServiceShutdown cancels context before draining tasks
- c.Context() accessor lets task handlers check Done() for graceful exit

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 17:59:43 +00:00
Snider
629adb056b fix: lifecycle — clear shutdown flag on startup, document waiter goroutine
- ServiceStartup clears c.shutdown so Core supports restart cycles
- ServiceShutdown waiter goroutine documented as inherent to sync.WaitGroup

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 17:56:21 +00:00
Snider
61b034335a fix: Codex review round 4 — panic recovery, subtree preservation
- PerformAsync: defer/recover wraps task execution, broadcasts error on panic
- Command: preserve existing subtree when overwriting placeholder parent

Remaining known architectural:
- fs.go TOCTOU (needs openat/fd-based ops)
- Global lockMap (needs per-Core registry)
- ServiceShutdown goroutine on timeout (inherent to wg.Wait)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 17:52:48 +00:00
Snider
ee9e715243 fix: Codex review round 3 — 5 remaining findings
- Command: allow overwriting auto-created parent placeholders
- NewWithFactories: wrap original factory error cause
- I18n.SetTranslator: reapply saved locale to new translator
- Options/Drive: copy slices on intake (prevent aliasing)
- Embed.path: returns Result, rejects traversal with error

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 17:46:47 +00:00
Snider
bf1f8e51ad fix: Codex review round 2 — path traversal, shutdown order, races
High:
- embed.Extract: safePath validates all rendered paths stay under targetDir
- embed.path: reject .. traversal on arbitrary fs.FS
- ServiceShutdown: drain background tasks BEFORE stopping services

Medium:
- cli.Run: command lookup holds registry RLock (race fix)
- NewWithFactories: propagate factory/registration failures

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 17:35:09 +00:00
Snider
4c3a671b48 fix: Codex review — medium/low severity issues resolved
Medium:
- Config zero-value safe (nil ConfigOptions guards on all mutators)
- ServiceShutdown collects and returns first OnStop error
- Default logger uses atomic.Pointer (race fix)
- Command registration rejects duplicates (like Service)

Low:
- Array.AsSlice returns copy, not backing slice
- fs.validatePath constructs error on sandbox escape (was nil)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 17:25:12 +00:00
Snider
f1bd36db2e fix(critical): Codex review — 7 high-severity issues resolved
Critical:
- Result.Result() zero args returns receiver instead of panicking

High:
- i18n.SetLanguage: added mutex, forwards to translator
- embed.GetAsset: hold RLock through assets map read (race fix)
- cli.PrintHelp: safe type assertion on Translate result
- task.PerformAsync: guard nil task in reflect.TypeOf
- Service/Command registries initialised in New() (race fix)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 17:20:08 +00:00
Snider
bc06480b58 fix: AX audit round 7 — Err.Err renamed to Err.Cause
Remaining 32 Rule 1 violations are valid but not P0:
- Subsystem accessors returning typed pointers (fluent API)
- Error creators returning error (should return Result)
- Void fire-and-forget operations (Must, Progress, Log)
- Iterator returning iter.Seq (should use modern Go patterns)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 16:54:27 +00:00
Snider
2f39e8e1f4 fix: AX audit round 6 — Result returns, naming, literal style
- Data.Get, Drive.Get → Result (was typed pointers)
- I18n.Translator, I18n.Locales → Result
- Translator interface: Translate returns Result
- Array.Filter → Result, Core.Embed → Result
- Embed.BaseDir → BaseDirectory
- TaskState.ID → Identifier, SetTaskIdentifier method fix
- fs.go: Result{nil, true} → Result{OK: true} (5 lines)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 16:46:39 +00:00
Snider
298322ed89 fix: AX audit round 5 — full naming, Result returns throughout
Renames (via GoLand refactor):
- Option.K → Key, Option.V → Value
- Err.Op → Operation, Err.Msg → Message, Err.Err → Error
- CrashSystem.OS → OperatingSystem, Arch → Architecture
- TaskID → TaskIdentifier, TaskWithID → TaskWithIdentifier
- Ipc → IPC, BaseDir → BaseDirectory
- ServiceRuntime.Opts → Options

Return type changes:
- Options.Get, Config.Get → Result (was (any, bool))
- Embed.ReadDir → Result (was ([]fs.DirEntry, error))
- Translator.Translate, I18n.Translate → Result (was string)

Rule 6:
- data.go: propagate opts.Get failure, typed error for bad fs.FS

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 16:32:43 +00:00
Snider
cf25af1a13 fix: AX audit round 4 — semantic naming, Result returns
- Op → Operation, AllOps → AllOperations (no abbreviations)
- Translator.T → Translator.Translate (avoids testing.T confusion)
- Lock.Mu → Lock.Mutex, ServiceRuntime.Opts → .Options
- ErrorLog.Error/Warn return Result instead of error
- ErrorPanic.Reports returns Result instead of ([]CrashReport, error)
- Core.LogError/LogWarn simplified to passthrough

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 16:00:41 +00:00
Snider
b2d0deb99b fix: AX audit round 3 — 8 violations resolved
- core.go: Result{Value: wrapped} → Result{wrapped, false} (explicit failure)
- error.go: fmt.Sprint → Sprint wrapper, removed fmt import
- fs.go: Stat/Open propagate validatePath failures (return vp)
- lock.go: Startables/Stoppables return Result
- task.go: PerformAsync returns Result
- runtime.go: updated to unwrap Result from Startables/Stoppables

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 15:49:33 +00:00
Snider
8801e2ea10 fix: final AX audit — 9 remaining violations resolved
- fs.go: propagate validatePath failures (return vp) instead of bare Result{}
- app.go: Find() returns Result instead of *App
- log.go: fmt import removed — uses Sprint/Sprintf/Print from string.go/utils.go
- string.go: added Sprint() and Sprintf() wrappers for any-to-string formatting

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