Commit graph

466 commits

Author SHA1 Message Date
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
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
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
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
e2a68fc283 fix: harden DI container — lifecycle safety, Go 1.26 modernisation
- Prevent nil service registration and empty name discovery
- PerformAsync uses sync.WaitGroup.Go() with shutdown guard (atomic.Bool)
- ServiceShutdown respects context deadline, no goroutine leak on cancel
- IPC handler signature mismatch now returns error instead of silent skip
- Runtime.ServiceStartup/ServiceShutdown return error for Wails v3 compat
- Replace manual sort/clone patterns with slices.Sorted, slices.Clone,
  slices.Backward, maps.Keys
- Add async_test.go for PerformAsync coverage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 09:11:22 +00:00
Snider
915816b3b5 docs: add pkg/core documentation, remove 12MB stale generated site
- Add comprehensive docs/pkg/core.md covering DI container, service
  pattern, message bus (ACTION/QUERY/TASK), error handling, runtime
- Remove pkg/core/docs/site/ (ancient MkDocs HTML with Lethean branding)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-06 14:29:04 +00:00
Snider
4f6209f590 refactor: promote pkg/framework/core to pkg/core
pkg/framework/core/ → pkg/core/ (first-class import path)
pkg/framework/ shim deleted (no longer needed)

Import path: forge.lthn.ai/core/go/pkg/core

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-06 14:10:34 +00:00
Snider
e920397741 refactor: extract remaining pkg/ packages to standalone modules
- pkg/session → go-session
- pkg/ws → go-ws
- pkg/webview → go-webview
- pkg/workspace → go-io/workspace
- pkg/lab → lthn/lem/pkg/lab
- pkg/build deleted (empty dirs)
- lem-chat moved to lthn/LEM
- internal/core-ide + cmd/core-ide deleted (Wails artifacts, source in core/ide)
- internal/cmd deleted (Wails updater artifacts)
- Taskfile.yaml deleted (stale Wails duplicate)

pkg/ now contains only framework + log (stays).

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-06 13:48:00 +00:00
Snider
ef5c83c04e refactor: delete pkg/io, slim pkg/log to go-io/go-log re-exports
- Delete pkg/io/ entirely (all consumers now use go-io)
- Delete pkg/log/{errors.go,log.go} duplicates (now in go-log)
- Rewrite pkg/log/log.go as thin re-export layer over go-log
- Keep pkg/log/{service.go,rotation.go} (framework/go-io deps)
- Swap internal pkg/ imports to go-io/go-log across ~30 files

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-06 12:23:52 +00:00
Snider
ddc8582d7f refactor: remove pkg/help, use core/go-help module
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-06 09:11:54 +00:00
Snider
84397a2e10 refactor: remove pkg/i18n, use core/go-i18n module
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-06 09:09:02 +00:00
Snider
2958527774 refactor: extract pkg/coredeno to core/ts
Deno/TypeScript runtime bridge now lives in its own repo
at forge.lthn.ai/core/ts, completing the trifecta:
core/go, core/php, core/ts.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-06 09:04:38 +00:00
Claude
2b09a26507
chore: use slices.Contains for linear search
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 15:54:39 +00:00
Claude
eb186027a0
chore: use range-over-integer (Go 1.22+)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 15:53:09 +00:00
Claude
d60e87dac8
chore: use min()/max() builtins (Go 1.21+)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 15:52:06 +00:00
Claude
ff530d9898
chore: sort.Slice → slices.SortFunc
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 15:45:48 +00:00
Claude
13ed6d3f76
chore: use %w for error wrapping
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 15:39:19 +00:00
Claude
d570c87efc
chore: fmt.Errorf(static) → errors.New
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 15:38:38 +00:00
Claude
09c25b9975
chore: replace interface{} with any (Go 1.18+ alias)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 15:38:00 +00:00
Snider
3587d0ce27 test: add coverage for lab, session, sigil, repos, plugin packages
Brings 5 packages from low/zero coverage to solid test suites:
- pkg/lab: 0% → 100% (Store pub/sub, Config env loading)
- pkg/session: 0% → 89.9% (transcript parser, HTML renderer, search, video)
- pkg/io/sigil: 43.8% → 98.5% (XOR/ShuffleMask obfuscators, ChaCha20-Poly1305)
- pkg/repos: 18.9% → 81.9% (registry, topo sort, directory scan, org detection)
- pkg/plugin: 54.8% → 67.1% (installer error paths, Remove, registry Load/Save)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-24 13:29:15 +00:00
Snider
57ad74d4e2 refactor: delete pkg/cli, migrate imports to core/cli
pkg/cli now lives in forge.lthn.ai/core/cli as its own module.
All cmd/gocmd imports updated. qa docblock check stubbed pending
go-devops circular dependency resolution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 23:08:03 +00:00
Claude
1734acaae0
chore: migrate Snider deps from github.com to forge.lthn.ai
Update Borg dependency path from github.com/Snider/Borg to
forge.lthn.ai/Snider/Borg across go.mod and imports.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 21:40:25 +00:00
Snider
2a90ae65b7 refactor(cli): register commands through Core framework lifecycle
Replace the RegisterCommands/attachRegisteredCommands side-channel with
WithCommands(), which wraps command registration functions as framework
services. Commands now participate in the Core lifecycle via OnStartup,
receiving the root cobra.Command through Core.App.

Main() accepts variadic framework.Option so binaries pass their commands
explicitly — no init(), no blank imports, no global state.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-21 22:06:40 +00:00
Snider
58ca902320 feat(cli): add Viewport for scrollable content (logs, diffs, docs)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-21 18:13:37 +00:00
Snider
a0660e5802 feat(cli): add TextInput with placeholder, masking, validation
Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-21 18:13:07 +00:00
Snider
fcdccdbe87 feat(cli): add InteractiveList with keyboard navigation and terminal fallback
Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-21 18:12:37 +00:00
Snider
c2418a2737 feat(cli): stub Form, FilePicker, Tabs with simple fallbacks
Interfaces defined for future charmbracelet/huh upgrade.
Current implementations use sequential prompts.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-21 18:10:33 +00:00
Snider
175ad1e361 feat(cli): add ProgressBar with Increment, Set, SetMessage, Done
Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-21 18:10:01 +00:00