Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b7348d7
feat(app-builder): add deterministic replay
kattsushi Aug 8, 2026
1aa7977
feat(app-builder): add nested E2E harness
kattsushi Aug 8, 2026
f8638cd
feat(app-builder): expose public generate command
kattsushi Aug 9, 2026
613a965
feat(app-builder): enforce complete replay identities
kattsushi Aug 11, 2026
b85aabf
feat(app-builder): add typed generator catalog
kattsushi Aug 11, 2026
76aab96
feat(app-builder): add generation surface adapters
kattsushi Aug 11, 2026
70c732c
feat(app-builder): add atomic Todo renderers
kattsushi Aug 11, 2026
21b92cc
feat(app-builder): materialize atomic Todo plans
kattsushi Aug 11, 2026
731cbc1
feat(app-builder): preserve Todo v1 parity
kattsushi Aug 12, 2026
66bfa63
test(app-builder): bound semantic compilation proof
kattsushi Aug 12, 2026
8d7a2c9
refactor(app-builder): use declarative generation templates
kattsushi Aug 12, 2026
5515285
fix(app-builder): make generated template bytes authoritative
kattsushi Aug 12, 2026
027f002
fix(app-builder): use real ejs rendering
kattsushi Aug 12, 2026
90fb80e
chore(app-builder): parameterize template identities
kattsushi Aug 12, 2026
40a951a
refactor(app-builder): generalize reusable generation kernel
kattsushi Aug 12, 2026
2ad43e0
refactor(app-builder): use declarative generation templates (#145)
kattsushi Aug 13, 2026
dc97f7a
feat(app-builder): prove installed CLI generation
kattsushi Aug 13, 2026
54b9677
test(app-builder): prove CLI lifecycle cleanup
kattsushi Aug 13, 2026
7d3493c
feat(app-builder): gate showcase regeneration
kattsushi Aug 14, 2026
32d1562
feat(app-builder): gate showcase regeneration (#151)
kattsushi Aug 14, 2026
fa88569
test(app-builder): prove CLI lifecycle cleanup (#149)
kattsushi Aug 14, 2026
8cfeaa0
feat(app-builder): prove installed CLI generation (#147)
kattsushi Aug 14, 2026
5ac25f7
feat(app-builder): preserve Todo v1 parity (#143)
kattsushi Aug 14, 2026
d8cb781
feat(app-builder): materialize atomic Todo plans (#141)
kattsushi Aug 14, 2026
e82e9dc
feat(app-builder): add atomic Todo renderers (#139)
kattsushi Aug 14, 2026
c4c9931
feat(app-builder): add generation surface adapters (#137)
kattsushi Aug 14, 2026
a45773a
feat(app-builder): add typed generator catalog (#135)
kattsushi Aug 14, 2026
85cc877
feat(app-builder): enforce complete replay identities (#133)
kattsushi Aug 14, 2026
d6a26b8
feat(app-builder): regenerate canonical Todo showcase
kattsushi Aug 14, 2026
414681e
feat(app-builder): regenerate canonical Todo showcase (#130)
kattsushi Aug 14, 2026
28a09f9
feat(app-builder): expose public generate command (#129)
kattsushi Aug 14, 2026
4d4af9f
feat(app-builder): add nested E2E harness (#126)
kattsushi Aug 14, 2026
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
22 changes: 21 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,30 @@ jobs:
mkdir -p "$workspace"
node packages/app-builder/execution/demo/deny-network.cjs -- pnpm nx run @effectify/app-builder-execution:executable -- --workspace "$workspace" --approve

app-builder-showcase:
name: App Builder showcase drift check
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v6
with:
version: 10.14.0
- uses: actions/setup-node@v5
with:
node-version: "22.22.0"
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Build the public generate command
run: pnpm nx run @effectify/app-builder-cli:build --skipNxCache
- name: Regenerate through the installed public CLI and compare
run: node tools/check-app-builder-showcase.mjs --expected examples/app-builder-todo --output "$RUNNER_TEMP/app-builder-todo"

# Summary job
ci-summary:
name: 📊 CI Summary
runs-on: ubuntu-latest
needs: [lint, typecheck, build, test]
needs: [lint, typecheck, build, test, app-builder-showcase]
if: always() && (github.event_name != 'pull_request' || github.event.pull_request.draft == false)
steps:
- name: 📊 CI Summary
Expand All @@ -307,6 +326,7 @@ jobs:
echo "| 🔍 Type Check | ${{ needs.typecheck.result == 'success' && '✅ Success' || '❌ Failed' }} | TypeScript compiler checks |" >> $GITHUB_STEP_SUMMARY
echo "| 🏗️ Build | ${{ needs.build.result == 'success' && '✅ Success' || '❌ Failed' }} | Nx affected build |" >> $GITHUB_STEP_SUMMARY
echo "| 🧪 Test | ${{ needs.test.result == 'success' && '✅ Success' || '❌ Failed' }} | Unit tests with Vitest |" >> $GITHUB_STEP_SUMMARY
echo "| 🧩 App Builder showcase | ${{ needs.app-builder-showcase.result == 'success' && '✅ Success' || '❌ Failed' }} | Installed public generate drift check |" >> $GITHUB_STEP_SUMMARY

echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📈 Performance Metrics" >> $GITHUB_STEP_SUMMARY
Expand Down
3 changes: 2 additions & 1 deletion .nxignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.effect-reference/
.effect-reference/
examples/app-builder-todo/**
1 change: 1 addition & 0 deletions .oxfmtrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"sortImports": false,
"sortPackageJson": false,
"ignorePatterns": [
"examples/app-builder-todo/**",
"**/dist/**",
"**/build/**",
"**/.nx/**",
Expand Down
140 changes: 140 additions & 0 deletions examples/app-builder-todo/.effectify/app-builder/showcase.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
{
"version": "effectify.app-builder-showcase/1",
"command": "generate",
"request": {
"version": "effectify.app-builder-cli-request/1",
"command": "generate",
"payload": {
"intent": {
"version": "effectify.creation-intent/1",
"preset": "todo",
"capabilities": ["todo.events"]
},
"workspace": "app-builder-todo"
}
},
"provenance": {
"canonicalJson": "effectify-cjson/1",
"evidence": {
"blocks": "sha256:84da002bcb1cdf7ba5d492806f02cbb1fffbd0a5e872c452ffb514997153456b",
"catalog": "sha256:9a279207ce20bee7378d3e0ea2cfaf31de996a5371b2be07b425d50a95dbe2ad",
"dependencies": "sha256:4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",
"intent": "sha256:96b9f1671258586740db7423e0d5e5971a3e9eef8f5e24ac8686efbf9b405c01",
"outputs": "sha256:1d502d90d9bb8fcc603544c9acf46ab8e57b2433a6076300013601f0a3ca0ad9",
"pins": "sha256:01444b6cd4c1748f19561ca13a2d58c2c7d88aa479fb6100d88d230d0deffdb0",
"plan": "sha256:0db8472dc2518641b89e560304c60f7172939da780346403b5931c2c7f3d070b"
},
"outputIdentities": [
{
"mode": "100644",
"owner": "package-surface-presentation-manifest",
"path": "apps/todo-cli/package.json",
"sourceDigest": "sha256:1f7e39c504515ed23d106e172ccc2f0ef91186021b3a02e9066fe14ea4e2783e"
},
{
"mode": "100644",
"owner": "package-surface-presentation-barrel",
"path": "apps/todo-cli/src/index.ts",
"sourceDigest": "sha256:40f8ce0bfcb8db9c548ef19dd3509319e2c69ad03b27c57914d4788536b43eb5"
},
{
"mode": "100644",
"owner": "todo-presentation-apps-todo-cli-tests-todo-test-ts",
"path": "apps/todo-cli/tests/todo.test.ts",
"sourceDigest": "sha256:77b9145ff6a68b6c4660948ab8066e3e3091500e61135cbba4f748395f3d8074"
},
{
"mode": "100644",
"owner": "workspace-surface-nx-json",
"path": "nx.json",
"sourceDigest": "sha256:36ebfd6eabb0aa4a5a4060dd694d745b9ce16717f4027bb9107c63b1e782af52"
},
{
"mode": "100644",
"owner": "workspace-surface-package-json",
"path": "package.json",
"sourceDigest": "sha256:3b369e67b69581211e14df72d231a2331399888fe8f16c5095bab03b4616e426"
},
{
"mode": "100644",
"owner": "package-surface-application-manifest",
"path": "packages/todo/application/package.json",
"sourceDigest": "sha256:74a25e6b222c0e8e6ce8cf39df7e7a68d7b68faaaf01a1321d6d1c521f147f00"
},
{
"mode": "100644",
"owner": "package-surface-application-barrel",
"path": "packages/todo/application/src/index.ts",
"sourceDigest": "sha256:a606cc63ce4e3ab6eeb2345cdaf9c4bbd3bfb43f5a20f26bff250439194a154b"
},
{
"mode": "100644",
"owner": "todo-use-case-packages-todo-application-src-use-case-ts",
"path": "packages/todo/application/src/use-case.ts",
"sourceDigest": "sha256:fce13645ef2926c343137b96fd0b2a0cd9b2e73046d8608ff3daa7204755c7df"
},
{
"mode": "100644",
"owner": "package-surface-domain-manifest",
"path": "packages/todo/domain/package.json",
"sourceDigest": "sha256:9ed2f277b6b9bf848e1c077d7a600aea00c391a05d558cc6ba0ca8134023baca"
},
{
"mode": "100644",
"owner": "todo-event-packages-todo-domain-src-events-ts",
"path": "packages/todo/domain/src/events.ts",
"sourceDigest": "sha256:3826f139fcac911e8b84298d319400e2265730c62460b40501b3c1668be950f8"
},
{
"mode": "100644",
"owner": "package-surface-domain-barrel",
"path": "packages/todo/domain/src/index.ts",
"sourceDigest": "sha256:e003ca3479b14b5d3f94fdd0c8db7ffe20269aad29628e0d09f7f367cd2cc7f8"
},
{
"mode": "100644",
"owner": "todo-model-packages-todo-domain-tests-todo-test-ts",
"path": "packages/todo/domain/tests/todo.test.ts",
"sourceDigest": "sha256:99e4f463a497275f7bf6dacbe0402dde1b054dbe8e0568256dccd6a6a5448514"
},
{
"mode": "100644",
"owner": "package-surface-infrastructure-manifest",
"path": "packages/todo/infrastructure/package.json",
"sourceDigest": "sha256:5a28a70e873ee887a1819bafa2f11ae4d47cd44aaaac410d621493346d186889"
},
{
"mode": "100644",
"owner": "package-surface-infrastructure-barrel",
"path": "packages/todo/infrastructure/src/index.ts",
"sourceDigest": "sha256:73bfa06f4aac3cfee6fad9425490d86dbfab70ec79a69d2559a41c93bd391e35"
},
{
"mode": "100644",
"owner": "todo-integration-adapter-packages-todo-infrastructure-tests-todo-runtime-test-ts",
"path": "packages/todo/infrastructure/tests/todo-runtime.test.ts",
"sourceDigest": "sha256:8a305172097e942a07398ee5bfeec90e2a84a07ea5b97050138c8549dcf9949a"
},
{
"mode": "100644",
"owner": "workspace-surface-pnpm-workspace-yaml",
"path": "pnpm-workspace.yaml",
"sourceDigest": "sha256:38153e8982dc83ad4f576c62dd12e80fd222668b7ebd29c9c89092a41bf10544"
},
{
"mode": "100644",
"owner": "workspace-surface-tsconfig-build-json",
"path": "tsconfig.build.json",
"sourceDigest": "sha256:a12ff29d34e79ed58b9d9540572968f6f32721482f776500b2f2495810926138"
},
{
"mode": "100644",
"owner": "workspace-surface-vitest-config-mts",
"path": "vitest.config.mts",
"sourceDigest": "sha256:3d6177cfd34114394a841bb24fdf661f2879b0b75cfe2d793ae414d2191081de"
}
],
"provenanceDigest": "sha256:62a41dcb635db627d5531abb58266493cfb56367960b6175ff2908132fa0dea9",
"version": "effectify.app-builder-replay-provenance/1"
}
}
55 changes: 55 additions & 0 deletions examples/app-builder-todo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# App Builder Todo Showcase

This directory is the canonical Todo workspace produced by the installed public App Builder `generate` command. It is a committed inspection fixture with its own pnpm and Nx workspace; root pnpm, Nx, and formatter discovery intentionally exclude it.

## Canonical provenance

The exact request, replay provenance, output identities, file modes, and source digests are recorded in [`.effectify/app-builder/showcase.json`](.effectify/app-builder/showcase.json). The generated set is limited to the 18 paths named by `provenance.outputIdentities`; this README and the receipt are authored freeze metadata.

Regeneration is allowed only after `Pr130Regeneration.requirePr130RegenerationEligibility` accepts the complete pinned Unit 1–6 proof and the current frozen PR #130 head.

## Public regeneration path

From the repository root, build the public packages and run the isolated checker:

```sh
pnpm install --frozen-lockfile
pnpm nx run @effectify/app-builder-cli:build --skipNxCache
output="$(mktemp -d)/app-builder-todo"
node tools/check-app-builder-showcase.mjs \
--expected examples/app-builder-todo \
--output "$output"
```

The checker packs the public contracts, generation, and CLI packages; installs those tarballs into an isolated driver; invokes its `node_modules/.bin/effectify-app-builder generate --events=jsonl`; and compares every output identity byte-for-byte and mode-for-mode. It never writes to this committed directory. If intentional canonical drift is reported, independently validate the gate and generated workspace before replacing only the receipt-listed generated paths.

## Standalone verification

In an isolated copy, materialize a lockfile and run the generated workspace through its package manager:

```sh
pnpm install --lockfile-only
pnpm install --frozen-lockfile
pnpm exec nx graph --print
pnpm exec nx run-many \
--targets=test,typecheck,build \
--projects=@effectify/todo-domain,@effectify/todo-application,@effectify/todo-infrastructure,@effectify/todo-cli
```

The exact project graph is:

- `@effectify/todo-domain`
- `@effectify/todo-application`
- `@effectify/todo-infrastructure`
- `@effectify/todo-cli`

## Acceptance evidence

| Boundary | Executable evidence |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| Unit 7 eligibility | Built `Pr130Regeneration.requirePr130RegenerationEligibility` with complete pinned Unit 1–6 identities and the frozen PR #130 head |
| Installed public generation | Packed registry-installed public CLI invocation with JSONL terminal evidence |
| Generated workspace | Exact four-project Nx graph; all project test, typecheck, and build targets |
| Runtime | Live Todo add, complete, remove, empty-list, and durable empty-state proof |
| Replay | Second installed public invocation writes zero paths and leaves all generated bytes and modes unchanged |
| Drift and exclusion | `tools/check-app-builder-showcase.mjs`, CLI showcase tests, root Nx/pnpm exclusion, format check, sync check, and `git diff --check` |
19 changes: 19 additions & 0 deletions examples/app-builder-todo/apps/todo-cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@effectify/todo-cli",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"build": "pnpm -w exec tsc -p tsconfig.build.json",
"test": "pnpm -w exec vitest run --config vitest.config.mts",
"typecheck": "pnpm -w exec tsc --noEmit -p tsconfig.build.json"
},
"dependencies": {
"effect": "catalog:",
"@effectify/todo-infrastructure": "workspace:*",
"@effectify/todo-application": "workspace:*",
"@effectify/todo-domain": "workspace:*"
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export const cliTemplate = String.raw`import * as Effect from "effect/Effect"
import * as Effect from "effect/Effect"
import * as Layer from "effect/Layer"
import * as Application from "../../../packages/todo/application/src/index.js"
import * as Infrastructure from "../../../packages/todo/infrastructure/src/index.js"
import type { TodoEvent } from "../../../packages/todo/domain/src/index.js"
import * as Application from "@effectify/todo-application"
import type { TodoEvent } from "@effectify/todo-domain"
import * as Infrastructure from "@effectify/todo-infrastructure"

export const createTestRuntime = (input: { readonly ids: ReadonlyArray<string>; readonly now: string }) => Effect.gen(function* () {
const layer = Application.layer.pipe(Layer.provideMerge(Infrastructure.testLayer(input)))
Expand All @@ -19,4 +19,3 @@ export const renderEvent = (event: TodoEvent): string =>
event._tag === "TodoAdded" ? "added:" + event.todo.id + ":" + event.todo.text
: event._tag === "TodoCompleted" ? "completed:" + event.todo.id + ":" + event.todo.text
: "removed:" + event.todo.id + ":" + event.todo.text
`
9 changes: 9 additions & 0 deletions examples/app-builder-todo/apps/todo-cli/tests/todo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { expect, it } from "@effect/vitest"
import * as Effect from "effect/Effect"
import { createTestRuntime } from "../src/index.js"

it.effect("runs deterministic Todo behavior", () => Effect.gen(function* () {
const todo = yield* createTestRuntime({ ids: ["todo-1"], now: "2026-01-01T00:00:00.000Z" })
expect(yield* todo.list()).toEqual([])
expect((yield* todo.add("write tests")).id).toBe("todo-1")
}))
21 changes: 21 additions & 0 deletions examples/app-builder-todo/nx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"defaultBase": "HEAD",
"plugins": [
{
"plugin": "@nx/js/typescript",
"exclude": ["tsconfig.build.json"],
"options": {
"typecheck": {
"targetName": "typecheck"
}
}
},
{
"plugin": "@nx/vitest",
"exclude": ["vitest.config.mts"],
"options": {
"testTargetName": "test"
}
}
]
}
20 changes: 20 additions & 0 deletions examples/app-builder-todo/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "@effectify/todo-workspace",
"packageManager": "pnpm@10.14.0",
"private": true,
"scripts": {
"build": "pnpm exec tsc -p tsconfig.build.json",
"test": "pnpm exec vitest run --config vitest.config.mts",
"typecheck": "pnpm exec tsc --noEmit -p tsconfig.build.json"
},
"devDependencies": {
"@effect/vitest": "catalog:",
"@types/node": "catalog:",
"@nx/js": "catalog:",
"@nx/vitest": "23.1.0",
"effect": "catalog:",
"nx": "23.1.0",
"typescript": "catalog:",
"vitest": "catalog:"
}
}
17 changes: 17 additions & 0 deletions examples/app-builder-todo/packages/todo/application/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@effectify/todo-application",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"build": "pnpm -w exec tsc -p tsconfig.build.json",
"test": "pnpm -w exec vitest run --config vitest.config.mts",
"typecheck": "pnpm -w exec tsc --noEmit -p tsconfig.build.json"
},
"dependencies": {
"effect": "catalog:",
"@effectify/todo-domain": "workspace:*"
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
export const applicationTemplate = String.raw`import * as Context from "effect/Context"
import * as Context from "effect/Context"
import * as Effect from "effect/Effect"
import * as Layer from "effect/Layer"
import * as Option from "effect/Option"
import * as Schema from "effect/Schema"
import { TodoEvent, TodoId, type Todo, type TodoEvent as Event, TodoAlreadyCompleted, TodoIdExhausted, TodoNotFound, TodoPersistenceError, TodoTextInvalid } from "../../domain/src/index.js"
import { TodoEvent, TodoId, type Todo, type TodoEvent as Event, TodoAlreadyCompleted, TodoIdExhausted, TodoNotFound, TodoPersistenceError, TodoTextInvalid } from "@effectify/todo-domain"

export interface TodoRepositoryApi {
readonly find: (id: TodoId) => Effect.Effect<Todo | undefined, TodoPersistenceError>
Expand Down Expand Up @@ -65,4 +65,3 @@ export const layer = Layer.effect(TodoApplication, Effect.gen(function* () {
})
return TodoApplication.of({ add, complete, list: repository.list, remove })
}))
`
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { TodoApplication } from "./index.js"
16 changes: 16 additions & 0 deletions examples/app-builder-todo/packages/todo/domain/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "@effectify/todo-domain",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"build": "pnpm -w exec tsc -p tsconfig.build.json",
"test": "pnpm -w exec vitest run --config vitest.config.mts",
"typecheck": "pnpm -w exec tsc --noEmit -p tsconfig.build.json"
},
"dependencies": {
"effect": "catalog:"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type { TodoEvent } from "./index.js"
Loading
Loading