Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 9 additions & 12 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ go build ./cmd/moltark # Go build (quick local check)
bazelisk test //... # Full Bazel suite (CI-authoritative)
go test ./... # Full Go suite
go test -count=1 ./... # Full suite (no cache)
go test -count=1 ./internal/moltark/... # Core package tests
go test -count=1 ./internal/engine/... # Engine tests (pipeline, resolve, plan, state)
go test -count=1 ./internal/filefmt/... # File format handler tests
go test -count=1 ./internal/module/... # Starlark module tests
go test -count=1 ./tests/integration/... # Integration tests only
go test -count=1 ./tests/features/... # Gherkin feature tests only

Expand All @@ -42,24 +44,19 @@ Use `-count=1` when changing snapshots or CLI behavior to avoid stale test-cache

**CLI layer**: `cmd/moltark/main.go` -> `internal/cliapp/app.go` -> `internal/command/` (one file per subcommand: init, plan, apply, show, doctor, version). Uses `github.com/mitchellh/cli`.

**Engine pipeline** (`internal/moltark/pipeline.go`): Five sequential phases:
1. **Evaluate** - load `molt.star` via Starlark (`config.go`) into `DesiredModel` (projects + components)
2. **Resolve** - resolve facts, providers, routed intents (`resolve.go`) into `ResolvedModel` with managed files
3. **Inspect** - read current repo state: structured files (TOML/JSON/YAML), `.moltark/state.json`, `.gitattributes`
4. **Persist** - build next state from desired+resolved model
5. **Plan** - classify each owned path as create/update/no-op/drift/conflict (`plan.go`)
**Package layout** (acyclic dependency order: model <- filefmt <- module <- engine):

**Service** (`internal/moltark/service.go`): Orchestrates `Plan`, `Apply`, `Show`, `Doctor` operations. Apply re-runs the full pipeline and verifies intent hasn't changed before writing.
- **`internal/model`** — Shared domain types and constants. All IR types (`DesiredModel`, `ResolvedModel`, `Plan`, `Change`, `State`, etc.), change status/reason enums, file format constants, module source identifiers, and clone helpers. Zero internal dependencies.

**First-party modules** (`internal/moltark/module_*.go`): `moltark/core`, `moltark/python`, `astral/uv`. Go and Rust are target ecosystems but do not yet have first-party module depth.
- **`internal/filefmt`** — Structured file format handlers. Path resolution for TOML (dot notation) and JSON/YAML (JSON Pointer), format-specific parsers/mutators (TOML, JSON, YAML), `.gitattributes` managed-block logic. Depends on model only.

**Structured file mutation**: Format-specific mutators for TOML (`pyproject.go`), JSON (`jsonfile.go`), YAML (`yamlfile.go`) that write only owned paths.
- **`internal/module`** — Starlark DSL and module system. Config loading (`LoadDesiredModel`, `InitRepository`), module registry, first-party modules (`moltark/core`, `moltark/python`, `astral/uv`), Starlark value conversion, and fact-ref value type. Depends on model + filefmt.

**Types** (`internal/moltark/types.go`): All IR types -- `DesiredModel`, `ResolvedModel`, `Pipeline`, `Plan`, `Change`, `State`, etc.
- **`internal/engine`** — Reconciliation engine. Five-phase pipeline (evaluate -> resolve -> inspect -> persist -> plan), service layer (`Plan`, `Apply`, `Show`, `Doctor`), change classification, state management, and plan rendering. Depends on model + filefmt + module.

## Testing Structure

- **Package tests**: `internal/moltark/*_test.go` -- planner, resolver, mutator, state logic
- **Package tests**: `internal/engine/*_test.go` (pipeline, resolve, plan, state), `internal/filefmt/*_test.go` (format handlers, paths), `internal/module/*_test.go` (config loading, core module)
- **Integration snapshots**: `tests/integration/` -- copies fixture repos to temp dirs, runs CLI commands, snapshots output via `go-snaps`
- **Gherkin features**: `tests/features/` -- behavioral scenarios via `godog`
- **Fixtures**: `tests/fixtures/` -- real repository structures (molt.star + pyproject.toml + state.json)
Expand Down
6 changes: 5 additions & 1 deletion internal/command/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,9 @@ go_library(
],
importpath = "github.com/ophidiarium/moltark/internal/command",
visibility = ["//:__subpackages__"],
deps = ["//internal/moltark"],
deps = [
"//internal/engine",
"//internal/model",
"//internal/module",
],
)
6 changes: 3 additions & 3 deletions internal/command/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package command
import (
"flag"

"github.com/ophidiarium/moltark/internal/moltark"
"github.com/ophidiarium/moltark/internal/engine"
)

type ApplyCommand struct {
Expand All @@ -26,7 +26,7 @@ func (c *ApplyCommand) Run(args []string) int {
return 1
}

c.printLine(moltark.RenderPlan(plan))
c.printLine(engine.RenderPlan(plan))
if plan.HasConflicts() {
c.errorf("Apply aborted due to conflicts.\n")
return 1
Expand All @@ -53,7 +53,7 @@ func (c *ApplyCommand) Run(args []string) int {
}

c.printLine("")
c.printLine(moltark.RenderApply(result))
c.printLine(engine.RenderApply(result))
return 0
}

Expand Down
4 changes: 2 additions & 2 deletions internal/command/doctor.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package command
import (
"flag"

"github.com/ophidiarium/moltark/internal/moltark"
"github.com/ophidiarium/moltark/internal/engine"
)

type DoctorCommand struct {
Expand All @@ -24,7 +24,7 @@ func (c *DoctorCommand) Run(args []string) int {
return 1
}

c.printLine(moltark.RenderDoctor(report))
c.printLine(engine.RenderDoctor(report))
if report.HasIssues {
return 1
}
Expand Down
7 changes: 4 additions & 3 deletions internal/command/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import (
"flag"
"fmt"

"github.com/ophidiarium/moltark/internal/moltark"
"github.com/ophidiarium/moltark/internal/model"
"github.com/ophidiarium/moltark/internal/module"
)

type InitCommand struct {
Expand All @@ -19,7 +20,7 @@ func (c *InitCommand) Run(args []string) int {
return 1
}

result, err := moltark.InitRepository(c.WorkingDir)
result, err := module.InitRepository(c.WorkingDir)
if err != nil {
c.errorf("Error: %s\n", err)
return 1
Expand All @@ -36,7 +37,7 @@ func (c *InitCommand) Help() string {
%s when one does not already exist.

This command does not reconcile pyproject.toml. Run "moltark plan"
and "moltark apply" after initialization.`, moltark.ProjectSpecFileName)
and "moltark apply" after initialization.`, model.ProjectSpecFileName)
}

func (c *InitCommand) Synopsis() string {
Expand Down
6 changes: 3 additions & 3 deletions internal/command/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"io"
"strings"

"github.com/ophidiarium/moltark/internal/moltark"
"github.com/ophidiarium/moltark/internal/engine"
)

type Meta struct {
Expand All @@ -16,8 +16,8 @@ type Meta struct {
Stderr io.Writer
}

func (m Meta) service() moltark.Service {
return moltark.NewService()
func (m Meta) service() engine.Service {
return engine.NewService()
}

func (m Meta) printf(format string, args ...any) {
Expand Down
9 changes: 5 additions & 4 deletions internal/command/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import (
"flag"
"fmt"

"github.com/ophidiarium/moltark/internal/moltark"
"github.com/ophidiarium/moltark/internal/engine"
"github.com/ophidiarium/moltark/internal/model"
)

type PlanCommand struct {
Expand All @@ -26,7 +27,7 @@ func (c *PlanCommand) Run(args []string) int {
return 1
}

c.printLine(moltark.RenderPlan(plan))
c.printLine(engine.RenderPlan(plan))

if plan.HasConflicts() {
return 1
Expand All @@ -47,9 +48,9 @@ func (c *PlanCommand) Help() string {

Options:

-detailed-exitcode Return 2 when changes are planned.`, moltark.ProjectSpecFileName)
-detailed-exitcode Return 2 when changes are planned.`, model.ProjectSpecFileName)
}

func (c *PlanCommand) Synopsis() string {
return fmt.Sprintf("Show repository changes required by %s", moltark.ProjectSpecFileName)
return fmt.Sprintf("Show repository changes required by %s", model.ProjectSpecFileName)
}
40 changes: 40 additions & 0 deletions internal/engine/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
load("@rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "engine",
srcs = [
"pipeline.go",
"plan.go",
"resolve.go",
"service.go",
"state.go",
],
importpath = "github.com/ophidiarium/moltark/internal/engine",
visibility = ["//:__subpackages__"],
deps = [
"//internal/filefmt",
"//internal/model",
"//internal/module",
],
)

go_test(
name = "engine_test",
size = "small",
srcs = [
"pipeline_test.go",
"plan_test.go",
"resolve_test.go",
"service_test.go",
"state_test.go",
],
data = [
"//tests/fixtures:all_fixtures",
],
embed = [":engine"],
deps = [
"//internal/filefmt",
"//internal/model",
"//internal/testrepo",
],
)
Loading