Scaffold VibeGeometry

This commit is contained in:
Meta Vens 2026-05-04 12:20:23 +01:00
commit f8b4e5e09e
21 changed files with 1285 additions and 0 deletions

9
.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
__pycache__/
*.pyc
.venv/
node_modules/
state/scratch-compaction-*.md
experiments/renders/
experiments/blender-cache/
*.blend1
*.blend2

144
AGENTS.md Normal file
View file

@ -0,0 +1,144 @@
# VibeGeometry Instructions
## Project Purpose
VibeGeometry is a research and implementation home for agent-authored Blender
scene generation.
The core problem is not "make a Python script that creates something shiny."
The core problem is building a pipeline where an agent can understand a visual
target, choose a procedural geometry strategy, construct inspectable Blender
5.0+ geometry-node graphs through Python, render the result, and revise from
evidence without accumulating clever junk.
## Canonical State
- Treat `state/map.yaml` as the canonical project map.
- Treat `state/scratch.md` as disposable working memory for one bounded
subgoal.
- Treat `state/evidence.jsonl` as the distilled durable ledger of what was
learned, verified, rejected, or accepted.
- Treat `notes/vibegeometry-implementation-plan.md` as the current
implementation plan.
- Update `state/map.yaml` when project understanding changes.
- Add evidence after meaningful research, implementation, verification, or
rejected paths, but keep it distilled. Routine proof belongs in git history,
renders, smoke artifacts, or targeted logs unless it changes what the next
session should believe.
- Do not turn notes, map, handoff, and evidence into four copies of the same
brain. Distinct jobs or bust.
## Important Paths
- Project root: `E:\Projects\VibeGeometry`
- Canonical map: `E:\Projects\VibeGeometry\state\map.yaml`
- Scratch surface: `E:\Projects\VibeGeometry\state\scratch.md`
- Distilled evidence ledger: `E:\Projects\VibeGeometry\state\evidence.jsonl`
- Branch ledger: `E:\Projects\VibeGeometry\state\branches.json`
- Handoff summary: `E:\Projects\VibeGeometry\notes\fresh-workspace-handoff.md`
- System map: `E:\Projects\VibeGeometry\notes\vibegeometry-current-system-map.md`
- Implementation plan: `E:\Projects\VibeGeometry\notes\vibegeometry-implementation-plan.md`
- Architecture rationale: `E:\Projects\VibeGeometry\notes\architecture-rationale.md`
- State CLI: `E:\Projects\VibeGeometry\tools\vibegeometry_state.py`
- Pre-compaction helper: `E:\Projects\VibeGeometry\tools\vibegeometry_prepare_compaction.py`
## Useful Commands
Use the bundled Python runtime if `python` is not on PATH:
```powershell
npm run state:status
npm run state:prepare-compaction
& 'C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe' '.\tools\vibegeometry_state.py' add-evidence --type research --status ok --note '...'
& 'C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe' '.\tools\vibegeometry_state.py' add-branch --id branch-id --hypothesis '...'
```
## Session Bootstrap And Re-entry Protocol
On fresh session load, do this before implementation:
1. read:
- `state/map.yaml`
- `notes/fresh-workspace-handoff.md`
- `notes/vibegeometry-current-system-map.md`
- `notes/vibegeometry-implementation-plan.md`
2. run:
- `npm run state:status`
3. restate the current next action from the persisted state before starting
edits
4. if the user only asked to rehydrate or reorient, stop after orientation and
wait for an explicit continue instruction
After compaction, resume, or suspicious continuity loss:
1. rerun `npm run state:status`
2. reread `state/map.yaml` and `notes/fresh-workspace-handoff.md`
3. treat the persisted next action as authoritative unless fresh repo evidence
contradicts it
When context pressure is clearly rising:
1. stop broad exploration
2. narrow the active move to one bounded pipeline organ
3. persist map or handoff updates, plus distilled evidence only when the lesson
changes future belief, before the blackout lands
When the user says to prepare for imminent compaction:
1. immediately write hot live context from memory into a new
`state/scratch-compaction-<guid>.md` file before reading files, running
status checks, or doing tidy persistence work
2. run `tools/vibegeometry_prepare_compaction.py` before editing persistence
surfaces
3. use its warnings as the checklist for map, handoff, scratch, evidence, and
git hygiene
4. update only the state that actually needs to change
5. after hot context has been folded into canonical persisted state, delete the
`state/scratch-compaction-<guid>.md` file so stale emergency memory does not
linger
6. run `tools/vibegeometry_prepare_compaction.py` again after edits and scratch
cleanup
7. fix errors, address warnings, and commit the completed persistence pass
unless the work is deliberately mid-surgery
## Operating Discipline
- Before substantial edits, restate the current mechanism and intended change.
- Prefer one clear hypothesis per iteration.
- Verify with checks that reflect the visual and procedural goal, not just
proxy success.
- Revert or discard changes that do not clearly improve the target.
- If the diff grows while understanding shrinks, stop implementation and switch
to diagnosis.
- Keep maps and prose together; do not replace useful maps with prose-only
explanations.
- Commit completed work before it rots in the worktree unless the task is
deliberately mid-surgery or the user asked to leave changes uncommitted.
- Push after major completed passes when a remote exists, unless the user asked
not to or there is a concrete reason to keep the commit local for a moment.
- Before handoff, compaction, or phase boundaries, sync `state/map.yaml`, add
distilled evidence when the lesson changes future belief, refresh
`notes/fresh-workspace-handoff.md`, and make the next action explicit.
## Blender And Geometry-Node Doctrine
- Target Blender 5.0+ unless a task explicitly chooses another version.
- Prefer geometry nodes for procedural geometry and Python for construction,
orchestration, parameterization, render setup, and validation.
- Keep generated node groups named, inspectable, and reusable. A script that
creates an opaque graph is just a new kind of mess with sockets.
- Preserve attribute flow explicitly. Name important attributes, document where
they are created, transformed, consumed, and exposed.
- Prefer proven Blender node idioms and current API guidance over invented
graph tricks.
- Treat reference images as evidence-bearing inputs. Record what visual traits
are being targeted: silhouette, repetition, surface breakup, scale language,
material response, lighting, camera, and color.
- Render review is mandatory for visual claims. A script that runs is not a
scene that works.
- When a generated graph misses the target, revise one hypothesis at a time:
geometry structure, distribution logic, material, lighting, camera, or scale.
- Keep failed render attempts only when they teach a durable lesson. Otherwise
archive lightly or delete them; the repo is not a museum of almost.
- Do not bury visual intent inside Python constants. Scene briefs, graph
manifests, and review artifacts should expose what the agent meant to build.

109
README.md Normal file
View file

@ -0,0 +1,109 @@
# VibeGeometry
VibeGeometry is a research and implementation home for agent-authored Blender
scene generation.
The project exists to build a pipeline where Codex, or any other sufficiently
useful local agent, can take reference images and visual instructions, plan a
procedural geometry strategy, generate Blender Python scripts, construct
complex geometry-node graphs, render the result, and revise from evidence
instead of shrugging confidently at a pile of decorative node spaghetti.
The target is Blender 5.0 and later. The repo treats geometry nodes as the
main modeling surface, Python as the graph construction and orchestration
language, and rendered output as the evidence loop.
## Core Idea
VibeGeometry separates five things that are easy to smear together:
1. **Visual target**
What the result should look like, grounded in reference images and explicit
instructions.
2. **Geometry strategy**
The procedural plan: primitives, fields, instances, curves, surfaces,
simulation zones, attributes, materials, and camera composition.
3. **Node graph contract**
The legible graph structure that should exist in Blender, including named
groups, exposed parameters, attribute flow, and reusable node idioms.
4. **Python builder**
The script that creates the scene, builds node groups, links sockets,
assigns materials, places cameras and lights, and saves or renders the
`.blend` result.
5. **Render review**
What the actual output proves: visual match, graph sanity, missing geometry,
material errors, performance pain, and rejected approaches.
The script is not the design. The render is not the truth. The node graph is
the machine, and the repo exists to make that machine inspectable.
## Current Status
This is an initial scaffold.
Currently present:
- persistence surfaces for re-entry, evidence, planning, and compaction hygiene
- starter architecture notes for the Blender scene-generation pipeline
- a repo structure for schemas, prompts, examples, experiments, scripts, tools,
notes, and durable state
- small state inspection and pre-compaction helpers
Not present yet:
- Blender Python graph builder library
- scene brief schema
- node graph manifest schema
- render review schema
- reference-image ingestion helpers
- automated Blender render runner
- visual comparison or reviewer loop
## Repo Tour
- `AGENTS.md`: operating discipline for humans and agents
- `state/map.yaml`: canonical project map and current next action
- `state/scratch.md`: disposable working memory for one bounded subgoal
- `state/evidence.jsonl`: distilled belief-changing evidence
- `state/branches.json`: active or closed investigation branches
- `notes/fresh-workspace-handoff.md`: compact re-entry packet
- `notes/vibegeometry-current-system-map.md`: current repo machinery
- `notes/vibegeometry-implementation-plan.md`: implementation sequence
- `notes/architecture-rationale.md`: why the persistence surfaces exist
- `docs/architecture/`: pipeline contracts and design notes
- `docs/research/`: Blender geometry-node research notes
- `schemas/`: future JSON schema contracts
- `examples/`: future scene briefs, generated scripts, and review artifacts
- `experiments/`: disposable render attempts and captures
- `prompts/`: reusable agent prompts for scene planning and review
- `scripts/`: utility scripts that are not core persistence machinery
- `tools/vibegeometry_state.py`: state inspection and evidence helper
- `tools/vibegeometry_prepare_compaction.py`: pre-compaction audit helper
## Useful Commands
```powershell
npm run state:status
npm run state:prepare-compaction
```
To add distilled evidence:
```powershell
& 'C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe' '.\tools\vibegeometry_state.py' add-evidence --type design --status accepted --note '...'
```
## Best Starting Points
- `AGENTS.md`
- `state/map.yaml`
- `notes/vibegeometry-current-system-map.md`
- `notes/vibegeometry-implementation-plan.md`
- `docs/architecture/pipeline-overview.md`
For Codex-driven work, read `AGENTS.md` before changing the repo. It contains
the rules that keep the project from turning into a confident procedural hairball.

View file

@ -0,0 +1,64 @@
# Pipeline Overview
VibeGeometry's intended pipeline turns visual intent into Blender geometry-node
machinery, then uses rendered evidence to decide what actually worked.
```text
reference images + instructions
-> scene brief
-> visual trait extraction
-> geometry strategy
-> node graph manifest
-> Blender Python builder
-> generated scene and render
-> render review
```
## Scene Brief
The scene brief is the target contract. It should say what the scene needs to
look like before it says how to build it.
Expected contents:
- reference image paths or URLs
- user instructions
- target visual traits
- constraints and exclusions
- camera and composition notes
- material and lighting expectations
- acceptance criteria
- open questions
## Geometry Strategy
The geometry strategy is plain-language reasoning about how the target can be
made procedurally. It should explain primitives, distributions, curves,
surfaces, instances, attributes, material variation, and scale relationships.
This is where the agent proves it knows what machine it is about to build.
## Node Graph Manifest
The node graph manifest is the inspectable design for Blender. It should name:
- geometry node groups
- exposed sockets and default values
- attribute names and where they flow
- modifiers and object targets
- materials and shader parameters
- reusable graph idioms
- performance risks or version-sensitive API assumptions
## Python Builder
The Python builder turns the manifest into a Blender scene. Python owns
construction, orchestration, render setup, and file output. It should not hide
the procedural design in ad hoc mesh mutation unless the scene brief explicitly
requires that escape hatch.
## Render Review
Render review compares output to the target. It should identify visual hits,
misses, graph problems, material issues, camera/lighting errors, and the next
single hypothesis to test.

View file

@ -0,0 +1,15 @@
# Blender 5 Geometry Nodes Research
This file is the placeholder for current Blender 5.0+ API and geometry-node
best-practice notes.
Use it for distilled findings only:
- confirmed API changes
- node construction gotchas
- geometry-node idioms worth reusing
- rejected approaches that looked plausible and failed
- links to official Blender documentation or source material
Do not store generic tutorial mush here. The internet already has enough of
that and seems proud of itself.

11
examples/README.md Normal file
View file

@ -0,0 +1,11 @@
# Examples
Reviewed example artifacts live here.
Expected subfolders:
- `scene-briefs/`: target descriptions and acceptance criteria
- `blender/`: generated scripts, manifests, and small `.blend` receipts when useful
Keep disposable runs in `experiments/` until they teach something worth
promoting.

7
experiments/README.md Normal file
View file

@ -0,0 +1,7 @@
# Experiments
Disposable render attempts, captures, and test scenes live here.
Promote only reviewed examples or durable lessons. Failed visual attempts are
useful when they change future belief; otherwise they are just ballast with a
timestamp.

View file

@ -0,0 +1,86 @@
# Architecture Rationale
This note explains why VibeGeometry uses explicit state surfaces. It is not the
current implementation map and not the roadmap. For those, read:
- `state/map.yaml`
- `notes/fresh-workspace-handoff.md`
- `notes/vibegeometry-implementation-plan.md`
- `notes/vibegeometry-current-system-map.md`
## Core Failure
The central failure mode is local plausibility without global coherence.
An agent can write plausible Blender Python, create a large node graph, and
still lose the visual machine. Geometry-node work is especially good at this:
the script runs, the graph exists, the render has objects in it, and somehow
the target has been replaced by decorative procedural fog with a confident
variable name.
The answer is explicit structure:
- slow-changing map
- disposable scratch
- distilled evidence
- compact handoff
- implementation plan that is not pretending to be a second map
- ruthless deletion when code, graph, or memory stops serving the target
## State Split
VibeGeometry treats different kinds of cognition as different artifacts:
- `map`: canonical system model, invariants, accepted design, rejected paths
- `scratch`: temporary local reasoning for one bounded subgoal
- `evidence`: distilled proof, decisions, rejected paths, and scars that change future belief
- `system map`: source-grounded explanation of the live repo machinery
- `handoff`: compact re-entry packet
- `plan`: distilled forward implementation direction
- `output`: user-visible replies, code edits, commits, renders, and verification artifacts
The split matters because Blender scene generation has many seductive proxies:
valid Python, non-empty renders, high node counts, pretty materials, and
parameter sliders. None of those prove that the graph still serves the visual
target.
## Pipeline Principle
The working loop should be:
```text
reference images and instructions
-> scene brief
-> visual trait extraction
-> geometry strategy
-> node graph manifest
-> Blender Python builder
-> render evidence
-> review against target
-> revise one hypothesis
```
The graph is allowed to be complex. The intent is not. If the agent cannot
describe what geometry flows through each stage and why that stage belongs,
the next move is explanation or simplification, not another compensating node
group with a tragic little name.
## Compact-Rehydrate-Reorient
Compaction is a real state transition, not a cosmetic cleanup.
Before compaction, VibeGeometry should preserve:
- current objective and active subgoal
- latest stable map
- distilled evidence or rejected paths that change future belief
- open questions, blockers, and next action
After rehydrating, VibeGeometry should:
- reread canonical state instead of trusting prompt residue
- restate the next action from persisted state
- continue only when instructed or when the task explicitly calls for it
- avoid broad implementation until the current pipeline organ is understood
The aim is simple: the next waking thing should find coals instead of ash.

View file

@ -0,0 +1,64 @@
# Fresh Workspace Handoff
## Immediate Re-entry Instruction
Read `state/map.yaml`, this file, `notes/vibegeometry-current-system-map.md`,
and `notes/vibegeometry-implementation-plan.md`, then run:
```powershell
npm run state:status
```
Restate the persisted next action before editing.
Do not continue implementation automatically from a rehydrate-only request.
Do not trust this file for the exact live HEAD. Use git for volatile truth:
```powershell
git status --short --branch
git log --oneline -5
```
## Current State
VibeGeometry is an initial scaffold. It has persistence machinery, project
orientation docs, and helper tools, but no Blender builder library or artifact
schemas yet.
The current next action is to draft the scene brief and node graph manifest
schemas, then validate them against one tiny reference-driven example.
## Important Invariants
- Target Blender 5.0+ unless the task says otherwise.
- Geometry nodes are the primary procedural geometry surface.
- Python constructs and orchestrates graphs; it should not bury the design in
opaque imperative mesh generation.
- Visual target evidence must remain attached to scene briefs and render
review.
- A graph is not accepted just because it runs. Render evidence decides.
- Node groups, exposed sockets, and attributes need names clear enough for
future agents to inspect and modify.
- Add evidence only when it changes future belief.
## Open Questions
- What exact JSON shape should the scene brief use for references,
instructions, target traits, and acceptance criteria?
- What should the first node graph manifest schema require versus merely allow?
- Which Blender 5.0+ APIs and node construction helpers should become the
first stable builder layer?
- What is the smallest useful render-review artifact?
## Compaction Notes
When preparing for imminent compaction, write hot memory first to
`state/scratch-compaction-<guid>.md`, then run:
```powershell
npm run state:prepare-compaction
```
Fold only durable lessons into canonical state and delete the temporary
scratch-compaction file after it has served its purpose.

View file

@ -0,0 +1,59 @@
# VibeGeometry Current System Map
VibeGeometry does not have a Blender runtime yet. The live system is a
persistence scaffold plus a planned artifact pipeline for agent-authored
geometry-node scenes.
## Control Flow
1. Rehydrate from `state/map.yaml`, `notes/fresh-workspace-handoff.md`, this
file, and `notes/vibegeometry-implementation-plan.md`.
2. Run `npm run state:status`.
3. Work one bounded pipeline organ.
4. Validate the seam that matters.
5. Persist only belief-changing evidence.
6. Commit completed work.
## Core Surfaces
- `state/map.yaml`: canonical mission, boundaries, live architecture, next action.
- `state/evidence.jsonl`: distilled belief-changing evidence only.
- `state/branches.json`: active and closed research or implementation branches.
- `notes/fresh-workspace-handoff.md`: compact re-entry packet.
- `notes/vibegeometry-implementation-plan.md`: near-term implementation sequence.
- `docs/architecture/`: detailed contracts and rationale.
## Planned Artifact Pipeline
```text
reference images + user instructions
-> scene brief
-> target visual trait extraction
-> geometry strategy
-> node graph manifest
-> Blender Python builder script
-> generated .blend scene
-> render output
-> render review and graph audit
-> accepted example or revised hypothesis
```
## Missing Or Incomplete Organs
- Scene brief schema
- Node graph manifest schema
- Render review schema
- Blender Python builder helpers
- Geometry-node idiom library
- Headless Blender runner
- Reference-image storage and provenance rules
- Render comparison and review workflow
- Example scene fixtures
## Current North Star
Build the smallest reliable loop where an agent can describe a target, plan
procedural geometry, generate a legible Blender node graph through Python,
render it, and use review evidence to improve the next iteration. The loop
should reward coherent geometry flow, not just scripts that successfully make
Blender emit pixels.

View file

@ -0,0 +1,67 @@
# VibeGeometry Implementation Plan
## Current Phase
Build the first reliable scaffold for agent-authored Blender geometry-node
scenes. The immediate target is not a full procedural art factory. It is a
clean artifact loop with enough structure that agents can generate, inspect,
render, and revise graphs without losing the visual target.
Planning belongs in this file and the handoff/map state surfaces. Architecture
docs should describe durable contracts, boundaries, and data shapes; they
should not carry live next-action clutter.
## Near-Term Sequence
1. Define the scene brief contract.
- Capture references, text instructions, target visual traits, constraints,
camera/composition requirements, acceptance criteria, and known unknowns.
- Preserve source paths or URLs for every reference image.
- Separate visual target description from implementation strategy.
2. Define the node graph manifest contract.
- Describe node groups, sockets, exposed controls, modifiers, attribute
names, field flow, materials, and dependencies.
- Require enough naming and rationale for future agents to inspect the graph.
- Keep graph contracts Blender 5.0+ oriented.
3. Build a minimal Python builder seam.
- Start with one tiny scene that proves graph construction, material setup,
camera, lighting, save, and render orchestration.
- Prefer small composable helpers for node group creation and socket linking.
- Avoid a giant builder abstraction until repeated graph idioms demand it.
4. Build render review artifacts.
- Store render path, scene brief id, manifest id, script path, Blender
version, review status, visual misses, graph issues, and next hypothesis.
- Treat rendered output as the acceptance surface for visual claims.
5. Add geometry-node idiom notes and examples.
- Document established patterns for distribution, instancing, curve/surface
generation, attributes, material variation, simulation zones, and exposed
parameters.
- Keep examples small enough to inspect.
- Promote only graph patterns that survive review.
6. Generalize after the loop works.
- Add reference-image ingestion helpers.
- Add headless batch rendering.
- Add visual comparison or reviewer prompts.
- Add richer examples only after the contracts stop sliding.
## Deferred
- Large procedural asset library
- Automated visual scoring beyond basic review artifacts
- Fine-tuned graph-generation models
- Blender add-on packaging
- Complex simulation-node workflows before the basic graph loop is stable
## Discipline
- Work one bounded organ at a time.
- Explain target-to-graph data flow before broad refactors.
- Prefer explicit manifests and render evidence over implicit agent memory.
- If the diff grows while understanding shrinks, stop and simplify.
- Keep history in git; keep persisted state focused on what future sessions
need to believe.

10
package.json Normal file
View file

@ -0,0 +1,10 @@
{
"name": "vibegeometry",
"private": true,
"version": "0.0.0",
"description": "Persistent research repo for agent-authored Blender geometry-node scene generation.",
"scripts": {
"state:status": "\"C:\\Users\\Meta\\.cache\\codex-runtimes\\codex-primary-runtime\\dependencies\\python\\python.exe\" .\\tools\\vibegeometry_state.py status",
"state:prepare-compaction": "\"C:\\Users\\Meta\\.cache\\codex-runtimes\\codex-primary-runtime\\dependencies\\python\\python.exe\" .\\tools\\vibegeometry_prepare_compaction.py"
}
}

14
prompts/README.md Normal file
View file

@ -0,0 +1,14 @@
# Prompts
Reusable prompts for VibeGeometry agents live here.
Expected prompt families:
- scene brief extraction from reference images and instructions
- geometry strategy planning
- node graph manifest drafting
- Blender Python generation
- render review and graph audit
Keep prompts tied to explicit artifacts. A prompt that only says "make it
better" is a small bureaucracy for producing fog.

11
schemas/README.md Normal file
View file

@ -0,0 +1,11 @@
# Schemas
Future JSON schemas live here.
Planned contracts:
- `scene-brief.schema.json`
- `node-graph-manifest.schema.json`
- `render-review.schema.json`
Add schemas when the first concrete example needs validation.

7
scripts/README.md Normal file
View file

@ -0,0 +1,7 @@
# Scripts
Utility scripts live here.
Persistence machinery belongs in `tools/`. Blender runners, render helpers,
image prep utilities, and batch experiment scripts can live here once they
exist.

3
state/branches.json Normal file
View file

@ -0,0 +1,3 @@
{
"branches": []
}

1
state/evidence.jsonl Normal file
View file

@ -0,0 +1 @@
{"ts":"2026-05-04T00:00:00+00:00","type":"scaffold","status":"accepted","note":"Initialized VibeGeometry with Epiphany-style persistence surfaces adapted to Blender 5.0+ geometry-node scene generation."}

80
state/map.yaml Normal file
View file

@ -0,0 +1,80 @@
experiment:
name: VibeGeometry
status: incubation
phase: phase_0_scaffold
premise: >
Build a durable research and implementation home for agent-authored
Blender 5.0+ scenes that use Python to construct complex geometry-node
graphs for procedural geometry matching reference images and instructions.
mission:
primary: >
Enable Codex and other agents to generate inspectable, reusable Blender
geometry-node graphs that produce specific visual results from reference
images, text instructions, and iterative render review.
near_term: >
Define the scene brief, node graph manifest, Python builder, render runner,
and review loop before building a large library of graph idioms.
hard_boundaries:
- Blender 5.0+ is the target runtime unless a task explicitly chooses another version.
- Geometry nodes are the primary procedural modeling surface.
- Python scripts should construct and orchestrate node graphs, not hide the design in uninspectable imperative mesh code.
- Visual targets must preserve reference-image evidence and explicit instructions.
- A graph that runs is not accepted until rendered output is inspected against the target.
- Node groups must be named, parameterized, and legible enough for future agents to modify.
- Attribute creation, transformation, and consumption must be explicit.
- Prefer established Blender node idioms and current API guidance over bespoke graph inventions.
- Keep failed experiments only when they change future belief.
- Do not let proxy checks replace visual review.
live_architecture:
pipeline:
- Ingest reference images and instructions into a scene brief.
- Extract target visual traits: silhouette, repetition, distribution, material response, surface breakup, camera, lighting, palette, and scale cues.
- Plan a geometry strategy in plain language before scripting.
- Emit a node graph manifest describing groups, sockets, attributes, fields, modifiers, materials, and exposed controls.
- Generate Blender Python that builds the scene and node graphs from the manifest.
- Run Blender headlessly or interactively to produce `.blend` files and render evidence.
- Review renders against the visual target and record accepted, rejected, or revised hypotheses.
persistence:
- `state/map.yaml` stores canonical mission, boundaries, architecture, artifacts, and next action.
- `state/scratch.md` stores one bounded active thought surface.
- `state/evidence.jsonl` stores distilled belief-changing records.
- `notes/fresh-workspace-handoff.md` stores compact re-entry instructions.
- `notes/vibegeometry-current-system-map.md` explains the live repo machinery.
- `notes/vibegeometry-implementation-plan.md` stores the near-term build sequence.
active_subgoals:
- Keep the pipeline legible from visual target to graph strategy to Python builder to render review.
- Define artifact schemas before generating many example scenes.
- Build a minimal Blender runner only after the scene brief and graph manifest contracts are clear.
- Collect Blender 5.0+ geometry-node API and best-practice evidence before hardening graph builders.
- Preserve one-hypothesis iteration when visual results miss the target.
current_artifacts:
canonical_docs:
- notes/architecture-rationale.md
- notes/fresh-workspace-handoff.md
- notes/vibegeometry-current-system-map.md
- notes/vibegeometry-implementation-plan.md
- docs/architecture/pipeline-overview.md
tools:
- tools/vibegeometry_state.py
- tools/vibegeometry_prepare_compaction.py
pending_contracts:
- schemas/scene-brief.schema.json
- schemas/node-graph-manifest.schema.json
- schemas/render-review.schema.json
data_flow:
- Rehydrate from map, handoff, system map, implementation plan, and `npm run state:status`.
- Work one bounded pipeline organ.
- Verify the seam that matters.
- Add distilled evidence only when future belief changes.
- Commit completed work.
- If state grows faster than understanding, cut it back.
current_status:
summary: VibeGeometry has an initial Epiphany-style persistence scaffold adapted for Blender geometry-node scene generation, with project-neutral state, handoff, architecture notes, and helper tools.
next_action: Draft the scene brief and node graph manifest schemas, then validate them against one tiny reference-driven example.

11
state/scratch.md Normal file
View file

@ -0,0 +1,11 @@
# Scratch
## Current Subgoal
No active scratch subgoal.
## Notes
Use this file for temporary reasoning during one bounded task. Fold durable
lessons into `state/map.yaml`, `state/evidence.jsonl`, or the relevant note,
then clear this surface.

View file

@ -0,0 +1,334 @@
from __future__ import annotations
import argparse
import json
import re
import subprocess
from dataclasses import dataclass
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
STATE_DIR = ROOT / "state"
NOTES_DIR = ROOT / "notes"
MAP_PATH = STATE_DIR / "map.yaml"
SCRATCH_PATH = STATE_DIR / "scratch.md"
BRANCHES_PATH = STATE_DIR / "branches.json"
EVIDENCE_PATH = STATE_DIR / "evidence.jsonl"
HANDOFF_PATH = NOTES_DIR / "fresh-workspace-handoff.md"
PLAN_PATH = NOTES_DIR / "vibegeometry-implementation-plan.md"
SYSTEM_MAP_PATH = NOTES_DIR / "vibegeometry-current-system-map.md"
AGENTS_PATH = ROOT / "AGENTS.md"
@dataclass
class Finding:
level: str
message: str
def read_text(path: Path) -> str:
return path.read_text(encoding="utf-8")
def run_git(args: list[str]) -> str:
completed = subprocess.run(
["git", *args],
cwd=ROOT,
check=True,
capture_output=True,
text=True,
)
return completed.stdout.strip()
def extract_map_field(name: str) -> str | None:
prefix = f" {name}:"
for line in read_text(MAP_PATH).splitlines():
if line.startswith(prefix):
return line.split(":", 1)[1].strip()
return None
def extract_active_subgoals() -> list[str]:
lines = read_text(MAP_PATH).splitlines()
results: list[str] = []
in_section = False
for line in lines:
if not in_section:
if line.strip() == "active_subgoals:":
in_section = True
continue
if line.startswith(" - "):
results.append(line[4:].strip())
continue
if line and not line.startswith(" "):
break
return results
def current_scratch_subgoal() -> str | None:
lines = read_text(SCRATCH_PATH).splitlines()
for index, line in enumerate(lines):
if line.strip() != "## Current Subgoal":
continue
for candidate in lines[index + 1 :]:
stripped = candidate.strip()
if stripped:
return stripped
return None
def load_evidence() -> list[dict[str, Any]]:
records: list[dict[str, Any]] = []
for line_number, line in enumerate(read_text(EVIDENCE_PATH).splitlines(), start=1):
if not line.strip():
continue
try:
record = json.loads(line)
except json.JSONDecodeError as exc:
raise ValueError(f"evidence line {line_number} is invalid JSON: {exc}") from exc
if not isinstance(record, dict):
raise ValueError(f"evidence line {line_number} is not a JSON object")
records.append(record)
return records
def load_branches() -> dict[str, Any]:
data = json.loads(read_text(BRANCHES_PATH))
if not isinstance(data, dict):
raise ValueError("branches.json must be a JSON object")
branches = data.get("branches")
if not isinstance(branches, list):
raise ValueError("branches.json must contain a branches list")
return data
def add_required_file_checks(findings: list[Finding]) -> None:
for path in [
MAP_PATH,
SCRATCH_PATH,
BRANCHES_PATH,
EVIDENCE_PATH,
HANDOFF_PATH,
PLAN_PATH,
SYSTEM_MAP_PATH,
AGENTS_PATH,
]:
if path.exists():
findings.append(Finding("ok", f"found {path.relative_to(ROOT)}"))
else:
findings.append(Finding("error", f"missing {path.relative_to(ROOT)}"))
def add_content_checks(findings: list[Finding]) -> tuple[list[dict[str, Any]], dict[str, Any]]:
evidence: list[dict[str, Any]] = []
branches: dict[str, Any] = {}
summary = extract_map_field("summary")
next_action = extract_map_field("next_action")
if summary:
findings.append(Finding("ok", "state/map.yaml has current_status.summary"))
else:
findings.append(Finding("error", "state/map.yaml is missing current_status.summary"))
if next_action:
findings.append(Finding("ok", "state/map.yaml has current_status.next_action"))
else:
findings.append(Finding("error", "state/map.yaml is missing current_status.next_action"))
subgoals = extract_active_subgoals()
if subgoals:
findings.append(Finding("ok", f"state/map.yaml has {len(subgoals)} active subgoal(s)"))
else:
findings.append(Finding("warn", "state/map.yaml has no active_subgoals entries"))
scratch_subgoal = current_scratch_subgoal()
if scratch_subgoal == "No active scratch subgoal.":
findings.append(Finding("ok", "state/scratch.md has no stale active scratch subgoal"))
elif scratch_subgoal:
findings.append(Finding("warn", f"state/scratch.md has active scratch subgoal: {scratch_subgoal}"))
else:
findings.append(Finding("warn", "state/scratch.md has no Current Subgoal value"))
try:
evidence = load_evidence()
findings.append(Finding("ok", f"state/evidence.jsonl parses ({len(evidence)} record(s))"))
except ValueError as exc:
findings.append(Finding("error", str(exc)))
if EVIDENCE_PATH.exists() and EVIDENCE_PATH.stat().st_size > 25_000:
findings.append(Finding("warn", "state/evidence.jsonl is larger than 25 KB; consider distillation"))
try:
branches = load_branches()
active = [
branch
for branch in branches.get("branches", [])
if isinstance(branch, dict) and branch.get("status") == "active"
]
findings.append(Finding("ok", f"state/branches.json parses ({len(active)} active branch(es))"))
except (json.JSONDecodeError, ValueError) as exc:
findings.append(Finding("error", f"branches.json parse failed: {exc}"))
return evidence, branches
def add_handoff_checks(findings: list[Finding]) -> None:
text = read_text(HANDOFF_PATH)
stale_branch = re.search(r"Current branch before .*ahead \d+", text)
stale_head = re.search(r"Current HEAD before .*\b[0-9a-f]{7,40}\b", text)
if stale_branch or stale_head:
findings.append(Finding("error", "handoff embeds an exact branch or HEAD snapshot; use git commands instead"))
else:
findings.append(Finding("ok", "handoff avoids exact branch or HEAD snapshots"))
required_phrases = [
"Do not continue implementation automatically from a rehydrate-only request.",
"Do not trust this file for the exact live HEAD.",
"Immediate Re-entry Instruction",
]
for phrase in required_phrases:
if phrase in text:
findings.append(Finding("ok", f"handoff contains: {phrase}"))
else:
findings.append(Finding("warn", f"handoff missing: {phrase}"))
def add_agents_checks(findings: list[Finding]) -> None:
text = read_text(AGENTS_PATH)
if "vibegeometry_prepare_compaction.py" in text:
findings.append(Finding("ok", "AGENTS.md tells agents to use the compaction helper"))
else:
findings.append(Finding("error", "AGENTS.md does not mention tools/vibegeometry_prepare_compaction.py"))
if "prepare for imminent compaction" in text.lower():
findings.append(Finding("ok", "AGENTS.md names the imminent-compaction trigger"))
else:
findings.append(Finding("warn", "AGENTS.md does not name the imminent-compaction trigger phrase"))
def add_git_checks(findings: list[Finding]) -> tuple[str, str]:
status = run_git(["status", "--short", "--branch"])
try:
log = run_git(["log", "--oneline", "-5"])
except subprocess.CalledProcessError:
log = "(no commits yet)"
dirty_lines = [
line
for line in status.splitlines()[1:]
if line.strip()
]
if dirty_lines:
findings.append(Finding("warn", "git worktree has uncommitted changes; commit or explain before compaction"))
else:
findings.append(Finding("ok", "git worktree is clean"))
return status, log
def render_report(findings: list[Finding], status: str, log: str, evidence: list[dict[str, Any]]) -> str:
summary = extract_map_field("summary") or "(missing)"
next_action = extract_map_field("next_action") or "(missing)"
subgoals = extract_active_subgoals()
counts = {
"ok": sum(1 for finding in findings if finding.level == "ok"),
"warn": sum(1 for finding in findings if finding.level == "warn"),
"error": sum(1 for finding in findings if finding.level == "error"),
}
lines = [
"VibeGeometry pre-compaction persistence check",
f"Workspace: {ROOT}",
f"Findings: {counts['ok']} ok, {counts['warn']} warn, {counts['error']} error",
"",
"Git status:",
status,
"",
"Recent commits:",
log,
"",
f"Summary: {summary}",
f"Next action: {next_action}",
]
if subgoals:
lines.append("Active subgoals:")
lines.extend(f"- {item}" for item in subgoals)
if evidence:
latest = evidence[-1]
lines.extend(
[
"",
"Latest distilled evidence:",
f"- {latest.get('ts', '(missing ts)')} {latest.get('type', '(missing type)')}/{latest.get('status', '(missing status)')}: {latest.get('note', '(missing note)')}",
]
)
lines.append("")
lines.append("Findings:")
for finding in findings:
lines.append(f"[{finding.level.upper()}] {finding.message}")
lines.extend(
[
"",
"Pre-compaction checklist:",
"- Update state/map.yaml only if current understanding changed.",
"- Refresh notes/fresh-workspace-handoff.md if re-entry instructions changed.",
"- Add distilled evidence only for a belief-changing lesson, verification, rejected path, or scar.",
"- Keep exact branch or HEAD out of handoff prose; git commands own volatile truth.",
"- Commit completed persistence changes, or state why the worktree must stay dirty.",
"- Re-run this helper after edits before yielding to compaction.",
]
)
return "\n".join(lines)
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Audit VibeGeometry persistent state before imminent compaction."
)
parser.add_argument(
"--strict",
action="store_true",
help="Exit nonzero on warnings as well as errors.",
)
return parser
def main() -> int:
args = build_parser().parse_args()
findings: list[Finding] = []
evidence: list[dict[str, Any]] = []
add_required_file_checks(findings)
try:
evidence, _branches = add_content_checks(findings)
except OSError as exc:
findings.append(Finding("error", f"state content check failed: {exc}"))
try:
add_handoff_checks(findings)
except OSError as exc:
findings.append(Finding("error", f"handoff check failed: {exc}"))
try:
add_agents_checks(findings)
except OSError as exc:
findings.append(Finding("error", f"AGENTS check failed: {exc}"))
try:
status, log = add_git_checks(findings)
except (OSError, subprocess.CalledProcessError) as exc:
findings.append(Finding("error", f"git check failed: {exc}"))
status = "(git status unavailable)"
log = "(git log unavailable)"
print(render_report(findings, status, log, evidence))
has_error = any(finding.level == "error" for finding in findings)
has_warning = any(finding.level == "warn" for finding in findings)
if has_error or (args.strict and has_warning):
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())

179
tools/vibegeometry_state.py Normal file
View file

@ -0,0 +1,179 @@
from __future__ import annotations
import argparse
import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
STATE_DIR = ROOT / "state"
MAP_PATH = STATE_DIR / "map.yaml"
BRANCHES_PATH = STATE_DIR / "branches.json"
EVIDENCE_PATH = STATE_DIR / "evidence.jsonl"
def read_text(path: Path) -> str:
return path.read_text(encoding="utf-8")
def write_text(path: Path, content: str) -> None:
path.write_text(content, encoding="utf-8")
def load_branches() -> dict[str, Any]:
return json.loads(read_text(BRANCHES_PATH))
def save_branches(data: dict[str, Any]) -> None:
write_text(BRANCHES_PATH, json.dumps(data, indent=2) + "\n")
def append_evidence(record: dict[str, Any]) -> None:
with EVIDENCE_PATH.open("a", encoding="utf-8") as handle:
handle.write(json.dumps(record, ensure_ascii=True) + "\n")
def utc_stamp() -> str:
return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
def extract_map_field(name: str) -> str | None:
prefix = f" {name}:"
for line in read_text(MAP_PATH).splitlines():
if line.startswith(prefix):
return line.split(":", 1)[1].strip()
return None
def extract_active_subgoals() -> list[str]:
lines = read_text(MAP_PATH).splitlines()
results: list[str] = []
in_section = False
for line in lines:
if not in_section:
if line.strip() == "active_subgoals:":
in_section = True
continue
if line.startswith(" - "):
results.append(line[4:].strip())
continue
if line and not line.startswith(" "):
break
return results
def cmd_status(_: argparse.Namespace) -> int:
branches = load_branches().get("branches", [])
active = [branch for branch in branches if branch.get("status") == "active"]
summary = extract_map_field("summary") or "(missing)"
next_action = extract_map_field("next_action") or "(missing)"
subgoals = extract_active_subgoals()
print(f"Workspace: {ROOT}")
print(f"Summary: {summary}")
print(f"Next action: {next_action}")
print(f"Active branches: {len(active)} / {len(branches)}")
if subgoals:
print("Active subgoals:")
for item in subgoals:
print(f"- {item}")
return 0
def cmd_add_evidence(args: argparse.Namespace) -> int:
record: dict[str, Any] = {
"ts": utc_stamp(),
"type": args.type,
"status": args.status,
"note": args.note,
}
if args.branch:
record["branch"] = args.branch
append_evidence(record)
print("Appended evidence record.")
return 0
def cmd_add_branch(args: argparse.Namespace) -> int:
data = load_branches()
branches = data.setdefault("branches", [])
if any(branch.get("id") == args.id for branch in branches):
raise SystemExit(f"Branch '{args.id}' already exists.")
branches.append(
{
"id": args.id,
"hypothesis": args.hypothesis,
"status": "active",
"artifacts": args.artifact or [],
"notes": args.note or "",
}
)
save_branches(data)
print(f"Added branch '{args.id}'.")
return 0
def cmd_close_branch(args: argparse.Namespace) -> int:
data = load_branches()
for branch in data.get("branches", []):
if branch.get("id") != args.id:
continue
branch["status"] = args.status
if args.note:
branch["notes"] = args.note
save_branches(data)
print(f"Updated branch '{args.id}' to status '{args.status}'.")
return 0
raise SystemExit(f"Branch '{args.id}' was not found.")
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Inspect and update VibeGeometry state files."
)
subparsers = parser.add_subparsers(dest="command", required=True)
status_parser = subparsers.add_parser("status", help="Show a compact state summary.")
status_parser.set_defaults(func=cmd_status)
evidence_parser = subparsers.add_parser(
"add-evidence", help="Append one distilled JSONL evidence record."
)
evidence_parser.add_argument("--type", required=True)
evidence_parser.add_argument("--status", required=True)
evidence_parser.add_argument("--note", required=True)
evidence_parser.add_argument("--branch")
evidence_parser.set_defaults(func=cmd_add_evidence)
add_branch_parser = subparsers.add_parser(
"add-branch", help="Create a new active branch entry."
)
add_branch_parser.add_argument("--id", required=True)
add_branch_parser.add_argument("--hypothesis", required=True)
add_branch_parser.add_argument("--artifact", action="append")
add_branch_parser.add_argument("--note")
add_branch_parser.set_defaults(func=cmd_add_branch)
close_branch_parser = subparsers.add_parser(
"close-branch", help="Close an existing branch."
)
close_branch_parser.add_argument("--id", required=True)
close_branch_parser.add_argument(
"--status", required=True, choices=["accepted", "rejected", "archived"]
)
close_branch_parser.add_argument("--note")
close_branch_parser.set_defaults(func=cmd_close_branch)
return parser
def main() -> int:
parser = build_parser()
args = parser.parse_args()
return args.func(args)
if __name__ == "__main__":
raise SystemExit(main())