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
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,16 @@ typings/


coverage
test/cov
ts/test/cov

# Generated fixtures written by tests at runtime
ts/test/_gen

package-lock.json
yarn.lock


*~

test/coverage.html
ts/test/coverage.html

181 changes: 181 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# AGENTS.md

Guidance for AI coding agents (and humans) working **on** the `@voxgig/model`
codebase. For using the tool, start at the [README](./README.md) and
[docs/](./docs/). This file is about developing the package itself.

This repository holds **two implementations** of the same tool:

- **`ts/`** — the TypeScript package `@voxgig/model` (npm library + CLI).
**TypeScript is canonical.**
- **`go/`** — the Go module `github.com/voxgig/model/go`, kept in architectural
parity with the TypeScript version.

Keep this file accurate: if you change the build, test layout, or a convention,
update it in the same change.


## Layout

```
ts/ TypeScript implementation (canonical)
src/ library source (model.ts, build.ts, watch.ts, ...)
test/ node:test suites
dist/ dist-test/ COMMITTED build output
bin/voxgig-model CLI entry (plain JS)
model/ the package's own sample model
package.json
go/ Go implementation (parity)
*.go package `model` (build.go, producer.go, watch.go, ...)
cmd/voxgig-model/ Go CLI entry
*_test.go go test suites
go.mod go.sum
docs/ tutorial / how-to / reference / explanation
Makefile orchestrates both: `make build`, `make test`
.github/workflows/ CI (a `ts` job and a `go` job)
```


## Build & test

From the repository root, `make` drives both implementations:

```bash
make build # build-ts + build-go
make test # test-ts + test-go
make # all: build then test
```

### TypeScript (run from `ts/`)

```bash
cd ts
npm install
npm run build # tsc --build src test -> dist/ and dist-test/
npm test # node --test dist-test/**/*.test.js
TEST_PATTERN=watch npm run test-some
npm run test-cov
```

**You must `npm run build` before `npm test`** — tests run against compiled
output in `dist-test/` and import the library from `dist/`.

### Go (run from `go/`)

```bash
cd go
go build ./...
go vet ./...
gofmt -l . # must print nothing
go test ./...
```

Go **1.24+** is required (the `aontu/go` dependency declares `go 1.24.7`).


## Critical gotchas (TypeScript)

1. **Run TS commands from `ts/`.** `tsc --build src test` resolves `src` and
`test` as project paths relative to the current directory; from the wrong
directory it fails with `TS5083` and silently leaves `dist/` stale — so
tests then run against old code.

2. **`ts/dist/` and `ts/dist-test/` are committed.** After any source change,
rebuild and commit the regenerated output alongside the `.ts`.

3. **The CLI (`ts/bin/voxgig-model`) is plain JavaScript**, not compiled. It
`require`s `dist/model.js`, so the library must be built.

4. **`aontu` (npm) reads `opts.err`, not `opts.errs`** — collect mode. See
`ts/src/build.ts: resolveModel`.

5. **Generated test fixtures go in `ts/test/_gen/`** (gitignored). Tests write
their own fixtures there at runtime; do not commit them.


## The Go port

The Go module ports the **architecture**, not every mechanism. The build
lifecycle (pre → reload → post), producers, model output, dryrun, and watch
semantics match TypeScript. Two things differ by necessity:

- **Actions are a registry, not dynamic modules.** TypeScript declares actions
in a config file and loads them with `require()`. Go cannot load code at
runtime, so actions are registered programmatically via `ModelSpec.Actions`
(`map[string]ActionDef`) — see `go/producer.go`.
- **Watching polls modification times** (`go/watch.go`) rather than using
chokidar.

Other notes:

- **Unification** uses the real Go aontu engine
(`github.com/rjrodger/aontu/go`). Its `Generate(src)` has no base parameter,
so `AontuResolver` briefly `chdir`s to the model base (guarded by a mutex)
so `@"..."` imports resolve. aontu/go does not report import deps, so the
watcher tracks `*.jsonic` files under the base directory.
- **JSON key order** differs: Go's `encoding/json` sorts object keys;
TypeScript preserves insertion order. Content is otherwise equivalent — an
accepted cross-language difference.
- **`const Version`** lives in `go/model.go`; `make publish-go V=x.y.z`
rewrites it and tags `go/vx.y.z`.
- The Go port depends on **`aontu/go` only**; it does not use `util/go` (the
TypeScript `@voxgig/util` dependency is for pino logging, replaced here by a
minimal `Log` interface).


## Maintaining parity

TypeScript is canonical. When changing behavior:

1. Change TypeScript first, with a test (`ts/test/*.test.ts`).
2. Apply the equivalent change to Go, with a test (`go/*_test.go`).
3. Rebuild TypeScript and commit the regenerated `ts/dist/`.
4. Run both suites (`make test`); keep `gofmt`/`go vet` clean.
5. Update `docs/` if the API changed.


## Writing tests

**TypeScript:** `node:test` (`describe`/`test`), import from `../dist/...`.
Runtime fixtures under `ts/test/_gen/<name>/`, created in the test. Watch tests
must `await model.stop()` in a `finally`. CLI tests spawn the built bin without
a shell.

**Go:** standard `testing` with `t.TempDir()` fixtures. Watch tests must
`defer m.Stop()`. Do **not** `t.Parallel()` resolver-using tests —
`AontuResolver` changes the working directory.


## Common tasks (playbooks)

### Add a build action (product-level generator)
User-space, not framework code. TypeScript: declare in
`.model-config/model-config.jsonic`, implement under `build/`. Go: register an
`ActionDef` in `ModelSpec.Actions`. See
[docs/how-to.md](./docs/how-to.md#write-a-build-action).

### Add a built-in producer (framework-level)
TypeScript: add `ts/src/producer/<name>.ts`, wire it into `ts/src/model.ts`.
Go: add a `Producer` func in `go/producer.go`, wire it into `go/model.go`.
Rebuild and test both; commit `ts/dist/`.

### Change the model/build types
TypeScript: `ts/src/types.ts`. Go: `go/types.go`. Keep the two aligned.


## Before you commit

- `make build` is clean and `make test` is green (both languages).
- Regenerated `ts/dist/` and `ts/dist-test/` are staged with the source.
- `gofmt -l go` prints nothing; `cd go && go vet ./...` is clean.
- No `ts/test/_gen/` artifacts are staged.
- This file and `docs/` reflect any behavior or convention change.


## More documentation

- [docs/tutorial.md](./docs/tutorial.md) — learn the tool by building a model.
- [docs/how-to.md](./docs/how-to.md) — task recipes.
- [docs/reference.md](./docs/reference.md) — CLI, API, config, language.
- [docs/explanation.md](./docs/explanation.md) — architecture and design
rationale (read before structural changes).
38 changes: 38 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# CLAUDE.md

Guidance for Claude Code (and other AI agents) working in this repository.

This repo has **two implementations**: `ts/` (TypeScript, canonical) and `go/`
(Go, in parity). The full contributor/agent guide is in **@AGENTS.md** — read
it before making changes. The essentials:

## Build & test
- From the root, `make build` and `make test` drive both languages.
- **TypeScript** (`cd ts`): `npm run build` (`tsc --build src test`) **before**
`npm test`. `ts/dist/` and `ts/dist-test/` are **committed** — rebuild and
stage them with any source change.
- **Go** (`cd go`): `go build ./...`, `go vet ./...`, `gofmt -l .`,
`go test ./...`. Go 1.24+.

## Watch out for
- Run TS commands **from `ts/`**: `tsc --build src test` resolves project paths
relative to cwd (`TS5083` otherwise), and a failed build leaves stale `dist/`.
- Watch tests must release watchers (`await model.stop()` / `defer m.Stop()`),
or the process hangs.
- TS runtime fixtures belong in `ts/test/_gen/` (gitignored).
- TS: `aontu.generate` collects errors only with `opts.err` (not `opts.errs`).
- Go: `AontuResolver` `chdir`s to the model base — don't `t.Parallel()` tests
that resolve.

## Parity
TypeScript is canonical. Change TS (with a test) first, then mirror in Go (with
a test), rebuild `ts/dist/`, and run `make test`. Keep the two in step.

## Style
- TS: 2-space indent, no semicolons, single quotes, CommonJS. Match the file.
- Go: standard `gofmt`; package `model`; `/* Copyright © ... */` header.
- Never `console.log` in TS library code (use the pino logger); in Go log
through the `Log` interface.

When you change behavior or a convention, update `@AGENTS.md` and the relevant
file in `docs/` in the same change.
51 changes: 51 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
.PHONY: all build test clean build-ts build-go test-ts test-go vet-go clean-ts clean-go publish-go tags-go reset

all: build test

build: build-ts build-go

test: test-ts test-go

clean: clean-ts clean-go

# TypeScript (the npm package lives in ts/) — the canonical implementation.
build-ts:
cd ts && npm run build

test-ts:
cd ts && npm test

cov-ts:
cd ts && npm run test-cov

clean-ts:
rm -rf ts/dist ts/dist-test ts/node_modules

# Go (the module lives in go/) — kept in parity with TypeScript.
build-go:
cd go && go build ./...

test-go:
cd go && go test ./...

vet-go:
cd go && go vet ./... && gofmt -l .

clean-go:
cd go && go clean

# Publish the Go module: make publish-go V=0.1.1
publish-go: vet-go test-go
@test -n "$(V)" || (echo "Usage: make publish-go V=x.y.z" && exit 1)
sed -i 's/^const Version = ".*"/const Version = "$(V)"/' go/model.go
git add go/model.go
git commit -m "go: v$(V)"
git tag go/v$(V)
git push origin HEAD go/v$(V)

tags-go:
git tag -l 'go/v*' --sort=-version:refname

reset:
cd ts && npm run reset
cd go && go clean -cache && go build ./... && go test ./...
Loading
Loading