projectshape

package
v0.0.0-...-13f862e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 22, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package projectshape introduces named, shared understandings of the implementation pattern a workspace follows — the "architectural archetype" the project lives under (gm-4uso).

A Shape primes personas (PM, coaches, managers) with the right vocabulary and the right default recommendations. A coach helping with a 2-tier-with-SPA shape says different things than one helping a mobile-with-backend shape.

The persona dispatcher injects the active workspace's Shape via the prompt envelope's project-values layer (gm-3d6). Shapes are orthogonal to the gm-9rv persona axes (Personality / Perspective / Purview); this package owns the project-values layer injection only, not Phase or Purview interactions.

This bead (gm-4uso) ships the seed registry of five shapes plus the `.gemba/project_shape.toml` loader. CLI affordances (`gemba shape list`, `gemba shape update`) and operator-authored shape files under `.gemba/shapes/` are deliberately deferred to follow-up beads — the foundation here is the structured Shape type and the dispatcher injection.

Index

Constants

View Source
const ConfigFileName = "project_shape.toml"

ConfigFileName is the relative path (under the workspace's `.gemba/` directory) where operators pin the active shape.

Variables

View Source
var ErrUnknownShape = errors.New("projectshape: unknown shape")

ErrUnknownShape is returned by [Lookup] when the requested name has no registered shape. Wrapped errors carry the offending name in their text so the operator sees what they typed.

Functions

This section is empty.

Types

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry is the in-memory lookup table for named shapes. Built once at startup (typically via DefaultRegistry) and treated as read-only thereafter. Methods are safe for concurrent reads; Register is not concurrent-safe, mirroring the persona registry pattern.

func DefaultRegistry

func DefaultRegistry() *Registry

DefaultRegistry returns a Registry pre-populated with the 5 seed shapes from SeedShapes. The returned registry is independent of any other registry the caller already holds — mutations do not leak across calls.

func NewRegistry

func NewRegistry() *Registry

NewRegistry returns an empty Registry. Most callers want DefaultRegistry instead — it ships pre-populated with the 5 seed shapes.

func (*Registry) Lookup

func (r *Registry) Lookup(name string) (Shape, bool)

Lookup returns the shape registered under name. The bool is false when no shape is registered under that name. Callers that want a typed error (e.g. config validation) should call Registry.Resolve instead.

func (*Registry) LookupOrGeneric

func (r *Registry) LookupOrGeneric(name string) Shape

LookupOrGeneric returns the shape registered under name, or a zero-value Shape when name is empty or unknown. The dispatcher uses this on the no-shape-configured fallback path: a zero-value Shape's Render() returns the empty string, so the project-values layer simply contributes nothing — exactly what gm-4uso requires for a workspace that hasn't pinned a shape yet.

func (*Registry) Names

func (r *Registry) Names() []string

Names returns the registered shape names in ascending order. The `gemba shape list` CLI surface (deferred follow-up bead) consumes this to render its menu.

func (*Registry) Register

func (r *Registry) Register(s Shape) error

Register adds s to the registry. Returns an error when s.Name is empty or another shape is already registered under that name.

func (*Registry) Resolve

func (r *Registry) Resolve(name string) (Shape, error)

Resolve returns the shape registered under name, or wraps ErrUnknownShape when the name is not registered. Used by the config loader to surface a clean operator-facing error on a misspelled `active` value.

type Shape

type Shape struct {
	// Name is the canonical, stable identifier for this shape.
	// Personas reason about a stable identifier (e.g. "two-tier-spa")
	// across consults — DO NOT translate the name for display.
	Name string

	// Description is a one-paragraph blurb used at the top of the
	// rendered block. Speaks in the second person to the persona
	// ("Your project is …") so the framing reads naturally inline
	// with the rest of the system prompt.
	Description string

	// Tiers names the architectural tiers the project has, in the
	// order a request flows through them (e.g. "spa frontend",
	// "http api backend", "postgres"). Personas read this to scope
	// advice; "your project has no edge tier" follows from this list.
	Tiers []string

	// RequestPath is the canonical path a typical user request
	// follows through the tiers. Phrased as a chain
	// ("browser → https → api → postgres"). Concrete; one example
	// is more useful than abstract topology fields.
	RequestPath string

	// Tooling is the set of build/test/deploy tools the operator
	// reaches for daily (e.g. "vite", "go test", "goreleaser",
	// "systemd"). Keeps coach advice in the operator's vocabulary.
	Tooling []string

	// DeploymentSurface describes where the project runs in
	// production (e.g. "single VPS via systemd", "vercel + edge
	// functions", "iOS/Android stores + backend cluster"). Drives
	// recommendations about release cadence, rollback strategy, and
	// observability defaults.
	DeploymentSurface string
}

Shape is one architectural archetype. It captures the tiers a project actually has, the runtime path a typical request follows, the tooling and deployment surface, and a short prose blurb the persona reads as framing.

The fields are deliberately small. A persona doesn't need to know every detail of the build — it needs to know enough to say "you have no edge tier; don't recommend Cloudflare Workers patterns" or "the backend is in Go; suggest go test idioms, not jest".

A zero-value Shape is valid: Render returns the empty string so a dispatcher with no shape configured emits no project-values contribution from this layer.

func LoadConfig

func LoadConfig(path string, reg *Registry) (Shape, error)

LoadConfig reads `.gemba/project_shape.toml` at path and resolves the named shape via the supplied registry. Behaviour:

  • File missing → returns a zero-value Shape and a nil error. The dispatcher's Render() then contributes nothing to the project- values layer, which is the gm-4uso "no shape configured" fallback.
  • File present but [project_shape] block absent or `active` empty → returns zero-value Shape, nil error (treat as "no shape pinned").
  • File present, `active` set, and the registry has no such shape → returns a wrapped ErrUnknownShape so the operator sees a clean error instead of silent fallback to generic.
  • Malformed TOML → wrapped error naming the path.

path MUST be the full path to the file (typically filepath.Join(workspaceDir, ".gemba", ConfigFileName)). The caller owns workspace-dir resolution; this loader is path-pure for ease of testing.

func SeedShapes

func SeedShapes() []Shape

SeedShapes returns the 5 architectural-archetype seed shapes gm-4uso ships. The returned slice is fresh on every call so a caller can mutate it without polluting the registry.

The five seeds (in this order) are:

  1. gemba — this workspace's shape: two-tier Go server + Vite SPA on a single VPS (matches the bead's first seed verbatim).
  2. two-tier-spa — generalisation of (1): SPA + backend API.
  3. two-tier-ssr — server-rendered (Next/Remix/Rails/Django) plus progressive-enhancement JS.
  4. three-tier-with-edge — SPA → edge functions → origin db/services.
  5. mobile-with-backend — native/RN/Flutter mobile + backend API + push channel; mobile has its own release cycle.

Names match the bead's seed enumeration verbatim so personas can reason about a stable identifier across consults.

func (Shape) IsZero

func (s Shape) IsZero() bool

IsZero reports whether s carries no shape information. The dispatcher uses this to skip the project-values injection when no shape is configured.

func (Shape) Render

func (s Shape) Render() string

Render returns the human-readable, prompt-ready block the dispatcher injects into the project-values layer. Format is deliberately compact — bulleted facts rather than prose — so the model anchors on the structured signals without inflating the prompt budget.

A zero-value Shape returns the empty string. The dispatcher interprets that as "no shape configured" and omits the project- values contribution entirely (gm-4uso's no-shape fallback).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL