Commit graph

982 commits

Author SHA1 Message Date
Snider
c3f457c151 feat: JoinPath helper — joins segments with /
core.JoinPath("deploy", "to", "homelab") → "deploy/to/homelab"
Cli.Run uses it for command path resolution.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 12:23:05 +00:00
Snider
e220b9faab rename: Printl → Print
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 12:20:36 +00:00
Snider
d8ad60ce8a refactor: Printl helper in utils.go — Cli.Print delegates to it
core.Printl(w, format, args...) writes a formatted line to any writer,
defaulting to os.Stdout. Cli.Print() delegates to Printl.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 12:19:11 +00:00
Snider
6687db76f3 refactor: Cli output via Print() — single output path, redirectable
All CLI output goes through Cli.Print() instead of direct fmt calls.
SetOutput() allows redirecting (testing, logging, etc).

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 12:17:30 +00:00
Snider
8854d5c79f feat: utils.go — FilterArgs, ParseFlag with short/long flag rules
- FilterArgs: removes empty strings and Go test runner flags
- ParseFlag: single dash (-v, -🔥) must be 1 char, double dash (--verbose) must be 2+ chars
- Cli.Run() now uses FilterArgs and ParseFlag — no test flag awareness in surface layer
- Invalid flags silently ignored (e.g. -verbose, --v)

221 tests, 79.7% coverage.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 12:15:57 +00:00
Snider
c61a2d3dfe test: 214 tests, 79% coverage — GeneratePack with real files, SetOutput, crash reports
Hit compress/compressFile via GeneratePack with actual asset files on disk.
Added SetOutput log test. Crash report test covers Reports() graceful nil.

Remaining 0%: getAllFiles (group dir scan), appendReport (unexported filePath).
Both are internal plumbing — public API is fully covered.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 12:10:41 +00:00
Snider
afc235796f feat: Command DTO + Cli surface — AX-native CLI primitives
Command is now a DTO with no root/child awareness:
- Path-based registration: c.Command("deploy/to/homelab", handler)
- Description is an i18n key derived from path: cmd.deploy.to.homelab.description
- Lifecycle: Run(), Start(), Stop(), Restart(), Reload(), Signal()
- All return core.Result — errors flow through Core internally
- Parent commands auto-created from path segments

Cli is now a surface layer that reads from Core's command registry:
- Resolves os.Args to command path
- Parses flags into Options (--port=8080 → Option{K:"port", V:"8080"})
- Calls command action with parsed Options
- Banner and help use i18n

Old Clir code preserved in tests/testdata/cli_clir.go.bak for reference.

211 tests, 77.5% coverage.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 12:08:19 +00:00
Snider
b2d07e7883 test: 200 tests, 50.2% coverage — Data, I18n, Fs, Log, Embed, Runtime
New tests: Data List/ListNames/Extract, I18n with mock Translator,
Fs full surface (EnsureDir, IsDir, IsFile, Exists, List, Stat, Open,
Create, Append, ReadStream, WriteStream, Delete, DeleteAll, Rename),
Log all levels + Security + Username + Default + LogErr + LogPan,
Embed ScanAssets + GeneratePack + MountEmbed, Runtime ServiceName,
Core LogError/LogWarn/Must helpers.

Fixes: NewCommand inits flagset, New() wires Cli root command + app.

Remaining 0% (excluding CLI/App): compress, getAllFiles (internal),
Reports/appendReport (needs ErrorPanic filePath), SetOutput (trivial).

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 10:49:33 +00:00
Snider
1ca010e1fb test: rewrite test suite for AX primitives API
164 tests, 41.3% coverage. Tests written against the public API only
(external test package, no _test.go in pkg/core/).

Covers: New(Options), Data, Drive, Config, Service, Error, IPC,
Fs, Cli, Lock, Array, Log, App, Runtime, Task.

Fixes: NewCommand now inits flagset, New() wires Cli root command.

Old tests removed — they referenced With*, RegisterService, and
other patterns that no longer exist.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 08:42:38 +00:00
Snider
f51c748f49 feat: AX primitives — Option/Options/Result, Data, Drive, full names
Core primitives:
- Option{K, V} atom, Options []Option universal input, Result[T] universal return
- Replaces With* functional options, Must*, For[T] patterns
- New(Options) returns *Core (no error — Core handles internally)

New subsystems:
- Data: embedded content mount registry (packages mount assets)
- Drive: transport handle registry stub (API, MCP, SSH, VPN)

Renames (AX principle — predictable names):
- ErrPan → ErrorPanic, ErrLog → ErrorLog, ErrSink → ErrorSink
- srv → service, cfg → config, err → error, emb → legacy accessor
- ErrorOptions/ErrorPanicOptions/NewErrorLog/NewErrorPanic removed
- Contract/ConfigService removed (unused)

RFC-025: Agent Experience updated to match implementation.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-20 08:22:30 +00:00
Snider
7c7a257c19 fix: clone Meta per crash report + sync Reports reads with crashMu
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 13:33:55 +00:00
Snider
4fa90a8294 fix: guard ErrLog against nil Log — falls back to defaultLog
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 13:28:01 +00:00
Snider
ead9ea00e5 fix: resolve CodeRabbit findings — init ordering, crash safety, lock order
- log.go: remove atomic.Pointer — defaultLog init was nil (var runs before init())
- error.go: Reports(n) validates n<=0, appendReport creates parent dir
- contract.go: WithServiceLock is order-independent (LockApply after all opts)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 13:20:30 +00:00
Snider
2406e81c20 fix(critical): RegisterAction infinite recursion + restore missing methods
- core.go: removed self-calling RegisterAction/RegisterActions aliases (stack overflow)
- task.go: restored RegisterAction/RegisterActions implementations
- contract.go: removed WithIO/WithMount (intentional simplification)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 11:05:29 +00:00
Snider
c2227fbb33 feat: complete DTO pattern — struct literals, no constructors
- All New* constructors removed (NewApp, NewIO, NewCoreCli, NewBus, NewService, NewCoreI18n, NewConfig)
- New() uses pure struct literals: &App{}, &Fs{}, &Config{ConfigOpts:}, &Cli{opts:}, &Service{}, &Ipc{}, &I18n{}
- Ipc methods moved to func (c *Core) — Ipc is now a DTO
- LockApply only called from WithServiceLock, not on every New()
- Service map lazy-inits on first write
- CliOpts DTO with Version/Name/Description

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 10:53:13 +00:00
Snider
173067719e fix: resolve Codex review findings — stale comments, constructor patterns
- config.go: comments updated from Cfg/NewEtc to Config/NewConfig
- service.go: comment updated from NewSrv to NewService
- embed.go: comments updated from Emb to Embed
- command.go: panic strings updated from NewSubFunction to NewChildCommandFunction
- fs.go: error ops updated from local.Delete to core.Delete
- core.go: header updated to reflect actual file contents
- contract.go: thin constructors inlined as struct literals (NewConfig, NewService, NewCoreI18n, NewBus)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 09:37:34 +00:00
Snider
2525d10515 fix: resolve Gemini review findings — race conditions and error handling
- error.go: appendReport now mutex-protected, handles JSON errors, uses 0600 perms
- log.go: keyvals slice copied before mutation to prevent caller data races
- log.go: defaultLog uses atomic.Pointer for thread-safe replacement

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 09:20:10 +00:00
Snider
8199727537 feat: restructure Core as unified struct with DTO pattern
Complete architectural overhaul of pkg/core:
- All subsystem types renamed to idiomatic Go (no stutter)
- Core struct: App, Embed, Fs, Config, ErrPan, ErrLog, Cli, Service, Lock, Ipc, I18n
- Exports consolidated in core.go, contracts/options in contract.go
- Service() unified get/register: c.Service(), c.Service("name"), c.Service("name", svc)
- Lock() named mutex map: c.Lock("srv"), c.Lock("ipc")
- Error system: Err/ErrLog/ErrPan + Log/LogErr/LogPan (shared ErrSink interface)
- CoreCommand with optional description (i18n resolves from command path)
- Tests moved to tests/ directory (black-box package core_test)
- Removed: ServiceFor/MustServiceFor, global instance, Display/Workspace/Crypt interfaces
- New files: app.go, fs.go, ipc.go, lock.go, i18n.go, task.go, runtime.go, contract.go

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 09:12:29 +00:00
Snider
bcaf1554f8 fix: resolve 3 critical review findings
C1: mnt_extract.go rename bug — source path was mutated before
    reading from fs.FS. Now uses separate sourcePath/targetPath.

C2: cli_command.go os.Stderr = nil — replaced with
    flags.SetOutput(io.Discard). No more global nil stderr.

C3: Cli() returned nil — now initialised in New() with
    NewCliApp("", "", "").

Found by opus code-reviewer agent (final review pass).

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 02:40:00 +00:00
Snider
3b3b042509 feat: add c.Cli() — zero-dep CLI framework on Core struct
Absorbs leaanthony/clir (1526 lines, 0 deps) into pkg/core:
  cli.go         — NewCliApp constructor
  cli_app.go     — CliApp struct (commands, flags, run)
  cli_action.go  — CliAction type
  cli_command.go — Command (subcommands, flags, help, run)

Any CoreGO package can declare CLI commands without importing
a CLI package:

  c.Cli().NewSubCommand("health", "Check status").Action(func() error {
      return c.Io().Read("status.json")
  })

Uses stdlib flag package only. Zero external dependencies.
core/cli becomes the rich TUI/polish layer on top.

Based on leaanthony/clir — zero-dep CLI, 0 byte go.sum.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 01:43:03 +00:00
Snider
8f2e3d9457 chore: clean up — remove core.go re-export, pkg/mnt, go-io/go-log deps
Removed:
- core.go (top-level re-export layer, no longer needed)
- pkg/mnt/ (absorbed into pkg/core/mnt.go)
- pkg/log/ (absorbed into pkg/core/log.go)
- go-io dependency (absorbed into pkg/core/io.go)
- go-log dependency (absorbed into pkg/core/error.go + log.go)

Remaining: single package pkg/core/ with 14 source files.
Only dependency: testify (test-only).
Production code: zero external dependencies.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 01:28:14 +00:00
Snider
16a985ad5c feat: absorb go-log into core — error.go + log.go in pkg/core
Brings go-log's errors and logger directly into the Core package:
  core.E("pkg.Method", "msg", err)     — structured errors
  core.Err{Op, Msg, Err, Code}         — error type
  core.Wrap(err, op, msg)              — error wrapping
  core.NewLogger(opts)                 — structured logger
  core.Info/Warn/Error/Debug(msg, kv)  — logging functions

Removed:
  pkg/core/e.go — was re-exporting from go-log, now source is inline
  pkg/log/ — was re-exporting, no longer needed

Renames to avoid conflicts:
  log.New() → core.NewLogger() (core.New is the DI constructor)
  log.Message() → core.ErrorMessage() (core.Message is the IPC type)

go-log still exists as a separate module for external consumers.
Core framework now has errors + logging built-in. Zero deps.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 01:23:02 +00:00
Snider
dd6803df10 fix(security): fix latent sandbox escape in IO.path()
filepath.Clean("/"+p) returns absolute path, filepath.Join(root, "/abs")
drops root on Linux. Strip leading "/" before joining with sandbox root.

Currently not exploitable (validatePath handles it), but any future
caller of path() with active sandbox would escape. Defensive fix.

Found by Gemini Pro security review.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 01:16:30 +00:00
Snider
55cbfea7ca fix: apply Gemini review findings on embed.go
- Fix decompress: check gz.Close() error (checksum verification)
- Remove dead groupPaths variable (never read)
- Remove redundant AssetRef.Path (duplicate of Name)
- Remove redundant AssetGroup.name (key in map is the name)

Gemini found 8 issues, 4 were real bugs/dead code.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 01:12:10 +00:00
Snider
81eba2777a fix: apply Gemini Pro review — maps.Clone for crash metadata
Prevents external mutation of crash handler metadata after construction.
Uses maps.Clone (Go 1.21+) as suggested by Gemini Pro review.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 01:02:48 +00:00
Snider
d1c9d4e4ad refactor: generic EtcGet[T] replaces typed getter boilerplate
GetString/GetInt/GetBool now delegate to EtcGet[T].
Gemini Pro review finding — three identical functions collapsed to one generic.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 01:00:47 +00:00
Snider
8935905ac9 fix: remove goio alias, use io directly
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 00:45:17 +00:00
Snider
d7f9447e7a feat: add core.Io() — local filesystem I/O on Core struct
Brings go-io/local into Core as c.Io():
  c.Io().Read("config.yaml")
  c.Io().Write("output.txt", content)
  c.Io().WriteMode("key.pem", data, 0600)
  c.Io().IsFile("go.mod")
  c.Io().List(".")
  c.Io().Delete("temp.txt")

Default: rooted at "/" (full access like os package).
Sandbox: core.WithIO("./data") restricts all operations.

c.Mnt() stays for embedded/mounted assets (read-only).
c.Io() is for local filesystem (read/write/delete).
WithMount stays for mounting fs.FS subdirectories.
WithIO added for sandboxing local I/O.

Based on go-io/local/client.go (~300 lines), zero external deps.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 00:42:41 +00:00
Snider
077fde9516 rename: pack.go → embed.go
It embeds assets into binaries. Pack is what bundlers do.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 00:23:13 +00:00
Snider
9331f5067c feat: add Slicer[T] generics + Pack (asset packing without go:embed)
Slicer[T] — generic typed slice operations (leaanthony/slicer rewrite):
  s := core.NewSlicer("a", "b", "c")
  s.AddUnique("d")
  s.Contains("a")      // true
  s.Filter(fn)          // new filtered slicer
  s.Deduplicate()       // remove dupes
  s.Each(fn)            // iterate

Pack — build-time asset packing (leaanthony/mewn pattern):
  Build tool: core.ScanAssets(files) → core.GeneratePack(pkg)
  Runtime: core.AddAsset(group, name, data) / core.GetAsset(group, name)

  Scans Go AST for core.GetAsset() calls, reads referenced files,
  gzip+base64 compresses, generates Go source with init().
  Works without go:embed — language-agnostic pattern for CoreTS bridge.

Both zero external dependencies.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 00:21:08 +00:00
Snider
8765458bc6 feat: add core.Crash() — panic recovery and crash reporting
Adfer (Welsh: recover). Built into the Core struct:
  defer c.Crash().Recover()     // capture panics
  c.Crash().SafeGo(fn)          // safe goroutine
  c.Crash().Reports(5)          // last 5 crash reports

CrashReport includes: timestamp, error, stack trace,
system info (OS/arch/Go version), custom metadata.

Optional file output: JSON array of crash reports.
Zero external dependencies.

Based on leaanthony/adfer (168 lines), integrated into pkg/core.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 00:17:19 +00:00
Snider
66b4b08600 feat: add core.Etc() — configuration, settings, and feature flags
Replaces the old Features struct with Etc on the Core struct:
  c.Etc().Set("api_url", "https://api.lthn.sh")
  c.Etc().Enable("coderabbit")
  c.Etc().Enabled("coderabbit")  // true
  c.Etc().GetString("api_url")   // "https://api.lthn.sh"

Also adds Var[T] — generic optional variable (from leaanthony/u):
  v := core.NewVar("hello")
  v.Get()    // "hello"
  v.IsSet()  // true
  v.Unset()  // zero value, IsSet() = false

Removes Features struct from Core (replaced by Etc).
Thread-safe via sync.RWMutex. Zero external dependencies.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 00:14:44 +00:00
Snider
9a57a7bc88 feat: integrate mnt into Core struct — c.Mnt() for mount operations
Mnt is now a built-in capability of the Core struct, not a service:
  c.Mnt().ReadString("persona/secops/developer.md")
  c.Mnt().Extract(targetDir, data)

Changes:
- Move mnt.go + mnt_extract.go into pkg/core/ (same package)
- Core struct: replace `assets embed.FS` with `mnt *Sub`
- WithAssets now creates a Sub mount (backwards compatible)
- Add WithMount(embed, "basedir") for subdirectory mounting
- Assets() deprecated, delegates to c.Mnt().Embed()
- Top-level core.go re-exports Mount, WithMount, Sub, ExtractOptions
- pkg/mnt still exists independently for standalone use

One import, one struct, methods on the struct:
  import core "forge.lthn.ai/core/go"
  c, _ := core.New(core.WithAssets(myEmbed))
  c.Mnt().ReadString("templates/coding.md")

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 00:06:29 +00:00
Snider
c0d50bdf92 feat: add top-level core.go — re-exports DI container API
Users can now:
  import core "forge.lthn.ai/core/go"
  c, _ := core.New(core.WithService(factory))
  svc, _ := core.ServiceFor[*MyService](c, "name")

Re-exports: New, WithService, WithName, WithServiceLock, WithAssets,
ServiceFor, Core, Option, Message, Startable, Stoppable, LocaleProvider,
ServiceRuntime.

Sub-packages imported directly: pkg/mnt, pkg/log, etc.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-17 23:39:33 +00:00
Snider
21c4f718d3 feat: add pkg/mnt — mount operations for Core framework
core.mnt provides zero-dep mount operations:
- mnt.FS(embed, "subdir") — scoped embed.FS access (debme pattern)
- mnt.Extract(fs, targetDir, data) — template directory extraction (gosod/Install pattern)

Template extraction supports:
- Go text/template in file contents (.tmpl suffix)
- Go text/template in directory and file names ({{.Name}})
- Ignore files, rename files
- Variable substitution from any struct or map

Based on leaanthony/debme (70 lines) + leaanthony/gosod (280 lines),
rewritten as single zero-dep package. All stdlib, no transitive deps.

8 tests covering FS, Sub, ReadFile, ReadString, ReadDir, Extract.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-17 23:32:53 +00:00
Snider
7a9c9caabc chore: sync dependencies for v0.3.3
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-17 17:52:47 +00:00
Snider
fbb26b1be2 chore: sync dependencies for v0.3.2
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-17 17:47:41 +00:00
Snider
29e6d06633 fix(core): replace fmt.Errorf with structured errors, add log service tests
- Replace all fmt.Errorf calls with coreerr.E() from go-log for structured
  error context (op, msg, underlying error) across core.go, service_manager.go,
  and runtime_pkg.go (12 violations fixed)
- Replace local Error type and E() in e.go with re-exports from go-log,
  eliminating duplicate implementation while preserving public API
- Add comprehensive tests for pkg/log Service (NewService, OnStartup,
  QueryLevel, TaskSetLevel) — coverage 72.2% → 87.8%
- Update CLAUDE.md: Go 1.25 → 1.26, runtime.go → runtime_pkg.go,
  document go-log error convention
- No os.ReadFile/os.WriteFile violations found (all I/O uses go-io)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-17 08:03:05 +00:00
Snider
f4e2701018 chore: save LocaleProvider and Locales changes
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-17 01:45:42 +00:00
Snider
d64099b028 feat(core): add LocaleProvider interface for automatic i18n collection
Services implementing LocaleProvider have their locale FS collected
during RegisterService. The i18n service reads Core.Locales() on
startup to load all translations. Zero explicit wiring needed.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-17 01:31:19 +00:00
Snider
e1294b8412 chore: sync workspace dependencies
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-15 15:44:39 +00:00
Snider
5e7c0e9ec4 chore: ignore workspace and IDE dirs 2026-03-15 10:21:42 +00:00
Snider
d66ff46312 docs: remove implemented plans, annotate partial ones
18 plan files deleted (absorbed into core.help docs).
4 kept with implementation notes (lint MCP, AltumCode Layer 2).

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-14 08:09:20 +00:00
Snider
681c88795f docs: add scheduled actions implementation plan (7 tasks)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 12:15:45 +00:00
Snider
d98eef7d37 docs: add scheduled actions design
Attribute-driven, database-backed Action scheduling for CorePHP.
#[Scheduled] attribute on Actions, auto-discovery via scanner,
persist to scheduled_actions table, runtime control via admin.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 12:12:08 +00:00
Snider
fd8a42a088 docs: add AltumCode update checker implementation plan (5 tasks)
TDD plan for uptelligence AltumCode version detection + Claude Code
download skill. Covers VendorUpdateCheckerService, vendor seeding,
deployed version sync, and browser-automated download plugin.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 08:27:58 +00:00
Snider
831e0736b5 docs: add AltumCode update checker design
Two-layer system: uptelligence version detection (5 HTTP GETs)
+ Claude Code skill for browser-automated downloads from
LemonSqueezy (Playwright) and CodeCanyon (Chrome).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 08:20:24 +00:00
Snider
89d189dd95 docs: add human-friendly documentation for Core Go framework
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 13:02:37 +00:00
Snider
48346a32cb docs: add core/mcp extraction plan
Move Go MCP server from go-ai + PHP MCP from php-mcp into
a single core/mcp repo producing the core-mcp binary.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 18:29:48 +00:00
Snider
e3882963a9 docs: add plug package extraction implementation plan (11 tasks)
Covers extracting app/Plug/* into 8 core/php-plug-* packages with
Core\Plug\* namespace alignment. Contracts move to core/php framework,
Registry gains register() method for package self-registration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:22:02 +00:00