diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index bb564ae..2a71cbc 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -5,18 +5,19 @@ "email": "mho@looplia.run" }, "metadata": { - "version": "2.0.1", + "version": "2.1.0", "description": "Skills for product planning, project scaffolding, and agentic development workflows." }, "plugins": [ { "name": "product-context", - "description": "Product-level planning and iteration: envision, map, dispatch, validate, calibrate, reflect, watch.", + "description": "Product-level planning and iteration: envision, map, model, dispatch, validate, calibrate, reflect, watch.", "source": "./", "strict": false, "skills": [ "./skills/product-context/envision", "./skills/product-context/map", + "./skills/product-context/model", "./skills/product-context/dispatch", "./skills/product-context/validate", "./skills/product-context/calibrate", diff --git a/CHANGELOG.md b/CHANGELOG.md index 717db57..7a151a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,59 @@ bug fixes → **patch**; removing or breaking a skill contract → **major**. _Nothing yet._ +## [2.1.0] - 2026-06-16 + +Add an **object-first design stage** — `/aep-model` — that turns the verb-first +story map into a noun-first **Object Map** (OOUX/ORCA) before UI is built, so build +agents stop inventing one-step-one-screen task-wizard UIs. The structural UI plan +(objects, attributes, relationships, CTAs, screens) is auto-drafted from artifacts +AEP already produces, human-approved at a short gate, then governs build — leaving +only taste (look/voice/journey) to `/aep-calibrate`. Background and the verb-first +vs noun-first analysis: [`docs/research/ooux-object-modeling.md`](docs/research/ooux-object-modeling.md). + +### Added + +- **`/aep-model` skill** (`product-context`): runs ORCA (Objects → Relationships → + Calls-to-action → Attributes → screens) to draft an Object Map, takes a short + human review gate (object boundaries, primary anchor, task-flow exceptions), and + writes the approved noun-first blueprint. Sits between `/aep-map` and + `/aep-dispatch` for UI-facing products. Registered in + [`marketplace.json`](.claude-plugin/marketplace.json) `product-context` plugin. +- **Object Map artifacts + schemas**: `product/object-model.yaml` (cross-capability + object ontology) and `product/maps//object-map.yaml` (capability-scoped + ORCA/IA projection), via `_shared/templates/object-model-schema.yaml` and + `object-map-schema.yaml`. Object-first is the default; task-oriented flows are an + opt-in escape hatch recorded with a reason. +- **ORCA reference** (`product-context/model/references/orca-process.md`): + round-by-round derivation from AEP inputs + the object-first/task-oriented decision + framework and the completeness checks. +- **Glossary terms**: Object Model, Object Map, ORCA, Call-to-Action (CTA), Nested + Object Matrix, Object-First vs Task-Oriented. +- **Research note** `docs/research/ooux-object-modeling.md` and a `Research` category + in [`docs/README.md`](docs/README.md). + +### Changed + +- **Schema** (`product-context-schema.yaml`): adds `stories[].object_model_refs`, + `stories[].capability`, `architecture.modules[].kind`, and the `object-model` + quality dimension. Object-map approvals are tracked as thin `calibration.history` + references — the artifact bodies stay under `product/`, not inlined into + `product-context.yaml`. +- **`/aep-envision`**: declares the `object-model` structural gate by default for + UI-facing products. +- **`/aep-map`**: auto-drafts Object Maps after decomposition; sets `module.kind` + + `story.capability`; flips an approved map to `stale` on re-decompose; routes Next + Step to `/aep-model`. +- **`/aep-dispatch`**: injects the minimal Object Map slice into a story's context + package and refuses UI-facing stories without an approved (non-stale) map. +- **`/aep-launch`**: aborts a UI-facing story when no approved Object Map covers it. +- **`/aep-build`**: UI implementation obeys the injected Object Map slice (object structure and CTA grammar; taste still from calibration). +- **`/aep-validate`**: Mode A gains Object Map completeness checks (coverage, object + homes, anchors, task-flow justification, ref resolution). + +All additions are backward-compatible — the object-model path only engages for +UI-facing products that opt in. + ## [2.0.1] - 2026-06-16 Operationalize the **dogfood → reflect classifier → story** link so the G6 diff --git a/README.md b/README.md index 2f7d5eb..351d418 100644 --- a/README.md +++ b/README.md @@ -238,12 +238,12 @@ npx skills experimental_install The `skills` CLI selects by skill name (there's no "group" flag). The groups map to these `--skill` names: -| Group | `--skill` names | -| ------------------------------------------- | ----------------------------------------------------------------------------------------- | -| **Workflow** (agentic-development-workflow) | `aep-design`, `aep-launch`, `aep-build`, `aep-wrap`, `aep-git-ref` | -| **Product** (product-context) | `aep-envision`, `aep-map`, `aep-dispatch`, `aep-validate`, `aep-calibrate`, `aep-reflect` | -| **Setup** (project-setup) | `aep-onboard`, `aep-scaffold`, `aep-testing-guide` | -| **Patterns** (patterns) | `aep-gen-eval`, `aep-executor`, `aep-autopilot`, `aep-workflow-feedback` | +| Group | `--skill` names | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| **Workflow** (agentic-development-workflow) | `aep-design`, `aep-launch`, `aep-build`, `aep-wrap`, `aep-git-ref` | +| **Product** (product-context) | `aep-envision`, `aep-map`, `aep-model`, `aep-dispatch`, `aep-validate`, `aep-calibrate`, `aep-reflect` | +| **Setup** (project-setup) | `aep-onboard`, `aep-scaffold`, `aep-testing-guide` | +| **Patterns** (patterns) | `aep-gen-eval`, `aep-executor`, `aep-autopilot`, `aep-workflow-feedback` | ### Maintainer (legacy) workflow @@ -282,57 +282,9 @@ bun run skills:check # verify the copies are in sync (also runs in CI + pre-c The workflow separates **thinking** from **doing**: -``` -┌─────────────────────────────────────────────────────────────────┐ -│ │ -│ CONTROL PLANE (human decides what to build) │ -│ │ -│ You + AI collaborate on high-leverage decisions: │ -│ goals, decomposition, architecture, priorities, feedback │ -│ │ -│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ -│ │ /aep-envision │───►│ /aep-map │───►│ /aep-reflect │──┐ │ -│ │ │ │ │ │ │ │ │ -│ │ what to │ │ how to │ │ what we │ │ │ -│ │ build │ │ break it │ │ learned │ │ │ -│ │ │ │ down │ │ │ │ │ -│ └──────────┘ └──────────┘ └──────────┘ │ │ -│ ▲ │ │ │ │ -│ └────────────────┼──────────────┘ │ │ -│ │ feedback loop │ │ -│ ▼ │ │ -│ ┌────────────┐ │ │ -│ │ /aep-dispatch │ picks stories │ │ -│ │ │ from the map, │ │ -│ │ what to │ creates OpenSpec │ │ -│ │ work on │ changes │ │ -│ │ next │ │ │ -│ └─────┬──────┘ │ │ -│ │ │ │ -└────────────────────────┼─────────────────────────┼──────────────┘ - │ │ - story specs │ status + cost flow up │ - flow down │ │ - ▼ │ -┌──────────────────────────────────────────────────┼──────────────┐ -│ │ │ -│ EXECUTION PLANE (agents build it) │ │ -│ │ -│ Agents receive precise specs, work in isolation, │ -│ produce PRs. They don't decide what to build. │ -│ │ -│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │ -│ │ /aep-design │───►│ /aep-launch │───►│ /aep-build │───►│ /aep-wrap │ │ -│ │ │ │ │ │ │ │ │ │ -│ │ refine │ │ spawn │ │ implement│ │ archive │ │ -│ │ the spec │ │ agent │ │ + test │ │ + update│ │ -│ │ │ │ │ │ + PR │ │ status │ │ -│ └──────────┘ └──────────┘ └──────────┘ └─────────┘ │ -│ │ -│ (repeat per story — multiple stories run in parallel) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ -``` +![AEP mental model: control plane and execution plane](assets/aep-mental-model.png) + +> This is the conceptual split. The control plane also has **specialized steps** this view omits — `/aep-model` (noun-first Object Map, UI-facing only), `/aep-calibrate` (human alignment on `.5` layers), `/aep-validate`, and `/aep-watch`. They appear in the detailed [Product Context](#1-product-context--the-persistent-map) flow below. **Agents don't talk to each other.** They communicate through structured artifacts — context documents, story specs, interface contracts, signal files. The harness coordinates everything. This is a production system design, not a chatroom-style agent swarm. @@ -446,39 +398,12 @@ Each plugin implements one layer of the mental model. Captures the "what and why" of the entire product in a single `product-context.yaml` — committed to git, versioned, and machine-parseable. -``` -/aep-envision /aep-map /aep-reflect - │ │ │ - ▼ ▼ ▼ -Opportunity Brief System Map Classify feedback: -"should we build this?" "modules + interfaces" bug → fix story - │ │ refinement → next layer - ▼ ▼ discovery → update map -Context Document Story Graph shift → re-envision -"what exactly to build, "layered work items, │ - for whom, within waves + slices" │ - what constraints" │ │ - │ ▼ │ - │ Agent Topology │ - │ "roles + contracts" │ - │ │ │ - └───────────────┬───────────────┘ │ - │ │ - ▼ │ - /aep-dispatch │ - "pick next story, ◄─────────────────────────┘ - create OpenSpec change, (new stories feed back - route to /aep-design" into the dispatch queue) - │ - ├─── integer layer ──► /aep-design → /aep-launch → /aep-build → /aep-wrap - │ - └─── .5 alignment layer ──► /aep-calibrate → human aligns - → /aep-calibrate capture - → /aep-dispatch → /aep-launch → /aep-build → /aep-wrap -``` +![AEP product context flow](assets/aep-product-context-flow.png) All sections live in one `product-context.yaml` file — opportunity, product, architecture, stories (with state machine), topology, layer gates, cost tracking, and a semantic changelog. +> **`/aep-model` is UI-facing only** (the noun-first **Object Map** step shown above). It auto-drafts from the story map, takes a short human approval, then governs object structure — so build agents stop inventing one-step-one-screen task-wizard UIs. Stored under `product/` (`object-model.yaml` + `maps//object-map.yaml`), gated by dispatch/launch. Background: [docs/research/ooux-object-modeling.md](docs/research/ooux-object-modeling.md). + **Why this exists:** Without a product-level map, each feature is designed in isolation. Agents build incompatible pieces. Module boundaries are implicit. The YAML makes the whole system visible, machine-readable, and git-versioned before any code is written. ### 2. Feature Lifecycle — the execution cycle @@ -663,7 +588,7 @@ These aren't rules we invented — they're patterns extracted from Anthropic's e ## Getting Started -**Brand new to AEP?** Start with the [Orientation Guide](docs/orientation.md) for a 10-minute tour of the mental models, the 17 skills, and the four paths — then run `/aep-onboard`. +**Brand new to AEP?** Start with the [Orientation Guide](docs/orientation.md) for a 10-minute tour of the mental models, the 19 skills, and the four paths — then run `/aep-onboard`. **New to this plugin?** @@ -721,6 +646,7 @@ Generate a dimension-specific brief, explore or discuss, capture decisions for a | ---------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------- | | `/aep-envision` | product-context | Opportunity brief + context document | | `/aep-map` | product-context | System map + story graph + agent topology | +| `/aep-model` | product-context | Object-first UI structure (OOUX/ORCA Object Map) for UI products | | `/aep-dispatch` | product-context | Pick next story + create OpenSpec change | | `/aep-calibrate` | product-context | Human alignment checkpoint for any quality dimension | | `/aep-reflect` | product-context | Classify feedback + update context | diff --git a/assets/aep-mental-model.png b/assets/aep-mental-model.png new file mode 100644 index 0000000..9feb3d1 Binary files /dev/null and b/assets/aep-mental-model.png differ diff --git a/assets/aep-product-context-flow.png b/assets/aep-product-context-flow.png new file mode 100644 index 0000000..de6feb3 Binary files /dev/null and b/assets/aep-product-context-flow.png differ diff --git a/docs/README.md b/docs/README.md index 7aeeada..8c919cb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,13 +4,14 @@ Rules for organizing documentation in this directory. ## Categories -| Category | Directory | What goes here | Naming convention | -| -------------- | ------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- | -| **Decisions** | `decisions/` | ADRs, post-mortems, improvement proposals that change AEP itself | Descriptive slug (`aep-v2-lesson-learning.md`) | -| **Workflow** | `workflow/` | How AEP patterns work — process docs, not decisions about them | Descriptive slug (`autonomous-loop.md`) | -| **Tech-stack** | `tech-stack/` | Technology-specific gotchas, deployment patterns, library quirks | `-.md` (`rust-keyring-platform-features.md`) | -| **Lessons** | `lessons/` | Date-prefixed observations from downstream project runs | `YYYY-MM-DD--.md` | -| **Plans** | `plans/` | Design plans from `/design` skill | `YYYY-MM-DD-.md` | +| Category | Directory | What goes here | Naming convention | +| -------------- | ------------- | ----------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | +| **Decisions** | `decisions/` | ADRs, post-mortems, improvement proposals that change AEP itself | Descriptive slug (`aep-v2-lesson-learning.md`) | +| **Workflow** | `workflow/` | How AEP patterns work — process docs, not decisions about them | Descriptive slug (`autonomous-loop.md`) | +| **Tech-stack** | `tech-stack/` | Technology-specific gotchas, deployment patterns, library quirks | `-.md` (`rust-keyring-platform-features.md`) | +| **Lessons** | `lessons/` | Date-prefixed observations from downstream project runs | `YYYY-MM-DD--.md` | +| **Plans** | `plans/` | Design plans from `/design` skill | `YYYY-MM-DD-.md` | +| **Research** | `research/` | Exploratory studies, source reviews, and gap analyses before a decision or implementation plan exists | Descriptive slug (`ooux-object-modeling.md`) | ## Key Distinctions @@ -18,6 +19,7 @@ Rules for organizing documentation in this directory. - **Decisions** = reasoned changes to AEP itself. They explain why a pattern changed and what was decided. - **Tech-stack** = reusable technical knowledge that applies across projects. Not tied to a specific build run. - **Workflow** = how AEP patterns work day-to-day. Reference material for understanding the system. +- **Research** = evidence gathering and synthesis. It can recommend follow-up work, but it is not itself an accepted design decision. ## Top-level Files @@ -35,3 +37,4 @@ When adding new documentation, ask: 3. **"Does this change how AEP works?"** → `decisions/` 4. **"Does this explain an existing AEP pattern?"** → `workflow/` 5. **"Is this a forward-looking design for a feature?"** → `plans/` +6. **"Is this still exploratory evidence or gap analysis?"** → `research/` diff --git a/docs/glossary.md b/docs/glossary.md index 091149c..4ad7a93 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -669,6 +669,50 @@ product/ map.yaml # Backbone + layers + story stubs ``` +### Object Model (OOUX) + +The cross-capability, user-facing **object ontology** — the things the user perceives and acts on (e.g. ORDER, REPORT, AVATAR), distinct from backend data entities (**System Map** `domain_model`). Produced by `/aep-model` via OOUX noun foraging. The noun-first counterpart to the verb-first **User Story Map**. + +**Where it appears:** `product/object-model.yaml` (stable, under `product/`); `/aep-model` creates it; thin reference in `product-context.yaml` → `calibration.history`. + +See also: **Object Map**, **ORCA**, **User Story Map**, **System Map** + +### Object Map (OOUX) + +A capability-scoped projection of the **Object Model** into implementable structure: which objects appear, their attributes (core/secondary/metadata), how they nest (**Nested Object Matrix**), the **Calls-to-Action** on each, and structural screens. It is the noun-first bridge from the story map to the UI — what stops build agents from inventing one-step-one-screen task-wizard UIs. Structural only: visual/voice/journey stay in **Calibration Artifacts**. Lifecycle: `/aep-map` drafts (`status: draft`), `/aep-model` approves (`status: approved`), `/aep-dispatch` injects only the slice a story touches. + +**Where it appears:** `product/maps//object-map.yaml`; gated by `/aep-dispatch` and `/aep-launch` for UI-facing stories. + +See also: **Object Model**, **ORCA**, **Capability Map**, **Calibration** + +### ORCA (OOUX process) + +Sophia Prater's four-round OOUX process — **O**bjects → **R**elationships → **C**alls-to-action → **A**ttributes — answered before designing any screen. `/aep-model` runs an artifact-grounded version over AEP's existing inputs (`product.activities`, `stories[].description`, `architecture.domain_model`). See `skills/product-context/model/references/orca-process.md` and `docs/research/ooux-object-modeling.md`. + +See also: **Object Map**, **Call-to-Action**, **Nested Object Matrix** + +### Call-to-Action (CTA) + +An action a user can take _on an object_, tagged by role/persona (object × role matrix). In AEP, the **verbs** mined from stories/activities become CTAs hung on the objects they act on — the verb-first→noun-first join. Each CTA records placement (collection/detail/inline/global), priority, and the `from_story` it came from. + +**Where it appears:** `product/maps//object-map.yaml` → `ctas`. + +See also: **Object Map**, **ORCA** + +### Nested Object Matrix (NOM) + +An objects-by-objects matrix describing relationships in the intersections (cardinality + whether one object nests inside another's view) — a scalable entity-relationship diagram from OOUX Round R. Relationships pave the navigation paths and drive detail-view composition. + +**Where it appears:** `product/object-model.yaml` (cross-capability) and `product/maps//object-map.yaml` (capability-local) → `relationships`. + +See also: **Object Map**, **ORCA** + +### Object-First vs Task-Oriented + +The interaction-grammar choice: **object-first** (noun→verb — pick an object, then act; the AEP default) vs **task-oriented** (verb→noun — a guided wizard). Object Maps default every flow to object-first and record per-flow `task_oriented` deviations with a reason (e.g. onboarding, checkout). See the decision framework in `docs/research/ooux-object-modeling.md` §6. + +See also: **Object Map**, **ORCA** + ### Readiness Score A per-story spec completeness score (0.0–1.0) computed during `/aep-dispatch` (Step 3). Components: acceptance criteria count, interface obligations defined, files affected identified, verification defined, no unresolved open questions. Routes stories: < 0.5 → `/aep-design`, >= 0.7 → `/aep-launch`, between → user decision. diff --git a/docs/orientation.md b/docs/orientation.md index bae3bdc..1cdc510 100644 --- a/docs/orientation.md +++ b/docs/orientation.md @@ -1,6 +1,6 @@ # AEP Orientation Guide -**A 10-minute first-hour tour for new users.** Read this before (or right after) running `/aep-onboard`. When you finish, you'll know what AEP is, the three mental models that drive every skill, what each of the 17 skills does, and which of four concrete paths matches your situation. +**A 10-minute first-hour tour for new users.** Read this before (or right after) running `/aep-onboard`. When you finish, you'll know what AEP is, the three mental models that drive every skill, what each of the 19 skills does, and which of four concrete paths matches your situation. For precise definitions of every term used here, see the [Glossary](glossary.md). For a one-page decision tree, see the [Skills Quick Reference](skills-quick-reference.md). @@ -94,28 +94,29 @@ More: [README.md "The Feature Lifecycle"](../README.md) and [skills/agentic-deve --- -## 3. The 17 Skills at a Glance - -| Skill | Plugin | Session | Purpose | -| ------------------------ | ---------------------------- | --------- | --------------------------------------------------------------- | -| `/aep-onboard` | project-setup | Main | Install tools, verify env, configure plugins, orient new users | -| `/aep-scaffold` | project-setup | Main | Scaffold a full-stack TypeScript monorepo + initialize OpenSpec | -| `/aep-testing-guide` | project-setup | Main | Reference for workspace-level quality infrastructure | -| `/aep-envision` | product-context | Main | Opportunity brief + context document (what to build) | -| `/aep-map` | product-context | Main | System map + story graph + agent topology (how to decompose) | -| `/aep-validate` | product-context | Main | Generator/evaluator validation of any AEP artifact | -| `/aep-dispatch` | product-context | Main | Pick next story + create OpenSpec change + hand off | -| `/aep-calibrate` | product-context | Main | Human alignment checkpoint for any quality dimension | -| `/aep-reflect` | product-context | Main | Classify feedback + update context (close the loop) | -| `/aep-watch` | product-context | Main | Ingest telemetry/errors → auto-file stories (self-feeding loop) | -| `/aep-design` | agentic-development-workflow | Main | Interactive feature design (explore + propose + review) | -| `/aep-launch` | agentic-development-workflow | Main | Spawn autonomous workspace + optional evaluator | -| `/aep-build` | agentic-development-workflow | Workspace | Implement → test → PR → merge (autonomous) | -| `/aep-wrap` | agentic-development-workflow | Main | Post-merge archive + cleanup + update story status | -| `/aep-git-ref` | agentic-development-workflow | Main | AEP git + worktree conventions (on-demand) | -| `/aep-autopilot` | patterns | Main | Hands-free dispatch → launch → monitor → wrap loop | -| `/aep-gen-eval` | patterns | Both | Reusable generator/evaluator pattern for honest evaluation | -| `/aep-workflow-feedback` | patterns | Main | Capture + review process learnings from builds | +## 3. The 19 Skills at a Glance + +| Skill | Plugin | Session | Purpose | +| ------------------------ | ---------------------------- | --------- | ---------------------------------------------------------------- | +| `/aep-onboard` | project-setup | Main | Install tools, verify env, configure plugins, orient new users | +| `/aep-scaffold` | project-setup | Main | Scaffold a full-stack TypeScript monorepo + initialize OpenSpec | +| `/aep-testing-guide` | project-setup | Main | Reference for workspace-level quality infrastructure | +| `/aep-envision` | product-context | Main | Opportunity brief + context document (what to build) | +| `/aep-map` | product-context | Main | System map + story graph + agent topology (how to decompose) | +| `/aep-model` | product-context | Main | Object-first UI structure (OOUX/ORCA Object Map) for UI products | +| `/aep-validate` | product-context | Main | Generator/evaluator validation of any AEP artifact | +| `/aep-dispatch` | product-context | Main | Pick next story + create OpenSpec change + hand off | +| `/aep-calibrate` | product-context | Main | Human alignment checkpoint for any quality dimension | +| `/aep-reflect` | product-context | Main | Classify feedback + update context (close the loop) | +| `/aep-watch` | product-context | Main | Ingest telemetry/errors → auto-file stories (self-feeding loop) | +| `/aep-design` | agentic-development-workflow | Main | Interactive feature design (explore + propose + review) | +| `/aep-launch` | agentic-development-workflow | Main | Spawn autonomous workspace + optional evaluator | +| `/aep-build` | agentic-development-workflow | Workspace | Implement → test → PR → merge (autonomous) | +| `/aep-wrap` | agentic-development-workflow | Main | Post-merge archive + cleanup + update story status | +| `/aep-git-ref` | agentic-development-workflow | Main | AEP git + worktree conventions (on-demand) | +| `/aep-autopilot` | patterns | Main | Hands-free dispatch → launch → monitor → wrap loop | +| `/aep-gen-eval` | patterns | Both | Reusable generator/evaluator pattern for honest evaluation | +| `/aep-workflow-feedback` | patterns | Main | Capture + review process learnings from builds | For when-to-use decisions, see [docs/skills-quick-reference.md](skills-quick-reference.md). @@ -238,4 +239,4 @@ One-line pointers so you know what to look up when you hit an unfamiliar term. F --- -**You're done with orientation.** The rest of AEP is discoverable from the three mental models, the 17-skill table, and the four paths. When in doubt, reach for the decision tree in the quick reference — it covers the common forks. +**You're done with orientation.** The rest of AEP is discoverable from the three mental models, the 19-skill table, and the four paths. When in doubt, reach for the decision tree in the quick reference — it covers the common forks. diff --git a/docs/research/ooux-object-modeling.md b/docs/research/ooux-object-modeling.md new file mode 100644 index 0000000..2c70fd5 --- /dev/null +++ b/docs/research/ooux-object-modeling.md @@ -0,0 +1,305 @@ +# OOUX / OOUI 物件導向設計 × AEP 工作流研究 + +> **狀態:** 研究筆記(非實作計畫)。記錄 OOUX/OOUI(名詞優先)與 AEP(USM 動詞優先)的對照,回答「能否在所有 UI-facing design 階段先產生可審閱的 UI 結構計劃」。AEP 整合設計見 §7,**列為後續 follow-up,不在本次提交範圍**。 +> **日期:** 2026-06-16 **分支:** `docs/ooux-object-modeling-research` +> **方法:** 以使用者既有的對抗式查證筆記《Task vs Object 設計之爭》為理論骨幹,本次再以 `defuddle` + `WebFetch` 直接抓取一手網址補強(mgrep `--web` 本月配額用盡)。逐條標註「本次實抓」vs「沿用既有查證」。 + +--- + +## 0. TL;DR + +- **「按動詞切還是按名詞切」是 UI 拆解的根本分軸。** AEP 的設計骨幹(Jeff Patton USM)站在**動詞優先(verb-first)**:activity backbone → story graph → 一條條 story。OOUX(Sophia Prater)與 OOUI(上野学)站在**名詞優先(noun-first)**:先定物件,動作其後。 +- **兩者不對立、不在同一層。** USM 管 discovery/規劃/切片;OOUX/OOUI 管 IA/UI 結構。中間用「**從敘事挖名詞**」接起來。 +- **AEP 現況的缺口正中要害:** AEP 已有 `visual-design` 與 `ux-flow` calibration;`ux-flow` 能校準 journey / page map / transitions,但**仍沒有名詞優先的 Object Map**把 story map 接到畫面上的物件、欄位、巢狀與 CTA。於是 build agent 仍可能逐 story 即興生 UI —— 這正是 OOUI 批判的 **task-wizard(一步一畫面)** 溫床。 +- **可行性結論:可以(yes),但不是自動拍板。** ORCA 的四輪(Objects / Relationships / CTAs / Attributes)可以由 AEP **已經產出**的輸入(`product.activities`、story descriptions、`architecture.domain_model`、capability maps)產生 draft;凡 UI-facing capability/story 都應**必定觸發 Object Map review gate**,由 agent 提案、使用者回答少量關鍵問題後批准。 +- **v2 artifact 形狀:** 不把結構設計塞回 `product-context.yaml`。使用 `product/object-model.yaml` 存跨 capability 的使用者心智物件;使用 `product/maps//object-map.yaml` 存 capability-scoped ORCA / IA projection;`product-context.yaml` 只保留 operational state / history / references。 +- **唯一真正的地雷:** 把 USM backbone 一格一格直譯成畫面。**slice 切的是「範圍/要學什麼」,不是「介面型態」。MVP slice ≠ wizard。** + +--- + +## 目錄 + +- [§1 為什麼這條分軸重要](#1-為什麼這條分軸重要) +- [§2 核心對立:noun-first vs verb-first](#2-核心對立noun-first-vs-verb-first) +- [§3 兩大物件導向陣營:OOUX 與 OOUI](#3-兩大物件導向陣營ooux-與-ooui) +- [§4 ORCA 操作細節(四輪 × 四支柱 / 15 步)](#4-orca-操作細節四輪--四支柱--15-步) +- [§5 更早的源流:直接操作與 noun-verb 語法](#5-更早的源流直接操作與-noun-verb-語法) +- [§6 決策框架:何時 task、何時 object](#6-決策框架何時-task何時-object) +- [§7 套用到 AEP(分析,未執行)](#7-套用到-aep分析未執行) +- [§8 證據缺口與誠實標註](#8-證據缺口與誠實標註) +- [來源](#來源) + +--- + +## 1. 為什麼這條分軸重要 + +AEP 的設計流是 USM:`/aep-envision` 抽出 **activity backbone**(使用者依序做的事,左→右),`/aep-map` 切成分層 story graph,story 是最小工作單位。這非常適合「想清楚使用者要做什麼、先做哪一刀」,但它**回答不了「UI 結構該長怎樣」**。 + +```mermaid +graph TD + ROOT["一個系統怎麼拆
按動詞還是按名詞"] -->|"以行為/任務切分"| VERB["Verb-first 動詞優先
task-oriented"] + ROOT -->|"以對象/物件切分"| NOUN["Noun-first 名詞優先
object-oriented"] + VERB --> USM["User Story Mapping
AEP 的規劃與切片"] + NOUN --> OOUX["OOUX (Prater)
ORCA 物件先於動作"] + NOUN --> OOUI["OOUI (上野学)
物件選擇→動作選擇"] + USM -->|"挖出敘事中的名詞餵入"| OOUX + OOUX -->|"Object Map → 互動/UI"| IXD["互動/UI 設計
畫面與流程"] + USM -->|"直譯成一步一畫面 = 危險"| WIZ["Wizard 式 UI
OOUI 所批判的對象"] + + style ROOT fill:#e7f5ff,stroke:#1971c2,stroke-width:3px + style VERB fill:#ffe3e3,stroke:#c92a2a,stroke-width:2px + style USM fill:#ffe8cc,stroke:#d9480f,stroke-width:2px + style NOUN fill:#e5dbff,stroke:#5f3dc4,stroke-width:2px + style OOUX fill:#c5f6fa,stroke:#0c8599,stroke-width:2px + style OOUI fill:#c5f6fa,stroke:#0c8599,stroke-width:2px + style IXD fill:#d3f9d8,stroke:#2f9e44,stroke-width:2px + style WIZ fill:#f8f9fa,stroke:#868e96,stroke-width:2px +``` + +--- + +## 2. 核心對立:noun-first vs verb-first + +整場爭論濃縮成**一個語序問題**:介面結構先呈現「**對象(名詞)**」還是先呈現「**動作(動詞)**」。 + +> **✅ 本次實抓(ooux.com「What is OOUX」):** +> "Traditionally, digital product teams divvy up complexity by feature, user story, or task flow. In essence, teams tend to break up complexity by the _verbs_. Unfortunately, this often leads to cobbled-together, disjointed user experiences. Object-Oriented UX offers a better way... Instead of slicing up a system by _verbs_, OOUXers slice by _nouns_. As it turns out — this is how developers work too — object-orientedly." + +- **OOUX 立場:objects first, actions second**。先定義使用者心智模型裡的物件,動作之後才推導。 +- **對 task 派的批判:** 按 feature / **user story** / task flow 切複雜度,「often leads to cobbled-together, disjointed user experiences」。注意這句**點名了 user story** —— 是目前最接近「USM 被 OOUX 陣營點評」的一手敘述(但仍非 USM vs OOUX 的正式比較,見 §8)。 +- **文法比喻(Prater 核心修辭):** 使用者是主詞、互動是動詞,但**受詞(物件)必須先被指認**——"Sally kicked _what_?" 沒有受詞,動詞就懸空。✅(沿用既有查證,alistapart 2016) + +--- + +## 3. 兩大物件導向陣營:OOUX 與 OOUI + +物件導向設計有兩條獨立發展、結論高度一致的支脈:英語圈的 **OOUX** 與日語圈的 **OOUI**。 + +### 陣營 A:OOUX — Sophia Prater(英語圈) + +> **✅ 本次實抓(ooux.com ORCA 頁):** +> "OOUX is a philosophy for designing digital systems that respects the fact that people think in objects... It guides design and development teams in deliberately aligning their software to their user's real-world mental model of concrete objects." + +- **起源:** Sophia V. Prater。原典〈Object-Oriented UX〉發表於 A List Apart,2015-10-20;續篇〈OOUX: A Foundation for Interaction Design〉2016-04-19。✅(沿用既有查證) +- **關鍵自我定位(對本研究最重要):** OOUX **不是取代既有流程**,而是「a new ingredient to add to your current process」。本次抓到的 Medium 介紹文也用同一框架稱 OOUX 為「**上游工具**(upstream tool),在介面設計前先建立結構地基」。→ **結構上可與任何 discovery/規劃方法(含 USM)並存。** +- **與互動設計的接口:** OOUX 是上游/地基;**Object Map** 是從物件模型通往細部互動/畫面的橋。 + +### 陣營 B:OOUI — 上野学/ソシオメディア(日語圈) + +- **基本操作語法:物件選擇 → 動作選擇(object → verb)**,貫穿整個 App,用來消除 task-first 流程裡的「**對象選擇待ちモード**」。✅(沿用既有查證,sociomedia 8740) +- **對 task 指向的批判核心:** 它「切斷人與工具的相互發展螺旋、從工作中奪走創造性、把使用者關進搾取結構」;入口先擋一個「謎の人格」,使用者只能照那個人格企圖的方式使用。✅ +- **互動語法對照:** GUI 應是「名詞→動詞」;task-oriented 是「動詞→名詞」——這正是批判核心。✅(sociomedia 7279) +- **強版本主張(歸屬已確認,普適性有爭議):** 「modeless 取代 modal」「世の業務アプリケーションのおよそ8割はタスクベース」確為上野原話 ✅;但日語圈實務社群對其**普適性**有反論(ATM、新手 onboarding 等場景 task 指向反而對)。引用時請分清「這是上野的主張」與「這是普遍真理」。 + +--- + +## 4. ORCA 操作細節(四輪 × 四支柱 / 15 步) + +ORCA 是 OOUX 的「how」——把研究丟進去、把結構吐出來的協定。**這是把名詞優先變成可自動化步驟的關鍵**,故完整記錄。 + +> **✅ 本次實抓(ooux.com ORCA 頁):** "ORCA is a 15-step meat-grinder of a process... It stands for Objects, Relationships, Calls-to-Action, and Attributes." 並描述為 double-diamond 之外的「第三顆鑽石(Structure)」。 + +### 四支柱(先設計任何畫面前要回答的四個問題) + +| 支柱 | 問題 | Instagram 例 | +| ------------------- | -------------------------------------------- | ----------------------------------------------------------- | +| **Objects** | 使用者心智模型裡有哪些物件? | POST, USER, STORY, COMMENT, PRODUCT, REEL | +| **Relationships** | 物件彼此如何關聯? | 一個 USER 有多個 POST;一個 POST 可 feature 一/多個 PRODUCT | +| **Calls-to-Action** | 每個物件「召喚」使用者做什麼動作(依角色)? | POST 可被 like / comment / bookmark / share | +| **Attributes** | 哪些內容元素 + metadata 組成物件? | POST 有 image、caption、timestamp、like 數 | + +### 四輪(每輪逐步提高保真度) + +```mermaid +flowchart LR + R1["Round 1 Discovery
煙燻出複雜度"] --> R2["Round 2 Requirements
解開複雜度"] + R2 --> R3["Round 3 Prioritization
為使用者與商業排序"] + R3 --> R4["Round 4 Representation
卡片/詳情/列表"] + R4 -. "發現需補研究" .-> R1 + style R1 fill:#c5f6fa,stroke:#0c8599 + style R2 fill:#ffe8cc,stroke:#d9480f + style R3 fill:#e5dbff,stroke:#5f3dc4 + style R4 fill:#d3f9d8,stroke:#2f9e44 +``` + +| 輪 | 目的 | O | R | C | A | +| -------------------- | --------------------------------------- | -------------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------- | ------------------------------------------------ | +| **1 Discovery** | 煙燻出複雜度,產出第一版 **Object Map** | **Noun Foraging**(挖名詞) | **Nested Object Matrix(NOM)**:物件×物件矩陣,交叉格寫關係(可規模化的 ER 圖) | **CTA Matrix**:物件×角色矩陣,交叉格寫動作 | **Object Mapping**:色標的屬性清單,疊在 NOM 上 | +| **2 Requirements** | 把複雜度放顯微鏡下解開 | **Object Guide**(強化版術語表) | **MCSFD**:關係的 Mechanics / Cardinality / Sorting / Filtering / Dependencies | CTA Matrix 演化成 **OO User Stories** | 屬性的條件邏輯、可能值、必填等細節 | +| **3 Prioritization** | 同時顧使用者與商業的優先序 | 降級/淘汰/合併物件 | **Nav Flow**(情境導航) | CTA 分期 + **CTA placement**(依使用者優先序) | 屬性 force-rank → 聚焦版 Object Map | +| **4 Representation** | 終於開始想「長怎樣」 | — | — | — | **卡片/詳情/列表草圖 → 可導航原型 → 測 O/R/C/A** | + +**核心交付物:Object Map** —— Prater 稱它是「對你數位產品結構的 X 光透視」。它把「內容 → IA → UI」串起來,是 ORCA 的招牌。前三輪每輪各有 O/R/C/A 四步,第四輪把所有 O/R/C/A 收斂成可測原型。 + +> **與 AEP 自動化的關聯線索(✅ 本次實抓,Medium 介紹文):** 作者點名「AI-friendly:Claude 的 OOUX Component Builder 能從物件模型自動產生 React component」。→ **物件模型 → component 的自動生成已有先例**,呼應 §7 的可行性判斷。 + +--- + +## 5. 更早的源流:直接操作與 noun-verb 語法 + +- 物件導向 UI 上溯 **Shneiderman 的 direct manipulation(直接操作)**(1982, _Behaviour & Information Technology_ 1(3):237–256, DOI 10.1080/01449298208914450)。✅(沿用既有查證) +- **noun-verb paradigm / Object-Action Interface(OAI):** 「先選物件、再選動作」被視為比「先選動作、再選物件」更貼近真實世界直覺。✅ +- **意義:** OOUX/OOUI 不是 2015 年憑空冒出,而是把 1980s 直接操作的「noun-verb」直覺重新包裝成可操作流程。 + +--- + +## 6. 決策框架:何時 task、何時 object + +日語圈收斂出清楚的「使い分け/掛け合わせ」判準(部落格層級為主,🟡 業界共識度高)。**這張表是 object-first 預設下的「逃生門」依據** —— 讓自動化在該用 task/wizard 的場景(ATM、onboarding、結帳)有原則地退讓。 + +| 判準 | 偏 task-oriented | 偏 object-oriented | +| ------------- | ------------------- | --------------------------- | +| 使用者目的 | 未定形、需引導 | 已明確、想自由探索 | +| 物件數量 | 單一對象 | 多對象、多關係 | +| 動作數量 | 單一線性流程 | 一個對象掛多種動作 | +| 使用者熟練度 | 新手 / onboarding | 熟手 / 日常重複使用 | +| off-path 容錯 | 低(線性假設) | 高(可隨時換對象) | +| 典型例 | ATM、註冊精靈、結帳 | 檔案總管、CRM、後台管理工具 | + +> **階段式設計(日語圈推薦解):** 同一產品可分階段——**初次走 task 指向引導,第二次起切回 object 指向**讓熟手自由操作。🟡 + +--- + +## 7. 套用到 AEP(分析,未執行) + +> 本節為設計分析,**不在本次提交範圍**。實作(新 skill / 新 artifact / schema / 既有 skill 改動)待使用者看完研究後另開計畫。 + +### 7.1 AEP 設計脊椎現況(verb-first + split context) + +- `/aep-envision` → **activity backbone**(動詞/旅程)+ context document。 +- `/aep-map` → system map(`architecture.modules`、`architecture.domain_model` = **後端**實體、`interfaces`)+ story graph(layer/wave)+ topology。 +- v2 product context → 穩定 product definition 住在 `product/index.yaml` 與 `product/maps//...`;`product-context.yaml` 偏 operational state(architecture、stories、routing、history)。 +- `/aep-calibrate` → 7 個品質維度的**人類對齊**;其中 heavy 的 visual-design / ux-flow / copy-tone 產出 `calibration/.yaml`。`ux-flow` 已涵蓋 journey / page map / transitions / density,但不是 object-first 的物件結構模型。 +- `/aep-build` → agent 依 story + calibration **即興**實作 UI。 + +### 7.2 確切的缺口 + +| 缺什麼 | 後果 | +| ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | +| 沒有「使用者面物件模型」(Object Map)橋接 story map → UI | build agent 逐 story 即興生畫面結構 | +| `domain_model` 是**後端資料模型**,不是使用者心智的物件(含 attributes / relationships / CTAs) | 畫面上「物件、欄位、動作、彼此巢狀」無統一藍圖 | +| `ux-flow` 校準的是 journey/page/transition,不是 object ontology / CTA matrix / attribute model | 有 flow 校準仍可能沒有穩定的卡片、列表、詳情頁與操作入口語法 | +| UI-facing story 沒有 mandatory structural gate | 該問使用者的 object/IA 決策被隱性留給 build agent 猜 | + +### 7.3 觸發規則:UI-facing 必定 ask user question + +建議規則很簡單:**任何 UI-facing capability 或 story 都必定觸發 Object Map review gate**。這不是「使用者覺得 flow 怪才校準」,而是 UI 結構進入 build 前的預設關卡,類似 `/aep-map` 的 System Map review。 + +Gate 的問法應該少而準: + +1. **物件邊界:** 這些是不是使用者真的會用來思考的物件?哪些名字、合併、拆分錯了? +2. **主導航錨點:** 這個 capability 主要應該讓使用者先看到/選擇哪個 object? +3. **例外 flow:** 哪些流程應該明確標成 task-oriented(例如 onboarding、checkout、one-shot wizard),而不是 object-first? + +### 7.4 可行性判斷:**可以,但產出是 draft → review → approved** + +ORCA 四輪可由 AEP **已有輸入**生成第一版,但物件邊界與 IA 是高影響設計決策,必須保留人工批准: + +| ORCA 輪/支柱 | AEP 既有輸入來源 | 自動化邊界 | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------ | --------------------------- | +| **O**bjects(Noun Foraging) | `product.activities`、story descriptions(可能是 string 或 `{ what_changes, why }`)、problem/personas | agent 提案,使用者改名/拆合 | +| **R**elationships(NOM) | `architecture.domain_model`(實體關係)+ capability maps + UI 巢狀推導 | agent 提案,使用者確認主從 | +| **C**TAs(CTA Matrix) | story/activity 的**動詞** → 掛到物件;依 persona/role 分標 | agent 提案,使用者確認優先 | +| **A**ttributes | 各物件內容元素 + metadata,分 core/secondary/meta;可從 domain fields、acceptance criteria、existing UI 萃取 | agent 提案,使用者定重點 | + +→ 與 visual-design 不同,這不需要長時間外部品味探索;但與 System Map 一樣,需要 review gate。產物是**標準化、機器可讀、可局部注入**的 Object Map,而不是隱性 prompt 記憶。 + +### 7.5 v2 artifact 形狀(不塞回 `product-context.yaml`) + +依 v2 product context split,Object Map 應是穩定產品設計檔案,不是 operational YAML 的大型內嵌段落: + +```text +product/ + index.yaml + object-model.yaml # cross-capability user-facing object ontology + maps/ + / + frame.yaml + map.yaml + object-map.yaml # capability-scoped ORCA / IA projection +product-context.yaml # operational state only +calibration/ + ux-flow.yaml # journey/page/transition calibration remains separate +``` + +`product/object-model.yaml` 放跨 capability 的穩定語言: + +- objects、aliases、description、source evidence +- cross-capability relationships +- shared attributes / metadata +- naming decisions and rejected names +- provenance / confidence / open questions + +`product/maps//object-map.yaml` 放實作要用的 capability projection: + +- primary objects / supporting objects +- relationship matrix 或 nested object matrix 摘要 +- CTA matrix(object × role/persona) +- attribute priority(core / secondary / metadata) +- representation hints(card / list / detail / empty state) +- navigation anchors and entry points +- `interaction_mode`: `object_first` by default, `task_oriented` only with reason +- status: `draft | approved | stale` + +`product-context.yaml` 最多保留薄 reference / history,例如: + +```yaml +calibration: + history: + - dimension: object-model + artifact_path: product/maps/dashboard/object-map.yaml + summary: "Approved object-first IA for dashboard capability" +``` + +Story YAML 不承擔 Object Map 本體。若需要追蹤,只加輕量 reference(例如 `object_model_refs: [...]`),並讓 schema 以 passthrough 相容舊專案。 + +### 7.6 建議工作流(草案,待另立計畫細化) + +- 新增一個 product-context skill(暫名 `/aep-model`,若嫌與 `domain_model`/`data-model` 撞名可用 `/aep-objectify`),位置在 **`/aep-map` 之後、UI-facing dispatch 之前**。 +- `/aep-envision`:若產品/能力是 UI-facing,預設宣告需要 object-model structural gate。 +- `/aep-map`:在 story decomposition 後,對每個 UI-facing capability 產生 `product/maps//object-map.yaml` draft;必要時同步更新 `product/object-model.yaml`。 +- `/aep-model`:呈現 draft,逐一問 §7.3 的少量關鍵問題;批准後把 status 改為 `approved`。 +- `/aep-dispatch`:UI story 只注入對應 capability 的 Object Map slice,不把全域 model 整份塞進 context。 +- `/aep-launch`:若 UI-facing story 找不到 approved Object Map,abort 並要求先跑 `/aep-model`。 +- `/aep-build`:UI 實作必須遵守 Object Map;`ux-flow.yaml` 仍負責 journey/page/transition,`visual-design.yaml` 仍負責視覺品味。 +- 護欄寫進 anti-pattern:**禁止把 USM backbone 1:1 直譯成 wizard**。 +- 先例佐證:Medium 文提到的「OOUX Component Builder(物件模型 → React component 自動生成)」顯示「Object Map → 元件/畫面」這段確實可被自動化。 + +--- + +## 8. 證據缺口與誠實標註 + +- **沒有把 USM 與 OOUX 點名並列正式比較的一手文獻。** §1–§7 把「USM = verb-first」歸類、以及「USM × OOUX 接合工作流」,**屬本研究綜合推論(合理但未經文獻背書)**。 +- **「object-based 客觀上更可用」缺量化使用性研究** —— 屬規範性主張,非實證結論。OOUX 對 task 分解「disjointed」的批評是設計哲學論證 / 實務觀察。 +- **OOUI 強版本(約八成業務 App 為 task-based、modeless 取代 modal):** 歸屬已確認為上野原話 ✅,但普適性日語圈有反論。 +- **Object Map gate 是 AEP 設計推論,不是 OOUX 原文要求。** 「UI-facing 必定觸發 ask-user review gate」是把 OOUX/OOUI 套到 AEP v2 split context 後的工作流設計選擇。 +- **Medium 原文對 scraper 回 403**:其內容經 `WebFetch`(小模型萃取)取得,並與 ooux.com 一手頁交叉核對;視為**次級介紹來源**,核心歸屬以 ooux.com / A List Apart 為準。 + +--- + +## 來源 + +**本次實抓(live this session, 2026-06-16)** + +- [ooux.com — What is OOUX](https://ooux.com/what-is-ooux)(defuddle)—— verb-slicing → disjointed;slice by nouns;people think in objects +- [ooux.com — Introducing ORCA](https://ooux.com/resources/introducing-orca-the-third-diamond-in-your-ux-process)(defuddle)—— 四輪 / 15 步 / 四支柱、Object Map、NOM、CTA Matrix、Object Guide、MCSFD、OO User Stories、Nav Flow +- [Medium — OOUX 物件導向用戶體驗設計革命(@5kahoisaac)](https://medium.com/@5kahoisaac/ooux-object-oriented-ux-%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91%E7%94%A8%E6%88%B6%E9%AB%94%E9%A9%97%E8%A8%AD%E8%A8%88%E9%9D%A9%E5%91%BD-c7eab8f17e30)(WebFetch;原站 403)—— OOUX 為上游工具、library 例、OOUX Component Builder + +**沿用既有對抗式查證筆記(《Task vs Object 設計之爭》, 2026-06-09/10)** + +- [Sophia Prater — Object-Oriented UX(A List Apart, 2015-10-20)](https://alistapart.com/article/object-oriented-ux/) +- [Sophia Prater — OOUX: A Foundation for Interaction Design(A List Apart, 2016-04-19)](https://alistapart.com/article/ooux-a-foundation-for-interaction-design/) +- [ソシオメディア — OOUI 関連(8740)](https://www.sociomedia.co.jp/8740) +- [ソシオメディア — 名詞→動詞 操作構文(7279)](https://www.sociomedia.co.jp/7279) +- [ekrits — 上野学 OOUI 論考](https://ekrits.jp/2020/10/3914/) +- [Shneiderman 1982 — direct manipulation(BIT 1(3):237–256)](https://www.tandfonline.com/doi/abs/10.1080/01449298208914450) + +**次級/背景** + +- [Wikipedia — Object–action interface](https://en.wikipedia.org/wiki/Object%E2%80%93action_interface) +- [Nielsen Norman Group — Direct Manipulation](https://www.nngroup.com/articles/direct-manipulation/) +- [セブンデックス — OOUI とタスク指向UI の掛け合わせ](https://sevendex.com/post/3676/) + +--- + +> **下一步(不在本次提交):** 依 §7 另開計畫,設計 `/aep-model`(ORCA draft + review gate skill)、`product/object-model.yaml`、`product/maps//object-map.yaml`,以及 envision/map/dispatch/launch/build/validate/docs 的對應改動。 diff --git a/skills/agentic-development-workflow/build/SKILL.md b/skills/agentic-development-workflow/build/SKILL.md index 821cc8c..eca6675 100644 --- a/skills/agentic-development-workflow/build/SKILL.md +++ b/skills/agentic-development-workflow/build/SKILL.md @@ -311,6 +311,16 @@ Invoke the apply skill for guidance on implementing each task: The agent works **one task at a time**. Each task = one commit. The branch's commit history mirrors `tasks.md` 1:1, which makes per-task code review on the PR straightforward. +> **UI-facing stories — obey the Object Map.** If the dispatched context package +> includes an Object Map slice (objects, attributes, relationships, CTAs, screens), +> treat it as binding structure: build the listed screens object-first (noun→verb — +> object collection/detail, the given CTA placements), use the specified core +> attributes, and do **not** introduce objects/screens outside the slice or collapse +> a flow into a step-by-step wizard unless the slice marks it `task_oriented`. +> Visual look (`calibration/visual-design.yaml`), copy voice (`copy-tone`), and +> journey/transition (`ux-flow`) still govern taste — the Object Map governs object +> structure and CTA grammar. + Update the progress file checkbox for each completed task, and mark the Phase 4 checkbox when all tasks are done. > **If you need to amend a just-committed task** (e.g., you forgot a file), use `git commit --amend --no-edit` _before_ moving on. Once you've committed the next task, leave prior commits alone — fixes belong as a follow-up commit, not a rewrite. diff --git a/skills/agentic-development-workflow/launch/SKILL.md b/skills/agentic-development-workflow/launch/SKILL.md index b466d2c..745b67d 100644 --- a/skills/agentic-development-workflow/launch/SKILL.md +++ b/skills/agentic-development-workflow/launch/SKILL.md @@ -74,7 +74,25 @@ type="${calibration_type:-visual-design}" **If the calibration artifact does not exist — ABORT.** The user must run `/aep-calibrate ` first. Agents dispatched without calibration context will reproduce the same generic output that created the need for alignment in the first place. -### 4. Clean up orphan worktree/branch from prior failed launches +### 4. Verify Object Map for UI-facing stories + +If the story is UI-facing (has `object_model_refs`, `calibration_type` in +{visual-design, ux-flow}, or a non-null `activity` whose module is `kind: ui`): + +```bash +# Find the object-map that COVERS this story (capability-agnostic — works whether or +# not story.capability is set), then require status: approved. +MAP=$(grep -rl 'story: ' product/maps/*/object-map.yaml 2>/dev/null | head -1) +{ [ -n "$MAP" ] && grep -q 'status: approved' "$MAP"; } \ + && echo "object map approved: $MAP" || echo "MISSING" +``` + +**If no approved Object Map covers the story (missing, `draft`, or `stale`) — +ABORT.** The user must run `/aep-model` first. A UI-facing agent launched without +the noun-first Object Map will invent ad-hoc, one-step-one-screen structure — +exactly what the Object Map exists to prevent. + +### 5. Clean up orphan worktree/branch from prior failed launches Git worktree, unlike jj's `jj workspace forget`, does not auto-clean if a previous `/aep-launch` died mid-flight. Two failure modes can block re-launch — both are silent and confusing on first encounter. Run these idempotent checks before `git worktree add`: diff --git a/skills/product-context/_shared/templates/object-map-schema.yaml b/skills/product-context/_shared/templates/object-map-schema.yaml new file mode 100644 index 0000000..e87ea50 --- /dev/null +++ b/skills/product-context/_shared/templates/object-map-schema.yaml @@ -0,0 +1,103 @@ +# Object Map Schema — product/maps//object-map.yaml +# +# The capability-scoped ORCA / IA projection (OOUX Rounds O→R→C→A plus Round 4 +# representation hints). One file per UI-facing capability. This is the +# noun-first bridge between the verb-first story map and the actual UI: it says +# WHICH objects appear, WHAT their fields are, HOW they nest, and WHAT actions +# (CTAs) hang off each — BEFORE any screen is designed. +# +# Lifecycle: /aep-map writes a `status: draft`; /aep-model presents it, asks the +# few high-leverage questions (object boundaries, primary anchor, task-flow +# exceptions), and on approval flips `status: approved`. /aep-dispatch injects +# ONLY the slice a story touches; /aep-launch refuses a UI-facing story whose +# capability has no `approved` object-map. +# +# This file is STRUCTURAL, not visual. Colors/typography/spacing stay in +# calibration/visual-design.yaml; journey/page/transition stay in +# calibration/ux-flow.yaml. The object-map governs object structure + CTA grammar. +# +# See model/references/orca-process.md for derivation rules. + +schema: object-map/v1 +capability: # product/index.yaml capabilities[].id; in v1/single-journey use the default capability = project slug +# Lifecycle: /aep-map writes `draft`; /aep-model flips to `approved`; /aep-map flips an +# `approved` map back to `stale` when it re-decomposes stories/activities under this +# capability. Dispatch/launch gates treat `draft` AND `stale` as "not ready" (abort → /aep-model). +status: draft # draft | approved | stale +generated_by: aep-map # aep-map (draft) | aep-model (refined/approved) +generated_at: +approved_at: null +approved_by: null # "human" — set at the /aep-model review gate +object_model_ref: product/object-model.yaml # the shared ontology this projects from + +# Canonical screen-id grammar (used wherever a screen is referenced, incl. coverage): +# : where ∈ collection | detail | create | edit +# e.g. "order:collection", "order:detail". screens[] below declares which views exist +# per object; coverage[].screens references them by this composite id. + +# ─── ROUND O — objects in play for THIS capability ────────────── +# Subset of product/object-model.yaml. Primary objects get home screens; support +# objects only appear nested inside others. +primary_objects: [] # object ids the user navigates TO (each gets a collection + detail) +supporting_objects: [] # object ids shown only inside another object's views + +# ─── ROUND R — Nested Object Matrix (capability-local) ────────── +relationships: + - from: + to: + cardinality: one_to_many # one_to_one | one_to_many | many_to_many + nested: true # embed `to` inside `from`'s detail view + nav: true # this link is a navigation path the user can follow + +# ─── ROUND C — CTA Matrix (object × role → actions) ───────────── +# Verbs are mined from the stories/activities for this capability and hung onto +# the object they act on. role references product/index.yaml personas[].id. +ctas: + - object: + role: + actions: + - verb: "create" # the action (imperative) + from_story: # provenance: the story whose verb this is (or activity:) + placement: collection # collection | detail | inline | global + priority: primary # primary | secondary + +# ─── ROUND A — attribute priority per object ──────────────────── +# What shows where. core → card + detail; secondary → detail only; +# metadata → sort/filter keys (not necessarily rendered). +attributes: + - object: + core: [] # always visible + secondary: [] # detail only + metadata: [] # sort/filter keys + +# ─── ROUND 4 — representation hints (structural IA only) ───────── +# NOT visual design. Just which structural views each object needs. +screens: + - object: + views: ["collection", "detail"] # collection (list/grid) | detail | create | edit + card_fields: [] # subset of attributes.core shown on the card + empty_state: "What to show when there are none" + +navigation: + anchor_object: # the capability's home object (what the user sees first) + entry_points: [] # object ids reachable from primary nav + +# ─── OBJECT-FIRST DEFAULT + TASK-ORIENTED ESCAPE HATCH ────────── +# Every flow defaults to object_first (noun→verb: pick an object, then act). +# List a flow here ONLY to deviate to task_oriented (a deliberate wizard), with a +# reason grounded in the decision framework (single goal / novice / linear / low +# off-path tolerance — see orca-process.md and docs/research/ooux-object-modeling.md §6). +interaction_modes: + - flow: # e.g., "onboarding", "checkout" + mode: task_oriented # object_first (default, no need to list) | task_oriented + reason: "Why a wizard wins here." + +# ─── STORY COVERAGE INDEX ─────────────────────────────────────── +# Which stories realize which objects/screens. /aep-dispatch reads this to inject +# the minimal slice into a story's context package. +coverage: + - story: + objects: [] + screens: [":collection"] + +open_questions: [] # unresolved IA/object decisions surfaced to the human diff --git a/skills/product-context/_shared/templates/object-model-schema.yaml b/skills/product-context/_shared/templates/object-model-schema.yaml new file mode 100644 index 0000000..d4b7de6 --- /dev/null +++ b/skills/product-context/_shared/templates/object-model-schema.yaml @@ -0,0 +1,82 @@ +# Object Model Schema — product/object-model.yaml +# +# The cross-capability, USER-FACING object ontology (OOUX Rounds O + R). +# Created and updated by /aep-model. This is a STABLE product-design file — it +# lives under product/ alongside product/index.yaml, NOT inside the operational +# product-context.yaml. product-context.yaml carries only a thin reference in +# calibration.history (dimension: object-model). +# +# These are MENTAL-MODEL objects — the things the user perceives and acts on — +# NOT backend data entities. They may map onto architecture.domain_model entities +# (cross-linked via `backs_onto`), but the two are different lenses: domain_model +# is how the system stores data; object-model is how the user thinks. +# +# Authoring: /aep-map produces a first draft; /aep-model presents it, asks the +# few high-leverage questions, and the human approves. Do not hand-author the +# whole thing — it is mined from existing AEP artifacts (activities, stories, +# domain_model) and then human-reviewed. +# +# See model/references/orca-process.md for how each field is derived. + +schema: object-model/v1 +project: +updated_at: + +# ─── OBJECTS (ORCA Round O — Noun Foraging) ────────────────────── +# The shared language of user-facing objects across the whole product. +# Foraged from product.activities, story descriptions, problem/persona text, +# and architecture.domain_model. Keep names consistent with docs/glossary.md +# (ubiquitous language). +objects: + - id: # e.g., "order", "report", "avatar" + name: "Human-readable" # e.g., "Order" + aliases: [] # other names users or the team call this thing + description: "One sentence — what this object is in the user's world" + backs_onto: [] # architecture.domain_model entity names (0..n; may be empty for pure-UI objects) + appears_in: [] # capability ids (from product/index.yaml capabilities[]) that use this object + source_evidence: # noun-foraging provenance — where this object was found + - "activity:" + - "story:" + - "domain_model:" + confidence: high # high | medium | low — extraction confidence (low → ask the human) + naming_decision: null # why this name won (fill when there was a real choice) + open_questions: [] # unresolved questions about this object's boundary + +# ─── RELATIONSHIPS (ORCA Round R — cross-capability spine) ─────── +# The shared backbone of the Nested Object Matrix. Capability-LOCAL relationships +# live in product/maps//object-map.yaml; only put cross-capability +# links here. +relationships: + - from: + to: + cardinality: one_to_many # one_to_one | one_to_many | many_to_many + label: "owns" # how the user understands the link (verb phrase) + nested: true # does `from` show `to` inline (drives detail-view composition)? + +# ─── SHARED ATTRIBUTES ─────────────────────────────────────────── +# Attributes an object carries everywhere. Capability-specific attributes belong +# in the capability object-map. kind: core (always shown) | secondary (detail +# only) | metadata (sort/filter keys). +shared_attributes: + - object: + attributes: + - name: + kind: core # core | secondary | metadata + type: string # display-type hint (string | number | date | enum | image | ref) + notes: null + +# ─── NAMING DECISIONS (ubiquitous language log) ────────────────── +# Keep the user-facing vocabulary stable. Mirror confirmed names into +# docs/glossary.md when they become product-wide terms. +naming_decisions: + - object: + chosen: "Order" + rejected: ["Purchase", "Transaction"] + reason: "Users say 'my orders', not 'my transactions'." + +# ─── PROVENANCE ────────────────────────────────────────────────── +provenance: + generated_by: aep-map # aep-map (first draft) | aep-model (refined at review) + rounds_completed: ["O", "R"] # which ORCA rounds this file reflects + reviewed: false # set true after the /aep-model human review gate + reviewed_at: null diff --git a/skills/product-context/_shared/templates/product-context-schema.yaml b/skills/product-context/_shared/templates/product-context-schema.yaml index 12d6891..c34be55 100644 --- a/skills/product-context/_shared/templates/product-context-schema.yaml +++ b/skills/product-context/_shared/templates/product-context-schema.yaml @@ -7,6 +7,7 @@ # Sections are populated incrementally: # /aep-envision → opportunity, product (incl. quality_dimensions), calibration.plan # /aep-map → architecture, stories, topology, layer_gates, cost +# /aep-model → product/object-model.yaml + product/maps//object-map.yaml (standalone); calibration.history (dimension: object-model); stories[].object_model_refs # /aep-calibrate → calibration.history, inline updates to architecture/product sections # /aep-dispatch → stories[].status, stories[].openspec_change (per story) # /aep-build → .dev-workflow/signals/status.json (story_status, pr_url, cost_usd) @@ -157,8 +158,11 @@ product: # Dimensions where human judgment is needed that agents cannot provide. # Declared during /aep-envision, checked by /aep-reflect, executed by /aep-calibrate. # Not every dimension needs calibration — only those where "correct but not right" is likely. + # `object-model` is a STRUCTURAL gate (auto-drafted by /aep-model, human-approved), + # not a taste calibration — declare it for UI-facing products so UI stories get a + # noun-first Object Map before build. See skills/product-context/model. quality_dimensions: - - dimension: visual-design # visual-design | ux-flow | api-surface | data-model | scope-direction | copy-tone | performance-quality + - dimension: visual-design # visual-design | ux-flow | object-model | api-surface | data-model | scope-direction | copy-tone | performance-quality criticality: high # high | medium | low first_calibration_layer: 0.5 rationale: "Why this dimension needs human calibration" @@ -174,6 +178,8 @@ calibration: - layer: 0.5 dimensions: ["visual-design"] # from product.quality_dimensions trigger: "Condition that activates this calibration" + # /aep-calibrate appends taste-dimension decisions here; /aep-model appends + # object-model approvals (artifact_path → product/maps//object-map.yaml). history: - dimension: visual-design calibrated_at: @@ -192,6 +198,7 @@ architecture: technical_spec: null # optional path to standalone technical specification document (e.g., "docs/technical-spec.md") modules: - name: + kind: backend # ui | backend | shared — `ui` modules render user-facing surfaces (used by /aep-model + the UI-facing story trigger) responsibility: "What this module does" does_not: "What this module does NOT do (defines the boundary)" owns: @@ -295,7 +302,9 @@ stories: layer: 0 module: activity: null # user activity from product.activities (null for infrastructure stories) + capability: null # capability id (product/index.yaml capabilities[].id); /aep-map sets it. Null in v1/single-journey → resolves to the default capability (project slug). Used to locate product/maps//object-map.yaml. calibration_type: null # visual-design | ux-flow | api-surface | data-model | scope-direction | copy-tone | performance-quality (null for non-calibration stories) + object_model_refs: [] # /aep-model: object-map slice(s) this UI story realizes, as "#" where ∈ object-map primary_objects/supporting_objects, e.g. ["product/maps/dashboard/object-map.yaml#order"] (empty for non-UI stories) slice: 1 # execution slice within layer (parallel batch) status: pending priority: critical # critical | high | medium | low diff --git a/skills/product-context/dispatch/SKILL.md b/skills/product-context/dispatch/SKILL.md index 0e2391a..a9ef675 100644 --- a/skills/product-context/dispatch/SKILL.md +++ b/skills/product-context/dispatch/SKILL.md @@ -10,7 +10,7 @@ Bridge between the product context (control plane) and the feature lifecycle (ex **Where this fits:** ``` -/aep-envision → /aep-map → /aep-scaffold +/aep-envision → /aep-map → /aep-model (UI-facing) → /aep-scaffold → [ /aep-dispatch → /aep-design → /aep-launch → /aep-build → /aep-wrap ] ▲ you are here → /aep-reflect → loop @@ -460,6 +460,41 @@ No additional context needed — decisions are already in the architecture secti **Backward compatibility:** For `.5` layer stories without `calibration_type` set, default to visual-design. Check both `calibration/visual-design.yaml` and `design-context.yaml` (legacy path). +#### Object Map Context (UI-facing stories) + +A story is **UI-facing** when it has `object_model_refs` set, `calibration_type` in +{visual-design, ux-flow}, or a non-null `activity` whose module has `kind: ui` +(`architecture.modules[].kind`). For these, inject the **Object Map slice**, not the +whole model: + +1. **Resolve the capability:** use `story.capability` if set; otherwise (v1 / + single-journey) the default capability is the project slug. The Object Map is + `product/maps//object-map.yaml`. +2. **Gate (must pass to dispatch):** the resolved object-map must exist, have + `status: approved`, and its `coverage[]` must list this story id. If it is missing, + `status` is `draft` or `stale`, or it does not cover the story → **do not + dispatch**; instruct the user to run `/aep-model` first (same posture as the + calibration gate). If `story.capability` is unset, fall back to scanning + `product/maps/*/object-map.yaml` for a `coverage[].story` match — an `approved` + match resolves the capability. +3. From the map's `coverage` index, select only the objects this story realizes and + include just those entries: their attributes (core/secondary/metadata), the + relationships among them, the CTAs (object × role) on them, and the screen(s) the + story builds. Skip unrelated objects — keep the slice minimal. +4. Include the object-first directive: + + ```markdown + This story realizes objects from an approved Object Map. + Build object-first (noun→verb): the listed screens, object cards/detail, + attributes, and CTA placements come from product/maps//object-map.yaml. + Do not introduce objects or screen structures not in this slice, and do not + collapse the flow into a step-by-step wizard unless the map marks it task_oriented. + ``` + +Visual look, copy voice, and journey/transition still come from +`calibration/{visual-design,copy-tone,ux-flow}.yaml` — the Object Map governs object +structure and CTA grammar, not taste. + ### Assembly Rules 1. **Prune aggressively** — irrelevant context degrades agent performance diff --git a/skills/product-context/envision/SKILL.md b/skills/product-context/envision/SKILL.md index 3688acc..df41071 100644 --- a/skills/product-context/envision/SKILL.md +++ b/skills/product-context/envision/SKILL.md @@ -94,6 +94,7 @@ Continue the conversation from Phase 0, now focused on product specifics. Lines - **Quality dimensions:** Which dimensions of this product require human judgment that agents cannot provide? Not every dimension needs calibration — only those where "correct but not right" is likely. Common dimensions: - **Visual design** — brand identity, color, typography, layout (nearly always needed for user-facing products) - **UX flow** — user journey, information architecture, page transitions + - **Object model** — the noun-first object structure behind the UI (objects, their attributes, relationships, and the actions/CTAs on each). Unlike the others this is a **structural gate**, not a taste calibration: `/aep-map` auto-drafts an Object Map and `/aep-model` gets a short human approval. **Declare `object-model` by default for any UI-facing product/capability** — it is what stops build agents from inventing one-step-one-screen task-wizard UIs. (Skip only for pure-backend/CLI products.) - **API surface** — endpoint naming, grouping, error contracts (when external consumers exist) - **Data model** — entity naming, field semantics (when domain language matters) - **Copy/tone** — brand voice, error messages, empty states diff --git a/skills/product-context/envision/templates/object-map-schema.yaml b/skills/product-context/envision/templates/object-map-schema.yaml new file mode 100644 index 0000000..e87ea50 --- /dev/null +++ b/skills/product-context/envision/templates/object-map-schema.yaml @@ -0,0 +1,103 @@ +# Object Map Schema — product/maps//object-map.yaml +# +# The capability-scoped ORCA / IA projection (OOUX Rounds O→R→C→A plus Round 4 +# representation hints). One file per UI-facing capability. This is the +# noun-first bridge between the verb-first story map and the actual UI: it says +# WHICH objects appear, WHAT their fields are, HOW they nest, and WHAT actions +# (CTAs) hang off each — BEFORE any screen is designed. +# +# Lifecycle: /aep-map writes a `status: draft`; /aep-model presents it, asks the +# few high-leverage questions (object boundaries, primary anchor, task-flow +# exceptions), and on approval flips `status: approved`. /aep-dispatch injects +# ONLY the slice a story touches; /aep-launch refuses a UI-facing story whose +# capability has no `approved` object-map. +# +# This file is STRUCTURAL, not visual. Colors/typography/spacing stay in +# calibration/visual-design.yaml; journey/page/transition stay in +# calibration/ux-flow.yaml. The object-map governs object structure + CTA grammar. +# +# See model/references/orca-process.md for derivation rules. + +schema: object-map/v1 +capability: # product/index.yaml capabilities[].id; in v1/single-journey use the default capability = project slug +# Lifecycle: /aep-map writes `draft`; /aep-model flips to `approved`; /aep-map flips an +# `approved` map back to `stale` when it re-decomposes stories/activities under this +# capability. Dispatch/launch gates treat `draft` AND `stale` as "not ready" (abort → /aep-model). +status: draft # draft | approved | stale +generated_by: aep-map # aep-map (draft) | aep-model (refined/approved) +generated_at: +approved_at: null +approved_by: null # "human" — set at the /aep-model review gate +object_model_ref: product/object-model.yaml # the shared ontology this projects from + +# Canonical screen-id grammar (used wherever a screen is referenced, incl. coverage): +# : where ∈ collection | detail | create | edit +# e.g. "order:collection", "order:detail". screens[] below declares which views exist +# per object; coverage[].screens references them by this composite id. + +# ─── ROUND O — objects in play for THIS capability ────────────── +# Subset of product/object-model.yaml. Primary objects get home screens; support +# objects only appear nested inside others. +primary_objects: [] # object ids the user navigates TO (each gets a collection + detail) +supporting_objects: [] # object ids shown only inside another object's views + +# ─── ROUND R — Nested Object Matrix (capability-local) ────────── +relationships: + - from: + to: + cardinality: one_to_many # one_to_one | one_to_many | many_to_many + nested: true # embed `to` inside `from`'s detail view + nav: true # this link is a navigation path the user can follow + +# ─── ROUND C — CTA Matrix (object × role → actions) ───────────── +# Verbs are mined from the stories/activities for this capability and hung onto +# the object they act on. role references product/index.yaml personas[].id. +ctas: + - object: + role: + actions: + - verb: "create" # the action (imperative) + from_story: # provenance: the story whose verb this is (or activity:) + placement: collection # collection | detail | inline | global + priority: primary # primary | secondary + +# ─── ROUND A — attribute priority per object ──────────────────── +# What shows where. core → card + detail; secondary → detail only; +# metadata → sort/filter keys (not necessarily rendered). +attributes: + - object: + core: [] # always visible + secondary: [] # detail only + metadata: [] # sort/filter keys + +# ─── ROUND 4 — representation hints (structural IA only) ───────── +# NOT visual design. Just which structural views each object needs. +screens: + - object: + views: ["collection", "detail"] # collection (list/grid) | detail | create | edit + card_fields: [] # subset of attributes.core shown on the card + empty_state: "What to show when there are none" + +navigation: + anchor_object: # the capability's home object (what the user sees first) + entry_points: [] # object ids reachable from primary nav + +# ─── OBJECT-FIRST DEFAULT + TASK-ORIENTED ESCAPE HATCH ────────── +# Every flow defaults to object_first (noun→verb: pick an object, then act). +# List a flow here ONLY to deviate to task_oriented (a deliberate wizard), with a +# reason grounded in the decision framework (single goal / novice / linear / low +# off-path tolerance — see orca-process.md and docs/research/ooux-object-modeling.md §6). +interaction_modes: + - flow: # e.g., "onboarding", "checkout" + mode: task_oriented # object_first (default, no need to list) | task_oriented + reason: "Why a wizard wins here." + +# ─── STORY COVERAGE INDEX ─────────────────────────────────────── +# Which stories realize which objects/screens. /aep-dispatch reads this to inject +# the minimal slice into a story's context package. +coverage: + - story: + objects: [] + screens: [":collection"] + +open_questions: [] # unresolved IA/object decisions surfaced to the human diff --git a/skills/product-context/envision/templates/object-model-schema.yaml b/skills/product-context/envision/templates/object-model-schema.yaml new file mode 100644 index 0000000..d4b7de6 --- /dev/null +++ b/skills/product-context/envision/templates/object-model-schema.yaml @@ -0,0 +1,82 @@ +# Object Model Schema — product/object-model.yaml +# +# The cross-capability, USER-FACING object ontology (OOUX Rounds O + R). +# Created and updated by /aep-model. This is a STABLE product-design file — it +# lives under product/ alongside product/index.yaml, NOT inside the operational +# product-context.yaml. product-context.yaml carries only a thin reference in +# calibration.history (dimension: object-model). +# +# These are MENTAL-MODEL objects — the things the user perceives and acts on — +# NOT backend data entities. They may map onto architecture.domain_model entities +# (cross-linked via `backs_onto`), but the two are different lenses: domain_model +# is how the system stores data; object-model is how the user thinks. +# +# Authoring: /aep-map produces a first draft; /aep-model presents it, asks the +# few high-leverage questions, and the human approves. Do not hand-author the +# whole thing — it is mined from existing AEP artifacts (activities, stories, +# domain_model) and then human-reviewed. +# +# See model/references/orca-process.md for how each field is derived. + +schema: object-model/v1 +project: +updated_at: + +# ─── OBJECTS (ORCA Round O — Noun Foraging) ────────────────────── +# The shared language of user-facing objects across the whole product. +# Foraged from product.activities, story descriptions, problem/persona text, +# and architecture.domain_model. Keep names consistent with docs/glossary.md +# (ubiquitous language). +objects: + - id: # e.g., "order", "report", "avatar" + name: "Human-readable" # e.g., "Order" + aliases: [] # other names users or the team call this thing + description: "One sentence — what this object is in the user's world" + backs_onto: [] # architecture.domain_model entity names (0..n; may be empty for pure-UI objects) + appears_in: [] # capability ids (from product/index.yaml capabilities[]) that use this object + source_evidence: # noun-foraging provenance — where this object was found + - "activity:" + - "story:" + - "domain_model:" + confidence: high # high | medium | low — extraction confidence (low → ask the human) + naming_decision: null # why this name won (fill when there was a real choice) + open_questions: [] # unresolved questions about this object's boundary + +# ─── RELATIONSHIPS (ORCA Round R — cross-capability spine) ─────── +# The shared backbone of the Nested Object Matrix. Capability-LOCAL relationships +# live in product/maps//object-map.yaml; only put cross-capability +# links here. +relationships: + - from: + to: + cardinality: one_to_many # one_to_one | one_to_many | many_to_many + label: "owns" # how the user understands the link (verb phrase) + nested: true # does `from` show `to` inline (drives detail-view composition)? + +# ─── SHARED ATTRIBUTES ─────────────────────────────────────────── +# Attributes an object carries everywhere. Capability-specific attributes belong +# in the capability object-map. kind: core (always shown) | secondary (detail +# only) | metadata (sort/filter keys). +shared_attributes: + - object: + attributes: + - name: + kind: core # core | secondary | metadata + type: string # display-type hint (string | number | date | enum | image | ref) + notes: null + +# ─── NAMING DECISIONS (ubiquitous language log) ────────────────── +# Keep the user-facing vocabulary stable. Mirror confirmed names into +# docs/glossary.md when they become product-wide terms. +naming_decisions: + - object: + chosen: "Order" + rejected: ["Purchase", "Transaction"] + reason: "Users say 'my orders', not 'my transactions'." + +# ─── PROVENANCE ────────────────────────────────────────────────── +provenance: + generated_by: aep-map # aep-map (first draft) | aep-model (refined at review) + rounds_completed: ["O", "R"] # which ORCA rounds this file reflects + reviewed: false # set true after the /aep-model human review gate + reviewed_at: null diff --git a/skills/product-context/envision/templates/product-context-schema.yaml b/skills/product-context/envision/templates/product-context-schema.yaml index 12d6891..c34be55 100644 --- a/skills/product-context/envision/templates/product-context-schema.yaml +++ b/skills/product-context/envision/templates/product-context-schema.yaml @@ -7,6 +7,7 @@ # Sections are populated incrementally: # /aep-envision → opportunity, product (incl. quality_dimensions), calibration.plan # /aep-map → architecture, stories, topology, layer_gates, cost +# /aep-model → product/object-model.yaml + product/maps//object-map.yaml (standalone); calibration.history (dimension: object-model); stories[].object_model_refs # /aep-calibrate → calibration.history, inline updates to architecture/product sections # /aep-dispatch → stories[].status, stories[].openspec_change (per story) # /aep-build → .dev-workflow/signals/status.json (story_status, pr_url, cost_usd) @@ -157,8 +158,11 @@ product: # Dimensions where human judgment is needed that agents cannot provide. # Declared during /aep-envision, checked by /aep-reflect, executed by /aep-calibrate. # Not every dimension needs calibration — only those where "correct but not right" is likely. + # `object-model` is a STRUCTURAL gate (auto-drafted by /aep-model, human-approved), + # not a taste calibration — declare it for UI-facing products so UI stories get a + # noun-first Object Map before build. See skills/product-context/model. quality_dimensions: - - dimension: visual-design # visual-design | ux-flow | api-surface | data-model | scope-direction | copy-tone | performance-quality + - dimension: visual-design # visual-design | ux-flow | object-model | api-surface | data-model | scope-direction | copy-tone | performance-quality criticality: high # high | medium | low first_calibration_layer: 0.5 rationale: "Why this dimension needs human calibration" @@ -174,6 +178,8 @@ calibration: - layer: 0.5 dimensions: ["visual-design"] # from product.quality_dimensions trigger: "Condition that activates this calibration" + # /aep-calibrate appends taste-dimension decisions here; /aep-model appends + # object-model approvals (artifact_path → product/maps//object-map.yaml). history: - dimension: visual-design calibrated_at: @@ -192,6 +198,7 @@ architecture: technical_spec: null # optional path to standalone technical specification document (e.g., "docs/technical-spec.md") modules: - name: + kind: backend # ui | backend | shared — `ui` modules render user-facing surfaces (used by /aep-model + the UI-facing story trigger) responsibility: "What this module does" does_not: "What this module does NOT do (defines the boundary)" owns: @@ -295,7 +302,9 @@ stories: layer: 0 module: activity: null # user activity from product.activities (null for infrastructure stories) + capability: null # capability id (product/index.yaml capabilities[].id); /aep-map sets it. Null in v1/single-journey → resolves to the default capability (project slug). Used to locate product/maps//object-map.yaml. calibration_type: null # visual-design | ux-flow | api-surface | data-model | scope-direction | copy-tone | performance-quality (null for non-calibration stories) + object_model_refs: [] # /aep-model: object-map slice(s) this UI story realizes, as "#" where ∈ object-map primary_objects/supporting_objects, e.g. ["product/maps/dashboard/object-map.yaml#order"] (empty for non-UI stories) slice: 1 # execution slice within layer (parallel batch) status: pending priority: critical # critical | high | medium | low diff --git a/skills/product-context/map/SKILL.md b/skills/product-context/map/SKILL.md index e8a7441..80c891a 100644 --- a/skills/product-context/map/SKILL.md +++ b/skills/product-context/map/SKILL.md @@ -42,7 +42,7 @@ If product definition is missing (no `product` section in either file), run `/ae Produce a **System Map** (see `templates/system-map.md`) from the Context Document: -- **Modules:** Major components with clear responsibility boundaries. Each module's "does not" definition is as important as its "does" definition. +- **Modules:** Major components with clear responsibility boundaries. Each module's "does not" definition is as important as its "does" definition. Set each module's `kind` (`ui` | `backend` | `shared`) — `ui` modules render user-facing surfaces, which drives the UI-facing story trigger used by `/aep-model`, dispatch, and launch. - **Interface contracts:** For every module-to-module connection, define the exact API surface — endpoints, data shapes, error contracts. These are not documentation; they are executable specifications enforced by contract tests. - **Data flow:** How information moves through the system for each user journey in the MVP contract. - **Third-party boundaries:** External service integration points with failure modes. @@ -98,7 +98,7 @@ Each story follows the **Story Spec** format (see `templates/story-spec.md`) and ### Activity Mapping -After decomposition agents produce their stories, map each story to a user activity from `product.activities`: +After decomposition agents produce their stories, map each story to a user activity from `product.activities` (and, in split mode, set `story.capability` to the owning `capabilities[]` id — this is how dispatch/launch later locate the story's Object Map; leave null in v1/single-journey, where the default capability is the project slug): - Stories that directly enable a user-facing capability get the activity they serve (e.g., "Create presigned upload URL" → `create-profile` because it enables the user to upload a selfie). - **Infrastructure/foundation stories that don't map to any specific user activity leave `activity` as null.** These are implementation enablers — they appear in the architecture view but NOT in the user journey story map. This is correct and expected. @@ -161,7 +161,7 @@ not silently no-op. If `product/index.yaml` exists (created by `/aep-envision` for multi-journey products), also write per-capability `map.yaml` files: -- `product/maps//aep-map.yaml` — backbone activities, layers, story stubs for this capability +- `product/maps//map.yaml` — backbone activities, layers, story stubs for this capability - Story stubs in `map.yaml` are sketches; the full stories in `product-context.yaml` are the operational versions > **Split mode note:** In split mode, the capability map's `map.yaml` story stubs are narrative sketches. The full stories are written to `product-context.yaml`, and `product/index.yaml` is NOT modified by `/aep-map` (it only reads from it). @@ -185,6 +185,49 @@ After defining each implementation layer, review `calibration.plan` from `produc - **Layer 1.5, 2.5** (subsequent `.5` layers): Extend calibration to new patterns. `/aep-calibrate` detects existing calibration artifacts and generates focused briefs covering only the delta. - **Opt-in, not automatic.** The `/aep-reflect` step after each layer classifies calibration needs by dimension. The human decides which dimensions need attention. But the workflow makes the question unavoidable. +> **Object Map feeds the heavy UI dimensions.** Once `/aep-model` has approved an +> Object Map for a capability, the `visual-design` and `ux-flow` `.5`-layer briefs +> derive their "pages/screens to design" from the Object Map's screen plan +> (`product/maps//object-map.yaml` → `screens`) instead of an ad-hoc `routes/` +> scan. Structure first (object-model), then taste (visual-design) and journey +> (ux-flow). + +### Object Map Drafts (UI-facing capabilities) + +After stories are decomposed, produce a **draft** noun-first Object Map for each +UI-facing capability (a capability that declared the `object-model` quality +dimension, or `visual-design`/`ux-flow`, or has user-facing stories). This is the +bridge from the verb-first story map to the UI — it stops build agents from +inventing one-step-one-screen task-wizard UIs. + +Mine the draft with the ORCA rounds (Objects → Relationships → CTAs → Attributes → +screens) from `product.activities`, `stories[].description`, and +`architecture.domain_model`. See the `/aep-model` skill and its +`references/orca-process.md` for the derivation, and +`templates/object-model-schema.yaml` + `templates/object-map-schema.yaml` for the +structure. Write: + +- `product/object-model.yaml` — cross-capability object ontology (`provenance.reviewed: false`) +- `product/maps//object-map.yaml` — per UI-facing capability, **`status: draft`** + +Use the `capabilities[]` ids for the `` path segment. In v1 / +single-journey products (no `capabilities[]`), use a single default capability = +the project slug (`product:` / `project:` in the YAML) and set every UI story's +`coverage` entry under that one map. + +**These are drafts only — do not mark them approved.** Object boundaries and IA +are high-impact design decisions; `/aep-model` presents the draft for a short human +review gate and flips `status: approved`. Dispatch/launch refuse UI-facing stories +without an approved Object Map. + +**Re-runs invalidate approval.** If a later `/aep-map` run re-decomposes stories or +activities under a capability whose object-map is already `approved`, flip that +map's `status: stale` (and `provenance.reviewed: false` on the shared +`object-model.yaml` if its objects changed). The dispatch/launch gates treat `stale` +like `draft` — they abort until `/aep-model` re-approves the delta. + +> If a project is pure-backend/CLI (no UI-facing capability), skip this step. + ### Feedback Loop Decomposition agents may discover module boundaries are wrong. They submit amendment proposals to the System Map. When amendments accumulate to 3+ items or any single amendment affects an interface contract, trigger an **Architecture Review** with the user before continuing. @@ -297,10 +340,16 @@ Decomposition is complete. If no project exists yet: /aep-scaffold ``` -If the project already exists, start executing stories: +If the project has UI-facing capabilities, approve the Object Map drafts before dispatching UI stories: + +``` +/aep-model +``` + +`/aep-model` presents the draft Object Map for a short human review gate and flips it to `approved`. Then start executing stories: ``` /aep-dispatch ``` -`/aep-dispatch` reads the story graph from `product-context.yaml` and begins moving stories through the state machine (`pending → ready → in_progress → ...`), routing each through `/aep-design → /aep-launch → /aep-build → /aep-wrap`. +`/aep-dispatch` reads the story graph from `product-context.yaml` and begins moving stories through the state machine (`pending → ready → in_progress → ...`), routing each through `/aep-design → /aep-launch → /aep-build → /aep-wrap`. For UI-facing stories it injects the approved Object Map slice and refuses to dispatch if no approved Object Map exists. diff --git a/skills/product-context/map/templates/object-map-schema.yaml b/skills/product-context/map/templates/object-map-schema.yaml new file mode 100644 index 0000000..e87ea50 --- /dev/null +++ b/skills/product-context/map/templates/object-map-schema.yaml @@ -0,0 +1,103 @@ +# Object Map Schema — product/maps//object-map.yaml +# +# The capability-scoped ORCA / IA projection (OOUX Rounds O→R→C→A plus Round 4 +# representation hints). One file per UI-facing capability. This is the +# noun-first bridge between the verb-first story map and the actual UI: it says +# WHICH objects appear, WHAT their fields are, HOW they nest, and WHAT actions +# (CTAs) hang off each — BEFORE any screen is designed. +# +# Lifecycle: /aep-map writes a `status: draft`; /aep-model presents it, asks the +# few high-leverage questions (object boundaries, primary anchor, task-flow +# exceptions), and on approval flips `status: approved`. /aep-dispatch injects +# ONLY the slice a story touches; /aep-launch refuses a UI-facing story whose +# capability has no `approved` object-map. +# +# This file is STRUCTURAL, not visual. Colors/typography/spacing stay in +# calibration/visual-design.yaml; journey/page/transition stay in +# calibration/ux-flow.yaml. The object-map governs object structure + CTA grammar. +# +# See model/references/orca-process.md for derivation rules. + +schema: object-map/v1 +capability: # product/index.yaml capabilities[].id; in v1/single-journey use the default capability = project slug +# Lifecycle: /aep-map writes `draft`; /aep-model flips to `approved`; /aep-map flips an +# `approved` map back to `stale` when it re-decomposes stories/activities under this +# capability. Dispatch/launch gates treat `draft` AND `stale` as "not ready" (abort → /aep-model). +status: draft # draft | approved | stale +generated_by: aep-map # aep-map (draft) | aep-model (refined/approved) +generated_at: +approved_at: null +approved_by: null # "human" — set at the /aep-model review gate +object_model_ref: product/object-model.yaml # the shared ontology this projects from + +# Canonical screen-id grammar (used wherever a screen is referenced, incl. coverage): +# : where ∈ collection | detail | create | edit +# e.g. "order:collection", "order:detail". screens[] below declares which views exist +# per object; coverage[].screens references them by this composite id. + +# ─── ROUND O — objects in play for THIS capability ────────────── +# Subset of product/object-model.yaml. Primary objects get home screens; support +# objects only appear nested inside others. +primary_objects: [] # object ids the user navigates TO (each gets a collection + detail) +supporting_objects: [] # object ids shown only inside another object's views + +# ─── ROUND R — Nested Object Matrix (capability-local) ────────── +relationships: + - from: + to: + cardinality: one_to_many # one_to_one | one_to_many | many_to_many + nested: true # embed `to` inside `from`'s detail view + nav: true # this link is a navigation path the user can follow + +# ─── ROUND C — CTA Matrix (object × role → actions) ───────────── +# Verbs are mined from the stories/activities for this capability and hung onto +# the object they act on. role references product/index.yaml personas[].id. +ctas: + - object: + role: + actions: + - verb: "create" # the action (imperative) + from_story: # provenance: the story whose verb this is (or activity:) + placement: collection # collection | detail | inline | global + priority: primary # primary | secondary + +# ─── ROUND A — attribute priority per object ──────────────────── +# What shows where. core → card + detail; secondary → detail only; +# metadata → sort/filter keys (not necessarily rendered). +attributes: + - object: + core: [] # always visible + secondary: [] # detail only + metadata: [] # sort/filter keys + +# ─── ROUND 4 — representation hints (structural IA only) ───────── +# NOT visual design. Just which structural views each object needs. +screens: + - object: + views: ["collection", "detail"] # collection (list/grid) | detail | create | edit + card_fields: [] # subset of attributes.core shown on the card + empty_state: "What to show when there are none" + +navigation: + anchor_object: # the capability's home object (what the user sees first) + entry_points: [] # object ids reachable from primary nav + +# ─── OBJECT-FIRST DEFAULT + TASK-ORIENTED ESCAPE HATCH ────────── +# Every flow defaults to object_first (noun→verb: pick an object, then act). +# List a flow here ONLY to deviate to task_oriented (a deliberate wizard), with a +# reason grounded in the decision framework (single goal / novice / linear / low +# off-path tolerance — see orca-process.md and docs/research/ooux-object-modeling.md §6). +interaction_modes: + - flow: # e.g., "onboarding", "checkout" + mode: task_oriented # object_first (default, no need to list) | task_oriented + reason: "Why a wizard wins here." + +# ─── STORY COVERAGE INDEX ─────────────────────────────────────── +# Which stories realize which objects/screens. /aep-dispatch reads this to inject +# the minimal slice into a story's context package. +coverage: + - story: + objects: [] + screens: [":collection"] + +open_questions: [] # unresolved IA/object decisions surfaced to the human diff --git a/skills/product-context/map/templates/object-model-schema.yaml b/skills/product-context/map/templates/object-model-schema.yaml new file mode 100644 index 0000000..d4b7de6 --- /dev/null +++ b/skills/product-context/map/templates/object-model-schema.yaml @@ -0,0 +1,82 @@ +# Object Model Schema — product/object-model.yaml +# +# The cross-capability, USER-FACING object ontology (OOUX Rounds O + R). +# Created and updated by /aep-model. This is a STABLE product-design file — it +# lives under product/ alongside product/index.yaml, NOT inside the operational +# product-context.yaml. product-context.yaml carries only a thin reference in +# calibration.history (dimension: object-model). +# +# These are MENTAL-MODEL objects — the things the user perceives and acts on — +# NOT backend data entities. They may map onto architecture.domain_model entities +# (cross-linked via `backs_onto`), but the two are different lenses: domain_model +# is how the system stores data; object-model is how the user thinks. +# +# Authoring: /aep-map produces a first draft; /aep-model presents it, asks the +# few high-leverage questions, and the human approves. Do not hand-author the +# whole thing — it is mined from existing AEP artifacts (activities, stories, +# domain_model) and then human-reviewed. +# +# See model/references/orca-process.md for how each field is derived. + +schema: object-model/v1 +project: +updated_at: + +# ─── OBJECTS (ORCA Round O — Noun Foraging) ────────────────────── +# The shared language of user-facing objects across the whole product. +# Foraged from product.activities, story descriptions, problem/persona text, +# and architecture.domain_model. Keep names consistent with docs/glossary.md +# (ubiquitous language). +objects: + - id: # e.g., "order", "report", "avatar" + name: "Human-readable" # e.g., "Order" + aliases: [] # other names users or the team call this thing + description: "One sentence — what this object is in the user's world" + backs_onto: [] # architecture.domain_model entity names (0..n; may be empty for pure-UI objects) + appears_in: [] # capability ids (from product/index.yaml capabilities[]) that use this object + source_evidence: # noun-foraging provenance — where this object was found + - "activity:" + - "story:" + - "domain_model:" + confidence: high # high | medium | low — extraction confidence (low → ask the human) + naming_decision: null # why this name won (fill when there was a real choice) + open_questions: [] # unresolved questions about this object's boundary + +# ─── RELATIONSHIPS (ORCA Round R — cross-capability spine) ─────── +# The shared backbone of the Nested Object Matrix. Capability-LOCAL relationships +# live in product/maps//object-map.yaml; only put cross-capability +# links here. +relationships: + - from: + to: + cardinality: one_to_many # one_to_one | one_to_many | many_to_many + label: "owns" # how the user understands the link (verb phrase) + nested: true # does `from` show `to` inline (drives detail-view composition)? + +# ─── SHARED ATTRIBUTES ─────────────────────────────────────────── +# Attributes an object carries everywhere. Capability-specific attributes belong +# in the capability object-map. kind: core (always shown) | secondary (detail +# only) | metadata (sort/filter keys). +shared_attributes: + - object: + attributes: + - name: + kind: core # core | secondary | metadata + type: string # display-type hint (string | number | date | enum | image | ref) + notes: null + +# ─── NAMING DECISIONS (ubiquitous language log) ────────────────── +# Keep the user-facing vocabulary stable. Mirror confirmed names into +# docs/glossary.md when they become product-wide terms. +naming_decisions: + - object: + chosen: "Order" + rejected: ["Purchase", "Transaction"] + reason: "Users say 'my orders', not 'my transactions'." + +# ─── PROVENANCE ────────────────────────────────────────────────── +provenance: + generated_by: aep-map # aep-map (first draft) | aep-model (refined at review) + rounds_completed: ["O", "R"] # which ORCA rounds this file reflects + reviewed: false # set true after the /aep-model human review gate + reviewed_at: null diff --git a/skills/product-context/map/templates/product-context-schema.yaml b/skills/product-context/map/templates/product-context-schema.yaml index 12d6891..c34be55 100644 --- a/skills/product-context/map/templates/product-context-schema.yaml +++ b/skills/product-context/map/templates/product-context-schema.yaml @@ -7,6 +7,7 @@ # Sections are populated incrementally: # /aep-envision → opportunity, product (incl. quality_dimensions), calibration.plan # /aep-map → architecture, stories, topology, layer_gates, cost +# /aep-model → product/object-model.yaml + product/maps//object-map.yaml (standalone); calibration.history (dimension: object-model); stories[].object_model_refs # /aep-calibrate → calibration.history, inline updates to architecture/product sections # /aep-dispatch → stories[].status, stories[].openspec_change (per story) # /aep-build → .dev-workflow/signals/status.json (story_status, pr_url, cost_usd) @@ -157,8 +158,11 @@ product: # Dimensions where human judgment is needed that agents cannot provide. # Declared during /aep-envision, checked by /aep-reflect, executed by /aep-calibrate. # Not every dimension needs calibration — only those where "correct but not right" is likely. + # `object-model` is a STRUCTURAL gate (auto-drafted by /aep-model, human-approved), + # not a taste calibration — declare it for UI-facing products so UI stories get a + # noun-first Object Map before build. See skills/product-context/model. quality_dimensions: - - dimension: visual-design # visual-design | ux-flow | api-surface | data-model | scope-direction | copy-tone | performance-quality + - dimension: visual-design # visual-design | ux-flow | object-model | api-surface | data-model | scope-direction | copy-tone | performance-quality criticality: high # high | medium | low first_calibration_layer: 0.5 rationale: "Why this dimension needs human calibration" @@ -174,6 +178,8 @@ calibration: - layer: 0.5 dimensions: ["visual-design"] # from product.quality_dimensions trigger: "Condition that activates this calibration" + # /aep-calibrate appends taste-dimension decisions here; /aep-model appends + # object-model approvals (artifact_path → product/maps//object-map.yaml). history: - dimension: visual-design calibrated_at: @@ -192,6 +198,7 @@ architecture: technical_spec: null # optional path to standalone technical specification document (e.g., "docs/technical-spec.md") modules: - name: + kind: backend # ui | backend | shared — `ui` modules render user-facing surfaces (used by /aep-model + the UI-facing story trigger) responsibility: "What this module does" does_not: "What this module does NOT do (defines the boundary)" owns: @@ -295,7 +302,9 @@ stories: layer: 0 module: activity: null # user activity from product.activities (null for infrastructure stories) + capability: null # capability id (product/index.yaml capabilities[].id); /aep-map sets it. Null in v1/single-journey → resolves to the default capability (project slug). Used to locate product/maps//object-map.yaml. calibration_type: null # visual-design | ux-flow | api-surface | data-model | scope-direction | copy-tone | performance-quality (null for non-calibration stories) + object_model_refs: [] # /aep-model: object-map slice(s) this UI story realizes, as "#" where ∈ object-map primary_objects/supporting_objects, e.g. ["product/maps/dashboard/object-map.yaml#order"] (empty for non-UI stories) slice: 1 # execution slice within layer (parallel batch) status: pending priority: critical # critical | high | medium | low diff --git a/skills/product-context/model/SKILL.md b/skills/product-context/model/SKILL.md new file mode 100644 index 0000000..5a5092c --- /dev/null +++ b/skills/product-context/model/SKILL.md @@ -0,0 +1,230 @@ +--- +name: aep-model +description: Object-first UI structure modeling (OOUX/ORCA) for UI-facing products. Use after /aep-map and before dispatching UI-facing stories, or when the user says "object model", "object map", "objectify", "OOUX", "ORCA", "noun-first IA", "what objects/screens". Mines a draft Object Map (objects → relationships → CTAs → attributes → screens) from the story map, presents it for a short human review gate, and on approval writes the noun-first blueprint that build agents follow. Bridges the verb-first story map to the UI so agents stop inventing task-wizard screens per story. +--- + +# Model + +Turn the verb-first story map into a noun-first **Object Map** before UI gets +built. AEP's spine (`/aep-envision` → `/aep-map`) plans what the user _does_; +this skill plans the _objects_ the user acts on — which objects appear, what their +fields are, how they nest, and what actions hang off each. Without it, build +agents invent screen structure per story, which drifts into disjointed +task-wizard UIs. With it, they inherit one consistent object-oriented blueprint. + +> **The one rule:** a story-map slice cuts _scope_, not _interface type_. Never +> translate the backbone one-step-one-screen into a wizard. MVP slice ≠ wizard. + +**Where this fits:** + +``` +/aep-envision → /aep-map → /aep-model → [ /aep-calibrate ] → /aep-dispatch → /aep-launch → /aep-build → /aep-wrap → /aep-reflect + ▲ you are here (UI-facing products) +``` + +**Session:** Main, interactive with user (object boundaries + IA need human review) +**Input:** Product definition (`product/index.yaml` split mode, `product-context.yaml` v1) + `stories`, `architecture.domain_model` from `product-context.yaml` +**Output:** `product/object-model.yaml` (cross-capability ontology) + one `product/maps//object-map.yaml` per UI-facing capability (`status: approved`); thin `calibration.history` entry + `changelog` in `product-context.yaml` + +**Schemas:** `templates/object-model-schema.yaml`, `templates/object-map-schema.yaml`. +**Process detail:** `references/orca-process.md` (ORCA round-by-round derivation + the object-first/task-oriented decision framework). + +--- + +## When this skill applies + +Run `/aep-model` for **UI-facing** products/capabilities only. A capability is +UI-facing when it declares the `object-model` quality dimension (set by +`/aep-envision`), or declares `visual-design`/`ux-flow`, or has user-facing stories +(non-null `activity` whose module is `kind: ui`). Pure-backend/CLI products skip it +— there are no user-perceived objects to model. + +If nothing is UI-facing, say so and route the user straight to `/aep-dispatch`. + +--- + +## Before Starting + +**File Resolution:** + +```bash +ls product/index.yaml 2>/dev/null && echo "SPLIT MODE" || echo "V1 MODE" +cat product-context.yaml +``` + +- **Split mode** (`product/index.yaml` exists): read product definition, + `personas`, `capabilities`, `activities`, `quality_dimensions` from + `product/index.yaml`; read `stories`, `architecture.domain_model` from + `product-context.yaml`. Write `product/object-model.yaml` and + `product/maps//object-map.yaml`. +- **V1 mode**: read everything from `product-context.yaml`. Still write the + standalone artifacts under `product/` (create the directory) — the object model + is a stable design file, never inlined into operational YAML. + +If `stories` is empty, run `/aep-map` first. If no product definition exists, run +`/aep-envision` first. + +**Mode detection:** + +- **Establishment** — no `product/object-model.yaml` yet → full ORCA over all + UI-facing capabilities. +- **Extension** — it exists → focused pass over only NEW objects/capabilities not + yet covered (e.g., a later layer introduced new activities). Do not redo + approved maps; extend them and re-gate only the delta. + +--- + +## Step 1: Generate (or refine) the Draft (ORCA, automated) + +**If `/aep-map` already wrote draft artifacts** (`product/object-model.yaml` and +`product/maps//object-map.yaml` with `status: draft`), **read and refine them** +— do not regenerate from scratch. Preserve their `provenance`/`source_evidence`, +fill gaps, and fix obvious errors. Generate fresh only when no draft exists. (Set +`provenance.generated_by`/`status` to reflect reality — `aep-map` for an untouched +draft, refined in place here.) + +Run the four ORCA rounds per `references/orca-process.md`. This step is +agent-driven — mine, don't ask yet. + +1. **Round O — Objects (Noun Foraging):** forage nouns from `product.activities`, + `stories[].description`, `product.problem`, `personas`, and + `architecture.domain_model`. Promote user-perceived things to objects; demote + implementation nouns. Cross-link `backs_onto` to domain entities. Record + `source_evidence` + `confidence`. +2. **Round R — Relationships (Nested Object Matrix):** for each object pair, set + cardinality + whether nested/navigable. Cross-capability links → object-model; + capability-local → object-map. +3. **Round C — CTAs (object × role matrix):** hang every story/activity verb onto + the object it acts on, tagged by persona, with placement + priority + the + `from_story` it came from. +4. **Round A — Attributes:** rank each object's fields core / secondary / + metadata. +5. **Round 4 — Representation hints:** project primary objects into structural + `collection` + `detail` views; pick each capability's `anchor_object`. IA only, + no visual design. + +Write: + +- `product/object-model.yaml` — the cross-capability ontology + (`provenance.reviewed: false`). +- `product/maps//object-map.yaml` per UI-facing capability with + **`status: draft`**, including the `coverage` index (story → objects/screens; screen + ids use the canonical `:` grammar). Use `capabilities[]` ids for + ``; in v1/single-journey (no `capabilities[]`) use one default + capability = the project slug. + +Default every flow to `object_first`. Only add an `interaction_modes` entry to +mark a flow `task_oriented`, with a reason grounded in the decision framework. + +--- + +## Step 2: Review Gate (the human decides) + +Object boundaries and IA are the highest-leverage design decisions here — same +error-cost logic as the `/aep-map` System Map gate. Present the draft compactly +(objects, the NOM, primary screens, any task-flow exceptions) and ask a SHORT set +of high-leverage questions, one at a time: + +1. **Object boundaries** — are these the objects the user actually thinks in? Any + wrong names, bad merges, or missing splits? (Surface every low-`confidence` + object explicitly.) +2. **Primary anchor** — per capability, which object should the user see / choose + first? +3. **Task-flow exceptions** — which flows should be explicit wizards (onboarding / + checkout / one-shot) instead of object-first? Capture the reason. + +Apply the answers to the draft. Keep the questions few — this is a gate, not a +redesign workshop. Heavy taste decisions (look, voice) are NOT this skill's job; +they stay in `/aep-calibrate` (visual-design, copy-tone) and journey/page/ +transition stays in ux-flow. + +--- + +## Step 3: Approve & Write + +On approval: + +1. Set each reviewed `product/maps//object-map.yaml` → + `status: approved`, `approved_by: human`, `approved_at: `. +2. Set `product/object-model.yaml` `provenance.reviewed: true` (+ `reviewed_at`). +3. Back-annotate stories: add `object_model_refs` to UI stories the map covers, + e.g. `object_model_refs: ["product/maps/dashboard/object-map.yaml#order"]`. + Keep it a thin reference — the map body never goes into `product-context.yaml`. +4. Append a thin record to `calibration.history` in `product-context.yaml`: + + ```yaml + - dimension: object-model + calibrated_at: "" + calibrated_from_layer: # the active layer whose UI stories this approval unblocks + mode: establishment # or extension + artifact_path: "product/maps//object-map.yaml" + summary: "Approved object-first IA for — N primary objects, M task-flow exceptions" + ``` + +5. Append a `changelog` entry (`type: map_update`, author: human, + `sections_changed: [calibration, stories]`). + +--- + +## Step 4: Validate YAML & Commit + +```bash +# Validate every YAML touched +python3 -c "import yaml,glob; [yaml.safe_load(open(f)) for f in ['product/object-model.yaml','product-context.yaml']+glob.glob('product/maps/*/object-map.yaml')]; print('YAML OK')" +``` + +If it fails, fix before committing (see `templates/product-context-schema.yaml` +guidance: quote list items with colons, flatten nested sub-lists, escape quotes). + +```bash +# Resolve $BASE (integration branch) — see git-ref "Integration Branch" (override → develop → main) +BASE=$(git config --get aep.integration-branch 2>/dev/null || true) +[ -z "$BASE" ] && { git show-ref --verify --quiet refs/heads/develop \ + || git show-ref --verify --quiet refs/remotes/origin/develop; } && BASE=develop +BASE=${BASE:-main} + +git pull --ff-only origin "$BASE" +git add product/ product-context.yaml +git commit -m "feat: object model — approved object-first IA for " +git push origin "$BASE" +``` + +--- + +## How downstream consumes the Object Map + +- **`/aep-dispatch`** injects only the **slice** for the objects a story touches + (from `coverage`) into the story's context package — not the whole model — and + **refuses** to dispatch a UI-facing story whose capability lacks an `approved` + object-map (run `/aep-model` first), mirroring the calibration gate. +- **`/aep-launch`** aborts a UI-facing story with no approved object-map. +- **`/aep-build`** treats the injected slice as binding: objects, their core + attributes, CTA placement, and screen structure come from the map; + visual-design/copy-tone/ux-flow calibration still own look, voice, and journey. +- **`/aep-validate`** Mode A runs the completeness checks (coverage, anchors, + task-flow reasons). + +--- + +## Anti-Patterns + +- **Backbone → wizard 1:1.** The deepest trap. A slice is scope, not screen shape. +- **Designing visuals here.** No palette/typography/spacing — that's + `/aep-calibrate visual-design`. This skill is structure only. +- **Modeling backend entities as objects.** If the user never perceives it, it's + `architecture`, not the object model. +- **Skipping the gate.** Auto-approving object boundaries reproduces the guesswork + the skill exists to prevent. Always run Step 2. +- **Fat operational YAML.** Never inline object-map bodies into + `product-context.yaml`; keep them under `product/` with thin references. + +--- + +## Next Step + +Object model approved. Heavy taste dimensions next (if planned), then dispatch: + +``` +/aep-calibrate visual-design # look & feel (optional, if a .5 layer plans it) +/aep-dispatch # inject object-map slices and start building +``` diff --git a/skills/product-context/model/references/orca-process.md b/skills/product-context/model/references/orca-process.md new file mode 100644 index 0000000..3697cf5 --- /dev/null +++ b/skills/product-context/model/references/orca-process.md @@ -0,0 +1,165 @@ +# ORCA Process — Deriving the Object Map from AEP Artifacts + +How `/aep-model` runs OOUX's ORCA process over what AEP already produced. This is +the noun-first bridge between the verb-first story map and the UI. Full background +and citations: `docs/research/ooux-object-modeling.md`. + +> **The one rule that matters most:** never translate the story-map backbone +> one-step-one-screen into a wizard. A slice cuts _scope_ ("what to learn / ship +> first"), not _interface type_. MVP slice ≠ wizard. The Object Map exists to stop +> exactly that failure. + +ORCA = **O**bjects → **R**elationships → **C**alls-to-action → **A**ttributes. +Sophia Prater's full process is four iterative rounds (Discovery → Requirements → +Prioritization → Representation); `/aep-model` runs a compressed, +artifact-grounded version because AEP already did the upstream research. + +--- + +## Inputs (what to read before mining) + +| ORCA needs | AEP source | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- | +| Candidate nouns | `product.activities[]`, `stories[].description.what_changes` (string or `{what_changes, why}`), `product.problem`, `personas[]` | +| Entity backing + relationships | `architecture.domain_model[]`, `architecture.modules[].key_concepts` | +| Verbs (→ CTAs) | activity names, `stories[].description`, acceptance criteria | +| Roles | `product/index.yaml` `personas[]` | +| Capability scoping | `product/index.yaml` `capabilities[]`, `product/maps//frame.yaml` | + +Read story `description` defensively — it is a string in some projects and a +`{what_changes, why}` object in others. Handle both. + +**Capability scoping fallbacks:** `frame.yaml` only exists for multi-journey +products (`/aep-envision` skips it for single-journey). If it is absent, scope the +capability from `product.activities` + the stories whose `capability` (or, in v1, +all UI stories) belong to it. In v1 / single-journey products there is no +`capabilities[]` — use one default capability whose id is the project slug. + +--- + +## Round O — Objects (Noun Foraging) + +1. **Forage nouns** from the inputs above. Collect every concrete thing the user + refers to or acts on. +2. **Promote to objects** the nouns that are (a) in the user's mental model and + (b) something the user views or acts on. Demote pure implementation nouns + (token, cache, queue) — they belong to `architecture`, not the object model. +3. **Disambiguate object vs. backend entity.** A `domain_model` entity that the + user never perceives is NOT an object. An object the user perceives that has no + single backing table (e.g., a composed "Dashboard") IS an object. Cross-link + with `backs_onto` where they coincide. +4. **Merge synonyms, split overloaded nouns.** Record the call in + `naming_decisions`. Keep names consistent with `docs/glossary.md`. +5. Record `source_evidence` and `confidence` for each object. Low-confidence + objects become review-gate questions. **Confidence rubric:** `high` = named + directly in an activity or as a `domain_model` entity; `medium` = inferred from + story text / appears once; `low` = guessed, a synonym merge, or a composed object + with no single backing entity. Surface every `low` at the gate. + +Output → `product/object-model.yaml` `objects[]`. + +## Round R — Relationships (Nested Object Matrix) + +For each ordered pair of objects, ask: does one contain, reference, or list the +other? Record `cardinality` (one_to_one / one_to_many / many_to_many) and whether +the link is `nested` (shown inline in the parent's detail view) and/or a `nav` +path. Seed from `domain_model` foreign-key-style relationships, then add +user-perceived links the data model doesn't capture. + +- Cross-capability links → `product/object-model.yaml` `relationships[]`. +- Capability-local links → `product/maps//object-map.yaml` `relationships[]`. + +Relationships are what pave navigation paths — get them right and the IA falls out. + +## Round C — Calls-to-Action (CTA Matrix) + +Build an **object × role** matrix. Every verb in the capability's stories and +activities is an action the user does _to some object_ — hang it there. + +- Map each story/activity verb to its object and the acting `role` (persona). +- Set `placement` (collection / detail / inline / global) and `priority`. + **Priority rule:** a CTA is `primary` when its story is `priority: critical|high` + or `business_value >= 7`, or it is the object's defining action (create/the verb in + the activity name); otherwise `secondary`. +- Keep `from_story` provenance so dispatch can trace a CTA back to the story that + needs it, and so coverage can be checked. + +A verb with no object is a smell: either an object is missing (go back to Round O) +or the action is really a task-flow (see the escape hatch below). + +Output → `product/maps//object-map.yaml` `ctas[]`. + +## Round A — Attributes + +For each object, list its content elements + metadata and rank them: + +- **core** — identity-defining; shown on the card and the detail view. +- **secondary** — detail view only. +- **metadata** — sort/filter keys (timestamps, counts, status). + +Mine from `domain_model` fields, acceptance criteria, and existing UI. **Tier +inference when there's no existing UI** (new products): name/title/label and the +object's primary identifier → `core`; other `required: true` domain fields → +`core`/`secondary`; timestamps, counts, status/enum flags → `metadata`; everything +else → `secondary`. Shared attributes → object-model; capability-specific → +object-map. + +## Round 4 — Representation hints (structural only) + +Project the above into structural views — for each primary object a `collection` +(list/grid) and `detail`, composed via the Nested Object Matrix; `create`/`edit` +where CTAs require them. Pick the capability's `anchor_object` (what the user sees +first). **This is IA, not visual design** — no colors, type, or spacing. Those +stay in `calibration/visual-design.yaml`; journey/page/transition stay in +`calibration/ux-flow.yaml`. + +--- + +## Object-first default vs. task-oriented escape hatch + +Default every flow to **object_first** (noun→verb: the user picks an object, then +acts). Deviate to **task_oriented** (a deliberate wizard) ONLY when the decision +framework says so, and record it in `interaction_modes` with a reason. + +| Lean task-oriented (wizard) | Lean object-first | +| --------------------------------------- | ---------------------------------- | +| Goal undefined, user needs guiding | Goal clear, user wants to explore | +| Single object | Many objects / relationships | +| One linear flow | One object, many actions | +| Novice / onboarding | Repeat / power user | +| Low off-path tolerance | High off-path tolerance | +| e.g. onboarding, checkout, setup wizard | e.g. dashboard, library, admin/CRM | + +Staged design is legitimate: a capability can be task-oriented on first use and +object-first thereafter. Record the deviation per flow; everything unlisted is +object_first. + +--- + +## The review gate (why this is not fully automatic) + +Object boundaries and IA are high-impact design decisions — wrong boundaries cost +more than code bugs (same reason `/aep-map` gates the System Map). `/aep-model` +generates the _draft_, then asks the human a SHORT, high-leverage set: + +1. **Object boundaries** — are these the objects the user actually thinks in? + Any wrong names, bad merges, or missing splits? +2. **Primary anchor** — for this capability, which object should the user see / + choose first? +3. **Task-flow exceptions** — which flows should be explicit wizards + (onboarding / checkout / one-shot) rather than object-first? + +Approve → flip `status: approved` and set `provenance.reviewed: true`. Only +`approved` object-maps unblock UI-facing dispatch/launch. + +--- + +## Completeness checks (also enforced by /aep-validate Mode A) + +- Every UI-facing capability has an `object-map.yaml`. +- Every noun foraged from an activity maps to an object (or is justified as + implementation-only). +- Every UI story's verbs map to a CTA on some object (`coverage` is complete). +- Every primary object has a home screen (`collection` or `detail`). +- Every `task_oriented` flow has a written `reason`. +- Object names are consistent with `docs/glossary.md` and `domain_model`. diff --git a/skills/product-context/model/templates/.aep-generated b/skills/product-context/model/templates/.aep-generated new file mode 100644 index 0000000..146dc47 --- /dev/null +++ b/skills/product-context/model/templates/.aep-generated @@ -0,0 +1 @@ +Generated by scripts/build-skills.sh from skills/product-context/_shared/. Do not edit; edit _shared/ and rebuild. diff --git a/skills/product-context/model/templates/agent-topology.md b/skills/product-context/model/templates/agent-topology.md new file mode 100644 index 0000000..611536b --- /dev/null +++ b/skills/product-context/model/templates/agent-topology.md @@ -0,0 +1,219 @@ +# Agent Topology Template + +Defines every agent role in the execution pipeline, how they communicate, and how the orchestrator routes work to them. This is an architectural document — changes here affect the behavior of the entire execution plane. + +The core rule: **agents communicate through structured artifacts, not free text.** Every input and output is schema-defined. Every handoff has validation. No exceptions. + +--- + +## Agent Roles + +### Role: [Role Name] + +**Purpose**: [One sentence — what this agent does in the pipeline.] + +**Responsibility boundary**: + +- Does: [Specific responsibilities] +- Does not: [Explicit exclusions — prevents role creep] + +**Input contract**: + +``` +[Schema definition of the work object this agent receives. Use TypeScript types, JSON Schema, or equivalent. Be explicit about required fields.] + +Example: +{ + story_spec: StorySpec, // The full story specification + context_slice: { + context_document: string, // Relevant sections only + system_map_module: ModuleDef, // This story's module definition + adjacent_interfaces: InterfaceContract[], + dependency_artifacts: Artifact[] // Public interfaces from completed dependencies + } +} +``` + +**Output contract**: + +``` +[Schema definition of what this agent produces.] + +Example: +{ + implementation: { + branch_name: string, + files_changed: FileDiff[], + pr_url: string + }, + verification: { + unit_tests: TestResult[], + contract_tests: TestResult[], + all_passing: boolean + }, + status_report: { + story_id: string, + outcome: "success" | "failure", + error_summary?: string, + what_was_not_tried?: string[] // Critical for fresh-agent retries + } +} +``` + +**Context window composition**: +[Exactly what goes into this agent's context and in what order. Less is more — irrelevant context degrades performance.] + +1. Story Spec (full) +2. Context Document (pruned to: Purpose, Technical Constraints, relevant Layer in MVP Contract) +3. System Map (this module + adjacent interface contracts only) +4. Dependency artifacts (public API surface only, not internals) + +**Cost budget**: + +- Expected tokens: [range, e.g., 10k–50k input, 5k–20k output] +- Expected duration: [range, e.g., 2–10 minutes] +- Alert threshold: [e.g., > 100k total tokens or > 20 minutes] + +[Repeat for each agent role] + +--- + +## Standard Roles + +Most projects will need at least these roles. Add or remove based on project complexity. + +### `implementer` + +Takes a story spec and produces code + tests + PR. The workhorse of the execution plane. + +### `contract-verifier` + +Takes a PR and runs interface contract tests against the System Map. Catches integration incompatibilities before merge. + +### `integration-tester` + +Runs end-to-end tests for layer gates. Operates on the combined codebase, not individual stories. + +### `failure-analyst` + +Takes a failed story's trace and produces a structured failure log with root cause hypothesis and unexplored alternatives. Feeds into fresh-agent retries. + +--- + +## Handoff Contracts + +### [Source Role] → [Target Role] + +**Trigger event**: [What causes this handoff — e.g., "implementer completes PR submission"] + +**Payload schema**: + +``` +[Exact structure passed from source to target] +``` + +**Pre-handoff validation**: +[Checks that run before the target agent starts. If validation fails, the handoff is rejected and the source agent is notified.] + +- [ ] Payload matches schema +- [ ] All required fields present and non-empty +- [ ] Referenced artifacts (files, PRs) actually exist +- [ ] [Domain-specific checks] + +**Failure handling**: [What happens if validation fails — retry source? escalate?] + +--- + +## Routing Rules + +### Dispatch Policy + +**Queue model**: [FIFO within execution slice / priority-based / other] + +**Assignment**: When a story transitions to `ready` in the work graph, the orchestrator: + +1. Checks conflict detection (see below) +2. Checks concurrency limit +3. Assembles context package per the implementer role's context window composition +4. Dispatches to the next available agent instance + +### Concurrency + +- **Maximum parallel agents**: [Number — start with 5–10, increase as stability is proven] +- **Per-module limit**: [Optional — prevent one module from consuming all agent capacity] + +### Conflict Detection + +Stories that modify overlapping files must not run in parallel. The orchestrator checks each ready story's "Files Likely Affected" against in-progress stories. Conflicts are serialized — the later story waits until the earlier one completes. + +### Retry Routing + +``` +Attempt 1: Same agent, error context appended to input +Attempt 2: Same agent, second retry +Attempt 3: failure-analyst produces structured log + → fresh implementer agent receives story spec + failure log +Attempt 4: Human escalation — story marked 'failed', user notified +``` + +Between attempt 2 and 3, the failure-analyst role intervenes to extract useful signal from the failures before handing off to a fresh agent. + +--- + +## Cost Tracking Schema + +Every agent invocation produces a trace record appended to the project's cost log: + +``` +{ + story_id: string, + agent_role: string, + attempt_number: number, + start_time: ISO8601, + end_time: ISO8601, + tokens_input: number, + tokens_output: number, + cost_usd: number, + outcome: "success" | "failure" | "escalated", + error_class?: string // e.g., "test_failure", "timeout", "context_overflow" +} +``` + +### Cost Alerts + +- **Per-story alert**: Triggered when total cost across all attempts for a single story exceeds [threshold]. +- **Per-layer alert**: Triggered when cumulative layer cost exceeds [threshold]. +- **Anomaly alert**: Triggered when a story's cost is > 3x the median for its complexity class (S/M/L). + +--- + +## Topology Diagram + +[Optional but recommended. A simple flow diagram showing agent roles, handoff directions, and the orchestrator's position.] + +``` + ┌─────────────────┐ + │ Orchestrator │ + │ (Control Plane) │ + └────────┬────────┘ + │ dispatches + ┌────────▼────────┐ + │ Implementer │ + │ (Execution Plane)│ + └────────┬────────┘ + │ PR submitted + ┌────────▼────────┐ + │Contract Verifier │ + └────────┬────────┘ + │ pass/fail + ┌────────▼────────┐ + ┌─────│ Orchestrator │─────┐ + │ └─────────────────┘ │ + │ (all layer stories done) │ (failure) + ┌────────▼────────┐ ┌─────────▼────────┐ + │Integration Tester│ │ Failure Analyst │ + └────────┬────────┘ └─────────┬────────┘ + │ │ + Layer Gate Fresh Implementer + Decision or Escalation +``` diff --git a/skills/product-context/model/templates/context-document.md b/skills/product-context/model/templates/context-document.md new file mode 100644 index 0000000..c23b851 --- /dev/null +++ b/skills/product-context/model/templates/context-document.md @@ -0,0 +1,206 @@ +# Context Document Template + +This is the root context document for the project. Every downstream agent inherits this document. Precision here prevents confusion downstream — every vague statement multiplies into ambiguity across parallel agents. + +Quality standard: **every statement must be convertible into a verification condition.** If it cannot be tested, it is not precise enough. + +--- + +## Project Identity + +- **Project name**: [Working name] +- **Opportunity Brief**: [Link to the Phase 0 Opportunity Brief] +- **Created**: [Date] +- **Last updated**: [Date] +- **Version**: [Increment on each meaningful change] + +--- + +## Problem Statement + +[Who has this problem? What is the problem, specifically? How do they deal with it today? Why is the current approach inadequate? This should be sharp enough that a stranger could read it and understand the pain without further explanation.] + +--- + +## Persona / JTBD + +### Primary Persona + +[Describe the primary user concretely. Not "developers" but "solo developers building SaaS products who deploy to edge platforms and want to integrate AI agents without managing infrastructure." Include relevant context: technical skill level, tools they already use, constraints they operate under.] + +### Job To Be Done + +[What job is the user hiring this product to do? Frame it as: "When [situation], I want to [motivation], so I can [expected outcome]."] + +--- + +## MVP Boundary + +### In Scope + +[Concrete list of what this system does. Each item should be specific enough to verify.] + +### Explicitly Out of Scope + +[What this system does NOT do, even if users might expect it. This prevents agents from expanding scope.] + +### Deferred (Possible Future Scope) + +[Things that might be added later but are not part of the current effort. Agents should not build toward these unless they come for free.] + +> **Important boundary:** "In Scope" defines what the system does. "Goals" (below) defines how you know it's working. A feature can be in scope but not yet a goal for this layer. + +--- + +## Goals and Non-Goals + +### Goals + +[Behavior-observable statements. Each must be verifiable by observing the running system. Not aspirations — observable behaviors. + +Example: "Poll the issue tracker on a fixed cadence and dispatch work to coding agents" is a goal. "Be a good orchestrator" is not.] + +1. [Goal — observable behavior] +2. [Goal — observable behavior] + +### Non-Goals + +[Things a reasonable person might expect this product to do, but it will NOT. Each explains why — preventing scope creep and setting correct expectations for downstream agents. + +Example: "Rich web UI — this is a CLI-first tool. A web dashboard is a separate product."] + +1. [Non-goal — why excluded] +2. [Non-goal — why excluded] + +--- + +## Technical Constraints + +### Required Stack + +[Non-negotiable technology choices and why.] + +### Preferred Stack + +[Preferences that can be overridden with good reason. State the preference and the reasoning.] + +### Infrastructure + +[Where this runs, deployment targets, environment constraints.] + +### External Dependencies + +[Third-party services this project depends on. For each: what it provides, failure behavior, known limitations.] + +--- + +## Failure Model + +What goes wrong and what the system does about it. Agents building this system need to implement these recovery behaviors. Undocumented failure modes become undocumented bugs. + +### Failure Classes + +| Class | Examples | Detection | Recovery | Escalation | +| ------ | ------------------ | --------------------------- | ----------------------------- | ------------------------------ | +| [Name] | [What triggers it] | [How the system detects it] | [Automatic recovery behavior] | [When/how a human is notified] | + +### Degraded Operation + +[What the system can still do when a dependency is down. "Nothing" is a valid answer but must be stated.] + +--- + +## Security Model + +### Trust Boundaries + +[What is trusted (e.g., "the operator's machine") and what is not (e.g., "user-submitted content"). Draw the line explicitly.] + +### Authentication & Authorization + +[How users prove identity. How access is controlled. "N/A for MVP" is acceptable but must be stated.] + +### Secret Handling + +[Where secrets come from, how they're stored, how they're passed to components. Never in plaintext config files.] + +--- + +## User Activities (Story Map Backbone) + +The backbone of the user story map. Each activity is a discrete step in the user's journey, ordered left-to-right as a narrative. These are discovered BEFORE defining layers — the backbone comes first, release boundaries (layers) are drawn across it second. + +Read the activities as a sentence to verify narrative coherence: "The user [activity 1], then [activity 2], then [activity 3]..." + +| Order | Activity ID | Name | Description | Layer Introduced | +| ----- | ----------- | ---- | ----------- | ---------------- | +| 1 | | | | 0 | +| 2 | | | | 0 | + +Activities from Layer 0 form the core backbone. Later layers may introduce new activities that extend the backbone to the right. + +--- + +## Layered MVP Contract + +Each layer is a complete, testable, deployable increment. Layer 0 is the walking skeleton — a horizontal slice across ALL activities in the backbone. Each subsequent layer adds capabilities. Most MVPs need 2–4 layers. More than that suggests scope is too large. + +### Layer 0: Walking Skeleton + +**User can**: [End-to-end journey in concrete steps. "User does X → sees Y → gets Z." Every step observable and verifiable.] + +**Verification**: [The test scenario that proves Layer 0 works.] + +### Layer 1: [Name] + +**User can**: [Everything from Layer 0, plus new capabilities.] + +**Verification**: [Test scenario for new capabilities.] + +### Layer 2: [Name] + +**User can**: [Everything from Layer 1, plus new capabilities.] + +**Verification**: [Test scenario.] + +--- + +## Success Criteria + +### Functional + +[What must work for this to be a successful MVP? Specific, testable conditions.] + +### Non-Functional + +[Performance, reliability, security. Each with a measurable threshold.] + +--- + +## Open Questions + +Decisions explicitly deferred. Downstream agents will see these and know not to assume answers. + +| Question | Why Deferred | Default Assumption | Revisit Trigger | +| -------- | ------------ | ------------------ | --------------- | +| | | | | + +--- + +## Key Decisions Log + +Significant decisions made during Product Framing. Helps downstream agents understand not just what was decided, but why — enabling consistent decisions in ambiguous situations. + +| Decision | Reasoning | Alternatives Considered | +| -------- | --------- | ----------------------- | +| | | | + +--- + +## Stress Test Record + +Challenges raised during Phase 1 Stage 3, and how they were resolved. + +| Challenge | Source Angle | Resolution | +| --------- | --------------------------------------------------------- | --------------------------------------------------------------------- | +| | Product viability / Technical feasibility / Scope control | Refined document / Marked as open question / Dismissed with reasoning | diff --git a/skills/product-context/model/templates/object-map-schema.yaml b/skills/product-context/model/templates/object-map-schema.yaml new file mode 100644 index 0000000..e87ea50 --- /dev/null +++ b/skills/product-context/model/templates/object-map-schema.yaml @@ -0,0 +1,103 @@ +# Object Map Schema — product/maps//object-map.yaml +# +# The capability-scoped ORCA / IA projection (OOUX Rounds O→R→C→A plus Round 4 +# representation hints). One file per UI-facing capability. This is the +# noun-first bridge between the verb-first story map and the actual UI: it says +# WHICH objects appear, WHAT their fields are, HOW they nest, and WHAT actions +# (CTAs) hang off each — BEFORE any screen is designed. +# +# Lifecycle: /aep-map writes a `status: draft`; /aep-model presents it, asks the +# few high-leverage questions (object boundaries, primary anchor, task-flow +# exceptions), and on approval flips `status: approved`. /aep-dispatch injects +# ONLY the slice a story touches; /aep-launch refuses a UI-facing story whose +# capability has no `approved` object-map. +# +# This file is STRUCTURAL, not visual. Colors/typography/spacing stay in +# calibration/visual-design.yaml; journey/page/transition stay in +# calibration/ux-flow.yaml. The object-map governs object structure + CTA grammar. +# +# See model/references/orca-process.md for derivation rules. + +schema: object-map/v1 +capability: # product/index.yaml capabilities[].id; in v1/single-journey use the default capability = project slug +# Lifecycle: /aep-map writes `draft`; /aep-model flips to `approved`; /aep-map flips an +# `approved` map back to `stale` when it re-decomposes stories/activities under this +# capability. Dispatch/launch gates treat `draft` AND `stale` as "not ready" (abort → /aep-model). +status: draft # draft | approved | stale +generated_by: aep-map # aep-map (draft) | aep-model (refined/approved) +generated_at: +approved_at: null +approved_by: null # "human" — set at the /aep-model review gate +object_model_ref: product/object-model.yaml # the shared ontology this projects from + +# Canonical screen-id grammar (used wherever a screen is referenced, incl. coverage): +# : where ∈ collection | detail | create | edit +# e.g. "order:collection", "order:detail". screens[] below declares which views exist +# per object; coverage[].screens references them by this composite id. + +# ─── ROUND O — objects in play for THIS capability ────────────── +# Subset of product/object-model.yaml. Primary objects get home screens; support +# objects only appear nested inside others. +primary_objects: [] # object ids the user navigates TO (each gets a collection + detail) +supporting_objects: [] # object ids shown only inside another object's views + +# ─── ROUND R — Nested Object Matrix (capability-local) ────────── +relationships: + - from: + to: + cardinality: one_to_many # one_to_one | one_to_many | many_to_many + nested: true # embed `to` inside `from`'s detail view + nav: true # this link is a navigation path the user can follow + +# ─── ROUND C — CTA Matrix (object × role → actions) ───────────── +# Verbs are mined from the stories/activities for this capability and hung onto +# the object they act on. role references product/index.yaml personas[].id. +ctas: + - object: + role: + actions: + - verb: "create" # the action (imperative) + from_story: # provenance: the story whose verb this is (or activity:) + placement: collection # collection | detail | inline | global + priority: primary # primary | secondary + +# ─── ROUND A — attribute priority per object ──────────────────── +# What shows where. core → card + detail; secondary → detail only; +# metadata → sort/filter keys (not necessarily rendered). +attributes: + - object: + core: [] # always visible + secondary: [] # detail only + metadata: [] # sort/filter keys + +# ─── ROUND 4 — representation hints (structural IA only) ───────── +# NOT visual design. Just which structural views each object needs. +screens: + - object: + views: ["collection", "detail"] # collection (list/grid) | detail | create | edit + card_fields: [] # subset of attributes.core shown on the card + empty_state: "What to show when there are none" + +navigation: + anchor_object: # the capability's home object (what the user sees first) + entry_points: [] # object ids reachable from primary nav + +# ─── OBJECT-FIRST DEFAULT + TASK-ORIENTED ESCAPE HATCH ────────── +# Every flow defaults to object_first (noun→verb: pick an object, then act). +# List a flow here ONLY to deviate to task_oriented (a deliberate wizard), with a +# reason grounded in the decision framework (single goal / novice / linear / low +# off-path tolerance — see orca-process.md and docs/research/ooux-object-modeling.md §6). +interaction_modes: + - flow: # e.g., "onboarding", "checkout" + mode: task_oriented # object_first (default, no need to list) | task_oriented + reason: "Why a wizard wins here." + +# ─── STORY COVERAGE INDEX ─────────────────────────────────────── +# Which stories realize which objects/screens. /aep-dispatch reads this to inject +# the minimal slice into a story's context package. +coverage: + - story: + objects: [] + screens: [":collection"] + +open_questions: [] # unresolved IA/object decisions surfaced to the human diff --git a/skills/product-context/model/templates/object-model-schema.yaml b/skills/product-context/model/templates/object-model-schema.yaml new file mode 100644 index 0000000..d4b7de6 --- /dev/null +++ b/skills/product-context/model/templates/object-model-schema.yaml @@ -0,0 +1,82 @@ +# Object Model Schema — product/object-model.yaml +# +# The cross-capability, USER-FACING object ontology (OOUX Rounds O + R). +# Created and updated by /aep-model. This is a STABLE product-design file — it +# lives under product/ alongside product/index.yaml, NOT inside the operational +# product-context.yaml. product-context.yaml carries only a thin reference in +# calibration.history (dimension: object-model). +# +# These are MENTAL-MODEL objects — the things the user perceives and acts on — +# NOT backend data entities. They may map onto architecture.domain_model entities +# (cross-linked via `backs_onto`), but the two are different lenses: domain_model +# is how the system stores data; object-model is how the user thinks. +# +# Authoring: /aep-map produces a first draft; /aep-model presents it, asks the +# few high-leverage questions, and the human approves. Do not hand-author the +# whole thing — it is mined from existing AEP artifacts (activities, stories, +# domain_model) and then human-reviewed. +# +# See model/references/orca-process.md for how each field is derived. + +schema: object-model/v1 +project: +updated_at: + +# ─── OBJECTS (ORCA Round O — Noun Foraging) ────────────────────── +# The shared language of user-facing objects across the whole product. +# Foraged from product.activities, story descriptions, problem/persona text, +# and architecture.domain_model. Keep names consistent with docs/glossary.md +# (ubiquitous language). +objects: + - id: # e.g., "order", "report", "avatar" + name: "Human-readable" # e.g., "Order" + aliases: [] # other names users or the team call this thing + description: "One sentence — what this object is in the user's world" + backs_onto: [] # architecture.domain_model entity names (0..n; may be empty for pure-UI objects) + appears_in: [] # capability ids (from product/index.yaml capabilities[]) that use this object + source_evidence: # noun-foraging provenance — where this object was found + - "activity:" + - "story:" + - "domain_model:" + confidence: high # high | medium | low — extraction confidence (low → ask the human) + naming_decision: null # why this name won (fill when there was a real choice) + open_questions: [] # unresolved questions about this object's boundary + +# ─── RELATIONSHIPS (ORCA Round R — cross-capability spine) ─────── +# The shared backbone of the Nested Object Matrix. Capability-LOCAL relationships +# live in product/maps//object-map.yaml; only put cross-capability +# links here. +relationships: + - from: + to: + cardinality: one_to_many # one_to_one | one_to_many | many_to_many + label: "owns" # how the user understands the link (verb phrase) + nested: true # does `from` show `to` inline (drives detail-view composition)? + +# ─── SHARED ATTRIBUTES ─────────────────────────────────────────── +# Attributes an object carries everywhere. Capability-specific attributes belong +# in the capability object-map. kind: core (always shown) | secondary (detail +# only) | metadata (sort/filter keys). +shared_attributes: + - object: + attributes: + - name: + kind: core # core | secondary | metadata + type: string # display-type hint (string | number | date | enum | image | ref) + notes: null + +# ─── NAMING DECISIONS (ubiquitous language log) ────────────────── +# Keep the user-facing vocabulary stable. Mirror confirmed names into +# docs/glossary.md when they become product-wide terms. +naming_decisions: + - object: + chosen: "Order" + rejected: ["Purchase", "Transaction"] + reason: "Users say 'my orders', not 'my transactions'." + +# ─── PROVENANCE ────────────────────────────────────────────────── +provenance: + generated_by: aep-map # aep-map (first draft) | aep-model (refined at review) + rounds_completed: ["O", "R"] # which ORCA rounds this file reflects + reviewed: false # set true after the /aep-model human review gate + reviewed_at: null diff --git a/skills/product-context/model/templates/opportunity-brief.md b/skills/product-context/model/templates/opportunity-brief.md new file mode 100644 index 0000000..0d6eb9e --- /dev/null +++ b/skills/product-context/model/templates/opportunity-brief.md @@ -0,0 +1,52 @@ +# Opportunity Brief Template + +This document captures the core bet behind a product idea. It is deliberately short — one page maximum. Its purpose is to force clarity before committing resources to product design. If the opportunity cannot be stated clearly in this format, it is not ready for Phase 1. + +--- + +## The Bet + +**I believe** [target user — be specific] +**has** [problem — describe the pain, not the solution] +**and currently** [how they deal with it today — workarounds, alternatives, suffering] +**I can build** [solution — one sentence] +**because** [unfair advantage — why you specifically can do this] + +--- + +## Why Now + +What has changed — in technology, market, regulation, or culture — that makes this opportunity viable today when it was not viable before? If nothing has changed, the opportunity may not be real. + +--- + +## Strongest Counter-Arguments + +List the 2–3 most compelling reasons this might fail or not matter. Be honest. If you cannot articulate the risks, you have not thought deeply enough. + +1. [Counter-argument 1] +2. [Counter-argument 2] +3. [Counter-argument 3] + +--- + +## Scale of Impact + +If this works, what is the magnitude of change? "Saves 10 minutes per week" is different from "enables a workflow that was previously impossible." Neither is inherently better, but the answer shapes every downstream decision. + +--- + +## Kill Criteria + +Under what conditions should this idea be abandoned? Define this now, before sunk-cost bias sets in. + +- [Condition that would invalidate the opportunity] +- [Condition that would invalidate the opportunity] + +--- + +## Decision + +- [ ] **Proceed** to Phase 1: Product Framing +- [ ] **Kill** — opportunity does not justify further investment +- [ ] **Defer** — revisit when [condition] diff --git a/skills/product-context/model/templates/product-context-schema.yaml b/skills/product-context/model/templates/product-context-schema.yaml new file mode 100644 index 0000000..c34be55 --- /dev/null +++ b/skills/product-context/model/templates/product-context-schema.yaml @@ -0,0 +1,501 @@ +# Product Context Schema v1 +# +# Single source of truth for the entire product. +# Created by /aep-envision and /aep-map, updated by /aep-dispatch, /aep-build, /aep-wrap, /aep-reflect, /aep-calibrate. +# Committed to git — version history tracks how the product evolves. +# +# Sections are populated incrementally: +# /aep-envision → opportunity, product (incl. quality_dimensions), calibration.plan +# /aep-map → architecture, stories, topology, layer_gates, cost +# /aep-model → product/object-model.yaml + product/maps//object-map.yaml (standalone); calibration.history (dimension: object-model); stories[].object_model_refs +# /aep-calibrate → calibration.history, inline updates to architecture/product sections +# /aep-dispatch → stories[].status, stories[].openspec_change (per story) +# /aep-build → .dev-workflow/signals/status.json (story_status, pr_url, cost_usd) +# /aep-wrap → stories[].status, stories[].completed_at, stories[].pr_url, stories[].cost_usd (read from signals) +# /aep-reflect → changelog, stories (new), architecture (amendments) + +schema: v1 +project: +version: "0.1.0" # semantic: major (opportunity shift), minor (architecture/map change), patch (dispatch/build/wrap) +updated_at: +dispatch_epoch: 0 # incremented on every /aep-dispatch run — consistency marker for agents + +# ─── SPLIT MODE (v2) ──────────────────────────────────────── +# Products can operate in two modes: +# +# 1. Single-file (v1): All sections live in product-context.yaml. +# This is the default for simple projects. +# +# 2. Split-mode (v2): Stable product definition lives in +# product/index.yaml. Operational state lives in +# product-context.yaml. Skills check product/index.yaml first; +# if absent, fall back to product-context.yaml. +# +# In split mode, product-context.yaml omits `opportunity` and +# `product` sections. The `product/index.yaml` file uses the +# `personas` field (list) instead of `product.persona` (object). +# +# Discovery convention used by all skills: +# product_def = product/index.yaml if exists, else product-context.yaml +# operational = product-context.yaml + +# ─── CONCURRENCY PROTOCOL ──────────────────────────────────── +# Only the MAIN SESSION writes to this file. Workspace agents +# report status through .dev-workflow/signals/. The main session +# (via /aep-wrap, /aep-dispatch, /aep-reflect) reads signals and updates the +# YAML. This prevents git merge conflicts from concurrent writers. + +# ─── OPPORTUNITY (/aep-envision Phase 0) ───────────────────────── +# Validates whether the idea is worth building at all. +# Kill early if the opportunity doesn't survive a 5-minute challenge. +opportunity: + bet: "I believe [target user] has [problem], and I can build [solution] because [advantage]" + why_now: "What changed that makes this viable now?" + counter_arguments: + - "Strongest reason this might fail" + scale_of_impact: "What magnitude of change if this works?" + kill_criteria: + - "Condition that would invalidate the opportunity" + decision: proceed # proceed | kill | defer + decided_at: + +# ─── PRODUCT (/aep-envision Phase 1) ───────────────────────────── +# Precise product definition. Every statement must be convertible +# into a verification condition. Vague statements are useless to agents. +product: + problem: "Sharp problem statement — who, what, why inadequate" + persona: + description: "Concrete user description with context, skill level, tools, constraints" + jtbd: "When [situation], I want to [motivation], so I can [outcome]" + goals: # behavior-observable statements, verifiable by observing the running system + - "e.g., Poll the issue tracker on a fixed cadence and dispatch work with bounded concurrency" + non_goals: # things the product deliberately will NOT do, with reasoning + - statement: "e.g., Rich web UI" + reasoning: "This is a CLI-first tool. A web dashboard is a separate product." + mvp_boundary: + in_scope: + - "Specific capability (testable)" + out_of_scope: + - "What this does NOT do, even if users might expect it" + deferred: + - "Might add later, but agents should not build toward this" + constraints: + required_stack: + frontend: + backend: + database: + orm: + preferred_stack: {} # overridable with good reason + infrastructure: "Where this runs, deployment targets" + external_deps: + - name: + provides: "What it does for us" + failure_mode: "Graceful degradation or hard fail?" + layers: + - layer: 0 + name: "Walking Skeleton" + user_can: "End-to-end journey in concrete steps" + verification: "Test scenario that proves Layer 0 works" + outcome_contract: # optional — anchors the layer in product outcomes, not just feature completion + hypothesis: "If users can complete the basic flow, architecture is validated" + success_metric: + type: task_completion_rate # task_completion_rate | time_to_complete | error_rate | satisfaction_score + target: ">= 60%" + decision_rule: + keep_if: "metric >= target" + otherwise: "reflect_and_reslice" # /aep-reflect evaluates and may re-slice + - layer: 1 + name: + user_can: "Everything from Layer 0, plus..." + verification: "Test scenario for new capabilities" + outcome_contract: null # same structure as above, or null if not yet defined + # ── USER ACTIVITIES (Story Map Backbone) ── + # The horizontal axis of a Jeff Patton story map. + # Each activity is a discrete step in the user's journey, ordered left-to-right. + # Extracted during /aep-envision BEFORE defining layers — backbone first, releases second. + # Stories reference activities by id. Infrastructure stories leave activity null. + activities: + - id: # e.g., "upload-selfie", "generate-avatar" + name: "Human-readable name" # e.g., "Upload Selfie" + description: "What the user does" # User-centric verb phrase + order: 1 # Sequence in journey (backbone left-to-right) + layer_introduced: 0 # Which layer first enables this activity + # ── FAILURE MODEL ── + # Undocumented failure modes become undocumented bugs. + failure_model: + classes: + - name: "e.g., External Service Failures" + examples: "API timeout, non-200 status, malformed response" + detection: "HTTP status code checks, response schema validation" + recovery: "Retry with exponential backoff, skip dispatch for this tick" + escalation: "Log warning, continue service, alert operator after 3 consecutive failures" + degraded_operation: "What the system can still do when a dependency is down" + # ── SECURITY MODEL ── + # Trust boundaries must be explicit. Implicit trust assumptions cause the worst bugs. + security_model: + trust_boundaries: "What is trusted vs. what is not" + auth: "Authentication and authorization approach (or 'N/A for MVP')" + secret_handling: "Where secrets come from, how stored, how passed to components" + success_criteria: + functional: + - "Specific, testable condition" + non_functional: + - "Measurable threshold (e.g., API p95 < 200ms)" + open_questions: + - question: "Decision explicitly deferred" + default_assumption: "What agents should assume for now" + revisit_trigger: "When to revisit this" + decisions: + - decision: "What was decided" + reasoning: "Why" + alternatives: + - "What else was considered" + stress_test: + - challenge: "What was challenged" + angle: product_viability # product_viability | technical_feasibility | scope_control + resolution: "How it was resolved" + # ── QUALITY DIMENSIONS ── + # Dimensions where human judgment is needed that agents cannot provide. + # Declared during /aep-envision, checked by /aep-reflect, executed by /aep-calibrate. + # Not every dimension needs calibration — only those where "correct but not right" is likely. + # `object-model` is a STRUCTURAL gate (auto-drafted by /aep-model, human-approved), + # not a taste calibration — declare it for UI-facing products so UI stories get a + # noun-first Object Map before build. See skills/product-context/model. + quality_dimensions: + - dimension: visual-design # visual-design | ux-flow | object-model | api-surface | data-model | scope-direction | copy-tone | performance-quality + criticality: high # high | medium | low + first_calibration_layer: 0.5 + rationale: "Why this dimension needs human calibration" + +# ─── CALIBRATION (/aep-envision + /aep-calibrate) ──────────────────── +# Tracks which quality dimensions need human alignment checkpoints +# and the history of calibration decisions. +# Plan populated by /aep-envision (refined by /aep-map), history populated by /aep-calibrate. +# .5 layers are "human alignment layers" — the team pauses agent execution +# to recalibrate intent across one or more quality dimensions. +calibration: + plan: + - layer: 0.5 + dimensions: ["visual-design"] # from product.quality_dimensions + trigger: "Condition that activates this calibration" + # /aep-calibrate appends taste-dimension decisions here; /aep-model appends + # object-model approvals (artifact_path → product/maps//object-map.yaml). + history: + - dimension: visual-design + calibrated_at: + calibrated_from_layer: 0.5 + mode: establishment # establishment | extension + artifact_path: "calibration/visual-design.yaml" # null for inline (light) calibrations + sections_updated: [] # e.g., ["architecture.interfaces"] for inline calibrations + summary: "What was decided" + +# ─── ARCHITECTURE (/aep-map Step 1) ────────────────────────────── +# Module boundaries and interface contracts. A wrong boundary costs +# more than any implementation bug. Human review required. +architecture: + style: "e.g., modular monolith, microservices" + overview: "2-3 sentences on structure and why" + technical_spec: null # optional path to standalone technical specification document (e.g., "docs/technical-spec.md") + modules: + - name: + kind: backend # ui | backend | shared — `ui` modules render user-facing surfaces (used by /aep-model + the UI-facing story trigger) + responsibility: "What this module does" + does_not: "What this module does NOT do (defines the boundary)" + owns: + - "Data/state/resources this module is authority on" + depends_on: + - + technology: null # null = default stack + key_concepts: # domain objects, patterns, abstractions implementers need + - "e.g., tokens, sessions, RBAC roles" + # ── DOMAIN MODEL ── + # Typed entity definitions for cross-cutting domain objects. + # Complements per-module key_concepts with precise schemas when entities span module boundaries. + domain_model: + - name: + purpose: "One sentence — what this entity represents" + fields: + - name: id + type: string + default: null + required: true + notes: "Stable identifier. Derived from [rule]." + normalization_rules: + - "e.g., Identifiers lowercased and slugified" + invariants: + - "e.g., A completed entity always has a non-null completed_at" + # ── PROTOCOL SEQUENCES ── + # Multi-step interaction contracts (handshakes, streaming, stateful exchanges). + # Only needed when interface contracts have ordering, state, or timing constraints. + protocol_sequences: + - name: + participants: ["module-a", "module-b"] + trigger: "What initiates this protocol" + steps: + - sender: module-a + message_type: "init" + timeout_ms: 5000 + payload_example: null # optional illustrative JSON payload (e.g., '{"type": "init", "params": {...}}') + - sender: module-b + message_type: "ack" + payload_example: null + error_behavior: "What happens if a step fails" + timeout_behavior: "What happens if a step takes too long" + interfaces: + - from: + to: + protocol: "HTTP REST | gRPC | function call | message queue" + endpoint: "Specific API path or function signature" + request: {} # exact data structure + response: {} # exact data structure + errors: + - code: + meaning: "What this error means" + sla: null # expected latency, throughput + data_flows: + - journey: "User journey name" + path: "User → [Module] → action → [Module] → response" + third_party: + - name: + provides: "Specific capability" + integration_point: "Which module, how" + failure_mode: "Behavior when down" + deployment: + environments: ["local", "staging", "production"] + module_runtime: {} # { "auth": "bun", "worker": "cloudflare-workers" } + persistence: {} # { "users": "postgres", "cache": "redis" } + amendment_log: # boundary/contract issues found during story decomposition + - proposed_by: + module_affected: + proposed_change: "What should change" + reasoning: "Why" + status: pending # pending | accepted | rejected + adrs: + - id: ADR-001 + title: "Architecture decision title" + context: "What prompted this" + decision: "What was decided" + reasoning: "Why" + consequences: "What this enables and constrains" + +# ─── STORIES (/aep-map Steps 2-3) ──────────────────────────────── +# The atomic units of work. Each story is a self-contained spec +# that an agent can implement without asking questions. +# +# State machine: +# pending → ready (all dependencies completed) +# pending → blocked (a dependency failed) +# ready → in_progress (/aep-dispatch assigns) +# in_progress → in_review (PR submitted) +# in_review → completed (verification passes) +# in_review → in_progress (verification fails, retry) +# in_progress → failed (retry limit exceeded) +# any → deferred (user postpones) +# +# Recovery transitions (user-initiated): +# failed → pending (user resets after spec fix) +# blocked → pending (blocking dependency resolved) +# deferred → pending (user un-defers) +stories: + - id: -001 + title: "Short, descriptive" + layer: 0 + module: + activity: null # user activity from product.activities (null for infrastructure stories) + capability: null # capability id (product/index.yaml capabilities[].id); /aep-map sets it. Null in v1/single-journey → resolves to the default capability (project slug). Used to locate product/maps//object-map.yaml. + calibration_type: null # visual-design | ux-flow | api-surface | data-model | scope-direction | copy-tone | performance-quality (null for non-calibration stories) + object_model_refs: [] # /aep-model: object-map slice(s) this UI story realizes, as "#" where ∈ object-map primary_objects/supporting_objects, e.g. ["product/maps/dashboard/object-map.yaml#order"] (empty for non-UI stories) + slice: 1 # execution slice within layer (parallel batch) + status: pending + priority: critical # critical | high | medium | low + business_value: null # 1-10 numeric. If null, derived from priority: critical=10, high=7, medium=4, low=1 + complexity: S # S | M | L + compile_mode: single_change # single_change | grouped_change | shared_enabler + change_group: null # group ID for grouped_change mode (max 3 stories per group) + dependencies: [] # story IDs that must complete first + description: + what_changes: "Observable difference when complete" + why: "Connection to Context Document / layer" + acceptance_criteria: + - "Specific, automatable test criterion" + interface_obligations: + implements: [] # interface contracts this story creates/modifies + consumes: [] # interface contracts this story calls + contract_tests_required: false + files_affected: + - "path/to/likely/file.ts" # for conflict detection + technical_notes: null # optional — known pitfalls, guidance to prevent mistakes + verification: + unit: ["What to unit test"] + integration: ["Cross-module tests"] + contract: ["Interface compliance tests"] + # ── Dispatch scoring (computed by /aep-dispatch, not manually set) ── + readiness_score: null # 0.0-1.0 spec completeness (acceptance criteria + interfaces + files + verification + open questions) + dispatch_score: null # (business_value + unblock_potential + critical_path_urgency + reuse_leverage) / (complexity_cost + ambiguity_penalty + interface_risk) + on_critical_path: false + # ── Execution tracking (updated by /aep-dispatch, /aep-build, /aep-wrap) ── + assigned_to: null # workspace session name + openspec_change: null # OpenSpec change name + dispatched_at_epoch: null # which dispatch_epoch assigned this story + attempt_count: 0 + max_retries: 4 # override per story (default from topology.routing.retry) + cost_usd: null + started_at: null # ISO 8601 + completed_at: null # ISO 8601 + pr_url: null + failure_logs: # structured — not free text + - attempt: 1 + error_class: test_failure # test_failure | timeout | context_overflow | merge_conflict + approach_summary: "What the agent tried" + failure_point: "Which step failed" + root_cause: "Best guess at why" + unexplored_alternatives: [] # critical for fresh-agent retries + timestamp: + +# ─── TOPOLOGY (/aep-map Step 4) ────────────────────────────────── +# Agent roles, contracts, and routing. Defined at planning time +# because agents need clear boundaries before execution starts. +topology: + roles: + - name: implementer + purpose: "Takes story spec, produces code + tests + PR" + does: + - "implement" + - "unit test" + - "contract test" + - "submit PR" + does_not: + - "decide scope" + - "modify architecture" + - "skip tests" + input_contract: # schema-defined — agents communicate through structured artifacts + story_spec: "Full story from stories section" + context_slice: + context_document: "Pruned to: problem, constraints, relevant layer" + system_map_module: "This story's module definition" + adjacent_interfaces: "Interface contracts where from/to matches module" + dependency_artifacts: "Public API surface from completed dependencies" + output_contract: + implementation: + branch_name: "string" + files_changed: "FileDiff[]" + pr_url: "string" + verification: + unit_tests: "TestResult[]" + contract_tests: "TestResult[]" + all_passing: "boolean" + status_report: + story_id: "string" + outcome: "success | failure" + error_summary: "string (if failure)" + what_was_not_tried: "string[] (critical for fresh-agent retries)" + context_composition: + - "Story spec (full)" + - "Context Document (pruned: problem, constraints, relevant layer)" + - "System Map (story's module + adjacent interfaces only)" + - "Dependency artifacts (public API surface only)" + cost_budget: + input_tokens_max: 50000 + output_tokens_max: 20000 + alert_threshold_total: 100000 + routing: + dispatch: fifo_within_slice # dispatch policy + concurrency_limit: 5 # max parallel agents + conflict_detection: files_affected_overlap + retry: "2x same agent → failure analyst → fresh agent → human escalation" + autonomous: false # true = /aep-autopilot can dispatch without human confirmation + auto_design: false # true = skip /aep-design, go straight to /aep-launch for ambiguous stories + skip_human_eval: none # none | backend | all — which stories skip human eval in /aep-wrap + # ─── v2 autonomy (all default to human-in-the-loop; opt-in only) ─── + full_auto: false # master switch — true automates the strategic gates (design escalation, qualitative outcome eval); implies auto_design + auto_outcome_eval + watch.auto_create + auto_outcome_eval: none # none | quantitative — quantitative layer outcome contracts auto-evaluate from telemetry (see reflect/references/telemetry-ingestion.md) + deploy_targets: # post-deploy dogfood targets (G4); omit → fall back to CI/deploy output + staging_url: null + production_url: null + dogfood: # host-aware post-deploy validation (see executor/references/dogfood-validation.md) + method: auto # auto | agent-browser | codex-native | playwright + post_deploy_env: none # none | staging | production + on_issue: create_story # create_story | escalate + post_merge_guard: # G4a — watch merged stories' deploy health (see autopilot/references/post-merge-guard.md) + window_min: 15 + auto_revert: false # conservative default: warn + escalate only; true = auto `gh pr revert` on confirmed regression + health_signals: [] # e.g. ["ci_status", "error_rate", "health_endpoint"] + telemetry_sources: [] # G5 — read-only signal sources. Detected by /aep-scaffold audit (or set by hand); /aep-map binds each needed quantitative success_metric + health_signal via metric_map (coverage rule: reflect/references/telemetry-ingestion.md §1.5). token_env only — never embed secrets. + # - { kind: error_stream, endpoint: "https://…?since={since}", token_env: SENTRY_TOKEN, metric_map: { error_rate: "" } } + watch: # G6 /aep-watch self-feeding discovery + sources: [] + interval: 30m + auto_create: false # surface proposed stories for confirmation; true (or full_auto) = auto-create + dispatch + handoffs: + - from: implementer + to: evaluator + trigger: implementation_complete # Phase 4 done, Phase 5 starts — evaluator runs BEFORE PR creation + payload: "eval-request.md + code diff + contracts.md + feature-verification.json" + validation: + - "All tasks committed on the feature branch" + - "Dev server running and accessible" + - "eval-request.md created with round number and change summary" + on_validation_failure: "reject handoff, notify source agent" + +# ─── LAYER GATES ───────────────────────────────────────────── +# Integration tests that verify a completed layer works as a whole. +# Must pass before advancing to the next layer. +layer_gates: + - layer: 0 + status: not_started # not_started | running | passed | failed + test_definition: "End-to-end user journey from Layer 0 MVP contract" + results: # structured test results + tests_run: 0 + tests_passed: 0 + tests_failed: 0 + failures: [] # [{ test: "...", error: "...", boundary: "module_a → module_b" }] + completed_at: null + +# ─── WAVES (/aep-map Step 3) ───────────────────────────────────── +# Groups stories by layer + wave for batch dispatch. +# Computed by /aep-map from the dependency DAG. Wave 1 has no in-layer +# dependencies; Wave 2 depends on Wave 1 completing; etc. +# User-facing term: "Wave". YAML field: stories[].slice. +waves: + - layer: 0 + wave: 1 + stories: [] # story IDs in this wave + theme: "Walking skeleton foundation" + - layer: 0 + wave: 2 + stories: [] + theme: "Walking skeleton integration" + +# ─── COST TRACKING ─────────────────────────────────────────── +# Accumulated cost data. Updated by /aep-build after each story. +# Reviewed by /aep-reflect for optimization opportunities. +cost: + total_usd: 0 + by_layer: {} # { "0": 12.50, "1": 8.30 } + by_module: {} # { "auth": 5.20, "api": 7.30 } + by_story: {} # { "PROJ-001": 3.10 } + alerts: # structured anomaly records + - story_id: null + type: cost_exceeded # cost_exceeded | retry_concentration | timeout_pattern + detail: "Description of the anomaly" + threshold: null + actual: null + timestamp: null + +# ─── CHANGELOG ─────────────────────────────────────────────── +# Semantic history of how the product context evolved. +# git log shows file diffs; changelog shows why things changed. +# Appended by every skill that modifies this file. +changelog: + - date: + type: initial # initial | envision_update | map_update | dispatch | reflection | outcome_evaluation | build | wrap | layer_gate_pass | layer_gate_fail | architecture_review + author: human # human | agent + summary: "What changed and why" + sections_changed: + - + # Only for type: reflect + feedback: + bugs: [] + refinements: [] + discoveries: [] + opportunity_shifts: [] diff --git a/skills/product-context/model/templates/references/symphony-spec-reference.md b/skills/product-context/model/templates/references/symphony-spec-reference.md new file mode 100644 index 0000000..ab44cdb --- /dev/null +++ b/skills/product-context/model/templates/references/symphony-spec-reference.md @@ -0,0 +1,234 @@ +# Symphony SPEC.md — Specification Writing Reference + +This document extracts reusable documentation patterns from OpenAI's [Symphony SPEC.md](https://github.com/openai/symphony/blob/main/SPEC.md), a ~15,000-word language-agnostic specification for a coding agent orchestration service. Symphony's spec is notable because it is precise enough that any coding agent can implement it in any programming language without clarifying questions. + +Use this reference when writing specifications for systems with protocol-level complexity. The patterns here are the standard to aim for. + +--- + +## Source Structure + +Symphony's SPEC.md has 15 sections. Each maps to an AEP template: + +| # | Symphony Section | What It Does | AEP Template | +| --- | ----------------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------ | +| 1 | Problem Statement | Defines what the service IS, what problems it solves, what it is NOT | `context-document.md` (Problem Statement) | +| 2 | Goals and Non-Goals | Behavior-observable goals, explicit non-goals with reasoning | `context-document.md` (Goals/Non-Goals) | +| 3 | System Overview | Named components + abstraction layers + external deps | `system-map.md` (Modules) | +| 4 | Core Domain Model | Typed entity fields, defaults, normalization rules, stable identifiers | `system-map.md` (Domain Model) | +| 5 | Workflow Specification | Repository contract with schema, validation, dynamic reload | `technical-spec.md` (Configuration) | +| 6 | Configuration Specification | Source precedence, typed getters, dynamic reload, config cheat sheet | `technical-spec.md` (Configuration) | +| 7 | Orchestration State Machine | Named states, transition triggers, idempotency rules | `technical-spec.md` (State Machines) | +| 8 | Polling, Scheduling, Reconciliation | Poll loop, candidate selection, concurrency, retry/backoff | `technical-spec.md` (Protocol Specs) | +| 9 | Workspace Management and Safety | Filesystem lifecycle, hooks, safety invariants | `technical-spec.md` (Security) | +| 10 | Agent Runner Protocol | Launch contract, handshake with JSON transcripts, streaming | `technical-spec.md` (Protocol Specs) | +| 11 | Issue Tracker Integration | Adapter contract, query semantics, normalization rules | `system-map.md` (Interface Contracts) | +| 12 | Prompt Construction | Template rendering, retry semantics, failure handling | `technical-spec.md` (Protocol Specs) | +| 13 | Logging, Status, Observability | Structured logs, runtime snapshots, optional HTTP API | `technical-spec.md` (Observability) | +| 14 | Failure Model | 5 failure classes, per-class recovery, restart recovery | `context-document.md` (Failure Model) / `technical-spec.md` | +| 15 | Security and Operational Safety | Trust boundaries, filesystem safety, secret handling | `context-document.md` (Security Model) / `technical-spec.md` | + +--- + +## Extracted Patterns + +### Pattern 1: Problem-First Framing + +Symphony opens with a single sentence saying what the service IS, then lists exactly 4 operational problems it solves, then states what it is NOT. + +**How Symphony does it:** + +> "Symphony is a long-running automation service that continuously reads work from an issue tracker, creates an isolated workspace for each issue, and runs a coding agent session for that issue inside the workspace." +> +> The service solves four operational problems: [enumerated list] +> +> Important boundary: Symphony is a scheduler/runner and tracker reader. Ticket writes are performed by the coding agent. + +**Why it works:** An agent reading this spec knows in 3 paragraphs exactly what to build and — critically — what NOT to build. The "important boundary" prevents the most common scope creep. + +**Use in AEP:** `context-document.md` Problem Statement section. Ensure every problem statement includes a "what it is NOT" boundary. + +--- + +### Pattern 2: Behavior-Observable Goals with Explicit Non-Goals + +Goals are statements that can be verified by observing the running system. Non-goals are things a reasonable person might expect but the system deliberately excludes. + +**How Symphony does it:** + +Goals: + +- "Poll the issue tracker on a fixed cadence and dispatch work with bounded concurrency." +- "Create deterministic per-issue workspaces and preserve them across runs." +- "Recover from transient failures with exponential backoff." + +Non-Goals: + +- "Rich web UI or multi-tenant control plane." +- "General-purpose workflow engine or distributed job scheduler." + +**Why it works:** Each goal is testable — you can observe the system and confirm it does (or doesn't do) the thing. Non-goals prevent agents from gold-plating. + +**Use in AEP:** `context-document.md` Goals/Non-Goals section. Distinct from "In Scope / Out of Scope" — scope defines what the system does, goals define how you know it's working. + +--- + +### Pattern 3: Typed Entity Definitions with Normalization Rules + +Every domain entity has typed fields with defaults, and normalization rules that prevent ambiguity. + +**How Symphony does it:** + +``` +Issue: + id (string) — Stable tracker-internal ID + identifier (string) — Human-readable ticket key (example: ABC-123) + priority (integer or null) — Lower numbers are higher priority + labels (list of strings) — Normalized to lowercase + blocked_by (list of blocker refs) — Each contains id, identifier, state +``` + +Normalization rules: + +- "Workspace Key: Derive from issue.identifier by replacing any character not in [A-Za-z0-9._-] with \_" +- "Normalized Issue State: Compare states after lowercase" + +**Why it works:** No ambiguity about what a field contains, what type it is, what the default is, or how to compare values. A coding agent in any language can implement this without guessing. + +**Use in AEP:** `system-map.md` Domain Model section. Replace unstructured "Key internal concepts" bullet lists with typed field tables. + +--- + +### Pattern 4: State Machine Documentation + +Stateful entities get explicit state diagrams with named states, transition triggers, and recovery rules. + +**How Symphony does it:** + +Orchestration states (distinct from tracker states): + +1. Unclaimed — not running, no retry scheduled +2. Claimed — reserved to prevent duplicate dispatch +3. Running — worker task exists +4. RetryQueued — worker not running, retry timer exists +5. Released — claim removed + +Transition triggers: Poll Tick, Worker Exit (normal), Worker Exit (abnormal), Retry Timer Fired, Reconciliation State Refresh, Stall Timeout. + +"Important nuance: A successful worker exit does not mean the issue is done forever." + +**Why it works:** Every state is named, every transition has a trigger, and easy-to-miss nuances are called out explicitly. + +**Use in AEP:** `technical-spec.md` State Machines section. Also useful in `system-map.md` when a module owns a state machine. + +--- + +### Pattern 5: Protocol Specs with Illustrative JSON Transcripts + +Multi-step interactions get exact handshake sequences with example payloads. + +**How Symphony does it:** + +```json +{"id":1,"method":"initialize","params":{"clientInfo":{"name":"symphony","version":"1.0"},"capabilities":{}}} +{"method":"initialized","params":{}} +{"id":2,"method":"thread/start","params":{"approvalPolicy":"...","sandbox":"...","cwd":"/abs/workspace"}} +{"id":3,"method":"turn/start","params":{"threadId":"","input":[{"type":"text","text":""}]}} +``` + +Each step includes: what to send, what to expect back, timeout behavior, error mapping. + +**Why it works:** An implementor can literally trace through the JSON transcript to verify their implementation. No prose interpretation needed. + +**Use in AEP:** `system-map.md` Protocol Sequences section and `technical-spec.md` Protocol Specifications section. + +--- + +### Pattern 6: "Important Boundary" / "Important Nuance" Callouts + +Inline blockquote markers flag precision points that are easy to miss. + +**How Symphony does it:** + +> **Important boundary:** Symphony is a scheduler/runner and tracker reader. Ticket writes are typically performed by the coding agent. + +> **Important nuance:** A successful worker exit does not mean the issue is done forever. The orchestrator schedules a short continuation retry. + +**Why it works:** Agents scan documents for actionable information. These callouts are semantic anchors that prevent the most common implementation mistakes. They stand out visually and can be grep'd. + +**Use in AEP:** Convention across all templates — `system-map.md`, `technical-spec.md`, and anywhere precision matters. + +--- + +### Pattern 7: Agent-Friendly Redundancy (Config Cheat Sheet) + +Symphony includes a section explicitly labeled "intentionally redundant" that summarizes all configuration in one flat table. + +**How Symphony does it:** + +> "This section is intentionally redundant so a coding agent can implement the config layer quickly." +> +> - `tracker.kind`: string, required, currently `linear` +> - `polling.interval_ms`: integer, default `30000` +> - `workspace.root`: path, default `/symphony_workspaces` +> [... all fields in one list] + +**Why it works:** Agents pay a cognitive/context tax for cross-referencing. A redundant summary eliminates cross-referencing entirely for the most common implementation task (reading config). + +**Use in AEP:** `story-spec.md` Implementation Cheat Sheet section. Applied at the story level (not product level) because the relevant subset varies per story. + +--- + +### Pattern 8: Enumerated Failure Classes with Per-Class Recovery + +Failures are taxonomized into classes, each with detection method and recovery behavior. + +**How Symphony does it:** + +5 failure classes: + +1. Workflow/Config Failures — missing files, invalid YAML, missing credentials +2. Workspace Failures — directory creation, hook timeout, invalid paths +3. Agent Session Failures — handshake failure, turn timeout, subprocess exit +4. Tracker Failures — API errors, non-200 status, malformed payloads +5. Observability Failures — snapshot timeout, dashboard render errors + +Each class has explicit recovery: "Dispatch validation failures: Skip new dispatches. Keep service alive. Continue reconciliation." + +**Why it works:** Undocumented failure modes become undocumented bugs. By enumerating failure classes early, every implementor handles the same set of failure scenarios the same way. + +**Use in AEP:** `context-document.md` Failure Model section (product-level) and `technical-spec.md` Failure Model section (implementation-level). + +--- + +### Pattern 9: Trust Boundary Documentation + +Security is not a checklist — it's a boundary declaration about what is trusted and what is not. + +**How Symphony does it:** + +> "Each implementation defines its own trust boundary." +> +> "Implementations should state clearly whether they are intended for trusted environments, more restrictive environments, or both." +> +> "Hooks are fully trusted configuration. Hooks run inside the workspace directory." + +Filesystem safety is mandatory: + +- Workspace path must remain under configured workspace root +- Coding-agent cwd must be the per-issue workspace path +- Workspace directory names must use sanitized identifiers + +**Why it works:** Rather than prescribing specific security controls, Symphony forces each implementation to explicitly declare its trust posture. This prevents the worst outcome: implicit trust assumptions that no one documented. + +**Use in AEP:** `context-document.md` Security Model section (trust boundaries and auth) and `technical-spec.md` Security section (filesystem safety, secret handling). + +--- + +## Key Principle + +The overarching principle from Symphony's approach: + +> **Define WHAT the system does and HOW it behaves under all conditions. Let implementors decide the programming language, framework, and internal architecture.** + +This is what separates a specification from documentation. Documentation describes what was built. A specification defines what must be built — precisely enough that the builder needs no clarifying questions. diff --git a/skills/product-context/model/templates/story-spec.md b/skills/product-context/model/templates/story-spec.md new file mode 100644 index 0000000..62380e2 --- /dev/null +++ b/skills/product-context/model/templates/story-spec.md @@ -0,0 +1,97 @@ +# Story Specification Template + +The atomic unit of work for the execution plane. A well-written story spec gives an agent everything it needs to implement, verify, and submit a PR without asking questions. + +Quality bar: **a single-responsibility agent reading only this spec, the Context Document, and the relevant System Map slice should produce correct, mergeable code.** + +--- + +## Metadata + +- **Story ID**: [Unique identifier, e.g., `SANDBOX-001`] +- **Title**: [Short, descriptive] +- **Layer**: [0, 1, 2… — which development layer] +- **Module**: [Primary module, as defined in System Map] +- **Activity**: [Which user activity this enables, from `product.activities`. Null for infrastructure stories that don't directly serve a user journey step.] +- **Wave**: [Which wave (execution slice) within the layer this belongs to] +- **Dependencies**: [Story IDs that must complete before this starts] +- **Estimated complexity**: [S / M / L] + +--- + +## Description + +### What changes when this story is complete + +[Observable difference in the system. Focus on behavior, not implementation. The agent decides how; this spec defines what.] + +### Why this story exists + +[Connect to the Context Document. Which layer of the MVP contract does this serve? Why this layer and not a later one?] + +--- + +## Acceptance Criteria + +Each must be automatable as a test. If it cannot be automated, it is too vague or belongs in manual review. + +1. [Criterion — specific, observable, testable] +2. [Criterion] +3. [Criterion] + +--- + +## Interface Obligations + +If this story touches a module boundary: + +- **Implements**: [Endpoints/APIs created or modified, referencing System Map contracts] +- **Consumes**: [Other module APIs called, referencing System Map contracts] +- **Contract tests required**: [Yes/No] + +--- + +## Technical Notes + +[Optional. Only include guidance that prevents known pitfalls. Do not over-specify — let the agent choose its approach.] + +--- + +## Implementation Cheat Sheet + +[Optional. Intentionally redundant summary of everything an implementer agent needs from the Context Document and System Map, copied here so the agent doesn't need to cross-reference. Include only when the story touches 2+ modules or has complex interface obligations. + +This section trades DRY for agent effectiveness — a coding agent implementing this story can work from this section alone without searching other documents.] + +- **Stack**: [relevant subset] +- **Module**: [name] — [one-line responsibility] +- **Key types**: [TypeScript/schema definitions the agent will need] +- **Adjacent interfaces**: [endpoints this story calls or implements, with shapes] +- **Conventions**: [naming, file structure, error handling patterns in this codebase] + +--- + +## Files Likely Affected + +[Optional. Helps orchestrator detect conflicts between parallel stories.] + +--- + +## Verification Strategy + +- **Unit tests**: [What to unit test] +- **Integration tests**: [Cross-module interaction tests, if applicable] +- **Contract tests**: [Interface compliance tests, if applicable] + +--- + +## Definition of Done + +All of the following must be true: + +1. All acceptance criteria pass as automated tests +2. All relevant contract tests pass +3. Code follows project conventions +4. PR submitted with description linking to this Story ID +5. No regressions in existing tests +6. Structured status report produced diff --git a/skills/product-context/model/templates/system-map.md b/skills/product-context/model/templates/system-map.md new file mode 100644 index 0000000..50978a0 --- /dev/null +++ b/skills/product-context/model/templates/system-map.md @@ -0,0 +1,182 @@ +# System Map Template + +Defines the architecture at the module level. Serves two functions: (1) establishes module boundaries so decomposition agents work independently, (2) defines interface contracts so parallel implementation stays compatible. + +A module boundary drawn wrong costs more to fix than any implementation bug. Review carefully before proceeding to story decomposition. + +### Callout Conventions + +Use these blockquote markers throughout this document to flag precision points that agents must not miss: + +> **Important boundary:** Where a responsibility stops and another begins +> **Important nuance:** Easy-to-miss detail that changes implementation +> **Important constraint:** Hard limit that shapes design choices + +--- + +## System Overview + +**Architecture style**: [e.g., microservices, modular monolith, serverless functions] + +**High-level description**: [2–3 sentences on structure and why this architecture was chosen, referencing Context Document constraints.] + +--- + +## Modules + +### [Module Name] + +**Responsibility**: [What this module does and does not do. The "does not" part defines the boundary.] + +**Owns**: [Data, state, or resources this module is the authority on. No other module directly modifies these.] + +**Depends on**: [Other modules this one calls or consumes from.] + +**Technology**: [If different from default stack.] + +**Key internal concepts**: [Domain objects, patterns, or abstractions implementers need to understand.] + +[Repeat for each module] + +--- + +## Domain Model + +Domain entities that span module boundaries or require precise typing. Module-specific concepts remain in the Modules section above. Each entity has typed fields so implementers in any language know exactly what to build. See `references/symphony-spec-reference.md` Pattern 3 for the standard. + +### [Entity Name] + +**Purpose**: [One sentence — what this entity represents in the system.] + +**Fields**: + +| Field | Type | Default | Required | Notes | +| -------- | ------ | --------- | -------- | -------------------------------------------- | +| `id` | string | — | yes | Stable across restarts. Derived from [rule]. | +| `status` | enum | `pending` | yes | See state machine if applicable. | + +**Normalization rules**: + +- [e.g., "All identifiers are lowercased and slugified"] +- [e.g., "Replace characters not in [A-Za-z0-9._-] with \_"] + +**Invariants**: + +- [Conditions that must always hold, e.g., "A completed entity always has a non-null completed_at timestamp"] + +[Repeat for each domain entity] + +--- + +## Interface Contracts + +For every module-to-module connection. These will be enforced by automated contract tests in Phase 4. An undefined interface is a guaranteed integration failure. + +### [Module A] → [Module B] + +**Protocol**: [HTTP REST, gRPC, message queue, function call, etc.] + +**Endpoint / Channel**: [Specific API path, queue name, or function signature.] + +**Request shape**: + +``` +[Exact data structure — TypeScript types, JSON Schema, or equivalent. Specify required vs optional, types, constraints.] +``` + +**Response shape**: + +``` +[Same specificity as request.] +``` + +**Error contract**: + +``` +[What errors can be returned, their shape, what the caller should do for each.] +``` + +**SLA**: [Expected latency, throughput, availability. "TBD" is acceptable if noted as open question.] + +--- + +## Protocol Sequences + +For interface contracts that involve multi-step interactions (handshakes, streaming, request-response chains), document the sequence here. Simple request-response contracts don't need this — use it when the interaction has ordering, state, or timing constraints. See `references/symphony-spec-reference.md` Pattern 5 for the standard. + +### [Protocol Name]: [Module A] <> [Module B] + +**Trigger**: [What initiates this protocol] + +**Sequence**: + +1. [Module A] sends [message type]: + ```json + { "type": "init", "payload": { "..." } } + ``` +2. [Module B] responds with [message type]: + ```json + { "type": "ack", "session_id": "..." } + ``` +3. [Steady-state interaction description] + +**Timeout behavior**: [What happens if step N takes too long] +**Error behavior**: [What happens if step N fails] + +> **Important nuance:** [Easy-to-miss detail about this protocol] + +--- + +## Data Flow + +For each primary user journey in the Layered MVP Contract, trace the data path: + +### [Journey Name] + +``` +User → [Module] → action → [Module] → action → response +``` + +Show which module handles each step, what data passes between them, where state is persisted. + +--- + +## Third-Party Boundaries + +### [Service Name] + +**Provides**: [Specific capability used.] +**Integration point**: [Which module, how.] +**Failure mode**: [Behavior when service is down — graceful degradation or hard fail?] +**Limitations**: [Rate limits, quotas, latency.] + +--- + +## Deployment Topology + +**Environments**: [Local dev, staging, production.] +**Module → Runtime mapping**: [Which modules run where.] +**Persistence**: [Databases/storage, which modules own them.] + +--- + +## Architecture Decision Records + +### ADR-001: [Title] + +**Context**: [What prompted this decision.] +**Decision**: [What was decided.] +**Reasoning**: [Why, over alternatives.] +**Consequences**: [What this enables and constrains.] + +--- + +## Amendment Log + +During story decomposition, agents may discover boundary or contract issues. Collected here, reviewed in batch. + +| Proposed By | Module Affected | Proposed Change | Reasoning | Status | +| ----------- | --------------- | --------------- | --------- | ------------------------- | +| | | | | pending/accepted/rejected | + +Trigger Architecture Review when: 3+ pending amendments, or any single amendment affects an interface contract. diff --git a/skills/product-context/model/templates/technical-spec.md b/skills/product-context/model/templates/technical-spec.md new file mode 100644 index 0000000..0d604b5 --- /dev/null +++ b/skills/product-context/model/templates/technical-spec.md @@ -0,0 +1,337 @@ +# Technical Specification Template: [System Name] + +A production-grade system specification for protocol-heavy systems. Use this template when the Context Document and System Map don't capture enough behavioral detail for agents to implement without ambiguity — typically when the system has multi-step protocols, multiple state machines, or complex failure/recovery semantics. + +> **Important boundary:** This template is opt-in. Most projects go directly from context-document to system-map. Use this only when the system has protocol-level complexity that those templates don't capture. + +**When to use this template:** During `/aep-map`, if the System Map reveals 3+ interface contracts requiring protocol sequences, 2+ distinct state machines, explicit failure classes with different recovery behaviors, or trust boundaries crossing module lines. + +**Reference exemplar:** See `references/symphony-spec-reference.md` for an annotated analysis of OpenAI's Symphony SPEC.md — the standard this template is modeled after. + +Quality standard: **every statement must be convertible into a verification condition.** If it cannot be tested, it is not precise enough. + +--- + +## 1. Service Identity + +**[System name]** is [one sentence defining what this service IS — what it does, for whom, in what context]. + +The service solves [N] operational problems: + +- [Problem 1 — concrete operational pain point this service eliminates] +- [Problem 2] +- [Problem 3] + +> **Important boundary:** [What this service is NOT. State the most likely misunderstanding about scope. Example: "Symphony is a scheduler/runner and tracker reader. Ticket writes are performed by the coding agent."] + +### Trust Posture + +[State explicitly whether this service is intended for trusted environments, restricted environments, or both. This shapes every downstream security and approval decision.] + +--- + +## 2. Goals and Non-Goals + +### 2.1 Goals + +[Behavior-observable statements. Each must be verifiable by observing the running system. Not aspirations — observable behaviors.] + +- [Goal — e.g., "Poll the issue tracker on a fixed cadence and dispatch work with bounded concurrency."] +- [Goal — e.g., "Recover from transient failures with exponential backoff."] +- [Goal — e.g., "Support restart recovery without requiring a persistent database."] + +### 2.2 Non-Goals + +[Things a reasonable person might expect this system to do, but it deliberately will NOT. Each explains why.] + +- [Non-goal — e.g., "Rich web UI or multi-tenant control plane."] +- [Non-goal — e.g., "General-purpose workflow engine or distributed job scheduler."] + +--- + +## 3. System Overview + +### 3.1 Components + +[Named components, each with a one-line responsibility. Number them for cross-referencing.] + +1. `[Component Name]` + - [One-line responsibility. What it does and — if ambiguous — what it does NOT do.] + +2. `[Component Name]` + - [One-line responsibility.] + +### 3.2 Abstraction Layers + +[Group components into named layers. This makes the system easier to port and reason about.] + +1. `[Layer Name]` ([layer purpose]) + - [What lives in this layer] + +2. `[Layer Name]` ([layer purpose]) + - [What lives in this layer] + +### 3.3 External Dependencies + +- [Dependency — what it provides, how failure is handled] +- [Dependency] + +--- + +## 4. Domain Model + +### 4.1 Entities + +#### [Entity Name] + +[One sentence — what this entity represents in the system.] + +Fields: + +| Field | Type | Default | Required | Notes | +| ------------ | --------- | --------- | -------- | --------------------------------------- | +| `id` | string | — | yes | Stable identifier. Derived from [rule]. | +| `status` | enum | `pending` | yes | See state machine in Section 5. | +| `created_at` | timestamp | — | no | ISO-8601. | + +#### [Entity Name] + +[Repeat for each domain entity.] + +### 4.2 Normalization Rules + +[How values are compared, derived, and sanitized across the system.] + +- `[Identifier Type]` — [Derivation rule, e.g., "Replace any character not in [A-Za-z0-9._-] with \_"] +- `[State comparison]` — [e.g., "Compare states after lowercase"] + +### 4.3 Invariants + +[Conditions that must always hold across the system, not just within one entity.] + +- [Invariant — e.g., "A running entity always has a non-null started_at timestamp"] +- [Invariant — e.g., "At most max_concurrent entities may be in Running state"] + +--- + +## 5. State Machines + +### [Stateful Entity] States + +[These are the system's internal states, which may differ from external/user-visible states.] + +1. `[State Name]` — [When the entity is in this state, what is true about it] +2. `[State Name]` — [Description] +3. `[State Name]` — [Description] + +> **Important nuance:** [Easy-to-miss detail about state semantics, e.g., "A successful exit does not mean the entity is done forever."] + +### Transition Triggers + +| Trigger | From State(s) | To State | Side Effects | +| ------- | ------------- | -------- | ------------------------------------------------ | +| [Event] | [State] | [State] | [What happens — cleanup, notifications, retries] | + +### Idempotency and Recovery Rules + +- [Rule — e.g., "Claimed checks are required before launching any worker"] +- [Rule — e.g., "Restart recovery is tracker-driven, no durable DB required"] + +--- + +## 6. Configuration Specification + +### 6.1 Source Precedence + +[Where configuration comes from, in priority order.] + +1. [Highest priority — e.g., CLI arguments] +2. [e.g., Configuration file values] +3. [e.g., Environment variable indirection] +4. [Lowest priority — built-in defaults] + +### 6.2 Dynamic Reload Semantics + +[Can config change at runtime? What happens when it does?] + +- [e.g., "Watch config file for changes. Re-apply without restart."] +- [e.g., "Invalid reloads must not crash the service. Keep last known good config."] + +### 6.3 Validation Rules + +[What must be true before the system starts dispatching work?] + +- [e.g., "Config file can be loaded and parsed"] +- [e.g., "API key is present after environment variable resolution"] + +### 6.4 Config Cheat Sheet + +> This section is intentionally redundant so a coding agent can implement the config layer quickly. + +| Key | Type | Default | Notes | +| ------------ | ------- | ----------- | ------------------ | +| `[key.path]` | string | `[default]` | [What it controls] | +| `[key.path]` | integer | `[default]` | [What it controls] | + +--- + +## 7. Protocol Specifications + +### [Protocol Name]: [Participant A] <> [Participant B] + +**Purpose:** [What this protocol accomplishes] + +**Compatibility note:** [What must be preserved for interoperability vs. what can vary] + +#### Launch Contract + +- Command: `[how the subprocess/service is started]` +- Working directory: [where it runs] +- Communication: [stdio, HTTP, gRPC, etc.] + +#### Startup Handshake + +[Illustrative transcript showing the exact message sequence. Equivalent payload shapes are acceptable.] + +```json +{"id":1,"method":"initialize","params":{...}} +// wait for response +{"method":"initialized","params":{}} +{"id":2,"method":"[next step]","params":{...}} +``` + +1. [Step 1 — what is sent, what to expect back, timeout] +2. [Step 2] +3. [Steady-state interaction begins] + +#### Streaming / Turn Processing + +[How the steady-state interaction works.] + +- [e.g., "Read line-delimited JSON from stdout"] +- [e.g., "Buffer partial lines until newline"] +- [e.g., "Stderr is diagnostics only, not protocol"] + +Completion conditions: + +- [e.g., "turn/completed → success"] +- [e.g., "subprocess exit → failure"] +- [e.g., "turn timeout → failure"] + +#### Timeout and Error Mapping + +| Timeout | Default | Applies To | +| -------- | ------- | ----------------------------- | +| `[name]` | [value] | [which phase of the protocol] | + +| Error Category | Cause | System Response | +| -------------- | ------------------ | ---------------------- | +| `[error_name]` | [what triggers it] | [what the system does] | + +--- + +## 8. Failure Model + +### 8.1 Failure Classes + +| # | Class | Examples | Detection | Recovery | Escalation | +| --- | ------ | ------------------ | --------------------------- | -------------------- | ---------------------------- | +| 1 | [Name] | [What triggers it] | [How the system detects it] | [Automatic recovery] | [When/how human is notified] | +| 2 | [Name] | [Triggers] | [Detection] | [Recovery] | [Escalation] | + +### 8.2 Partial State Recovery (Restart) + +[What state survives a restart and what must be reconstructed?] + +- [e.g., "No retry timers are restored from prior process memory"] +- [e.g., "Service recovers by fresh polling of active items and re-dispatching eligible work"] + +### 8.3 Operator Intervention Points + +[How operators control behavior without code changes.] + +- [e.g., "Edit config file — changes detected and re-applied automatically"] +- [e.g., "Change entity states in external system — running sessions stopped when reconciled"] +- [e.g., "Restart service — for process recovery or deployment"] + +--- + +## 9. Security and Operational Safety + +### 9.1 Trust Boundaries + +[What is trusted and what is not. Be explicit.] + +- [e.g., "The config file is fully trusted configuration"] +- [e.g., "External tracker data is not assumed trustworthy"] + +### 9.2 Filesystem Safety + +[Mandatory filesystem invariants.] + +- [e.g., "Working paths must remain under configured root"] +- [e.g., "Directory names must use sanitized identifiers"] + +### 9.3 Secret Handling + +- [e.g., "Support $VAR indirection in config"] +- [e.g., "Do not log API tokens or secret values"] +- [e.g., "Validate secret presence without printing them"] + +### 9.4 Script/Hook Safety + +[If the system executes user-provided scripts or hooks:] + +- [e.g., "Hooks are fully trusted configuration"] +- [e.g., "Hook output should be truncated in logs"] +- [e.g., "Hook timeouts are required to avoid hanging the system"] + +--- + +## 10. Observability + +### 10.1 Logging Conventions + +Required context fields: + +- [e.g., `entity_id`, `session_id`] + +Message format: + +- [e.g., "Stable key=value phrasing"] +- [e.g., "Include action outcome: completed, failed, retrying"] +- [e.g., "Avoid logging large raw payloads"] + +### 10.2 Runtime Snapshot + +[If the system exposes a monitoring interface, define the snapshot shape.] + +```json +{ + "running": [], + "retrying": [], + "totals": { "input_tokens": 0, "output_tokens": 0, "seconds_running": 0 } +} +``` + +### 10.3 Optional HTTP API + +[If applicable — endpoints, response shapes, error envelopes.] + +--- + +## 11. Operational Boundaries + +> **Important boundary:** [System-level boundary that shapes all design decisions] + +> **Important constraint:** [Hard limit — resource, scaling, or architectural] + +### Resource Limits and Backpressure + +- [e.g., "Maximum N concurrent workers"] +- [e.g., "Backoff formula: min(10000 * 2^(attempt-1), max_backoff_ms)"] + +### Scaling Constraints + +- [e.g., "Single-process, in-memory state — no distributed coordination"] +- [e.g., "Horizontal scaling requires partitioning by project"] diff --git a/skills/product-context/validate/SKILL.md b/skills/product-context/validate/SKILL.md index 8c2bc4a..f59c045 100644 --- a/skills/product-context/validate/SKILL.md +++ b/skills/product-context/validate/SKILL.md @@ -23,7 +23,7 @@ Run a generator/evaluator pattern against any artifact produced by the AEP workf **Where this fits:** ``` -/aep-envision → /aep-map → /aep-validate → /aep-dispatch → /aep-design → /aep-launch → /aep-build → /aep-wrap +/aep-envision → /aep-map → /aep-model (UI-facing) → /aep-validate → /aep-dispatch → /aep-design → /aep-launch → /aep-build → /aep-wrap ▲ you are here Also usable after any phase: @@ -82,6 +82,7 @@ The skill operates in one of four modes based on the artifact type. Each mode co - `calibration.plan[].dimensions[]` must reference `product/index.yaml` `product.quality_dimensions[]` - No `opportunity` or `product` section should exist in `product-context.yaml` when split mode is active - `product/index.yaml` must have `personas`, `capabilities`, and `product` sections +- If `object-model` is a declared quality dimension: every UI-facing capability has a `product/maps//object-map.yaml`; each `stories[].object_model_refs` entry points to an existing object-map path + object id; object names are consistent with `architecture.domain_model` and `docs/glossary.md` Mode A runs **two passes** — product design quality first, then technical correctness: @@ -110,6 +111,16 @@ Score using the story mapping dimensions from `.claude/skills/aep-gen-eval/refer - **Narrative Coherence:** Read activities left-to-right by `order` — they should form a coherent user narrative: "User [activity 1], then [activity 2], then..." If it doesn't flow, the backbone needs restructuring. - **Infrastructure Ratio:** If more than 60% of Layer 0 stories have null activity, the decomposition may be too technical — consider reframing stories around user capabilities. +**Pass 1 Object Map checks** (if any `product/maps/*/object-map.yaml` exists): + +- **CTA Coverage:** Every UI story's verbs map to a CTA on some object in its capability's object-map `coverage`. An uncovered UI story means a missing object or a hidden task-flow. +- **Object Home:** Every object in `primary_objects` has at least one `screens[]` entry (`collection` or `detail`). A primary object with no home screen is unreachable. +- **Anchor Present:** Each capability object-map declares a `navigation.anchor_object`. +- **Task-Flow Justified:** Every `interaction_modes` entry with `mode: task_oriented` has a non-empty `reason` (object-first is the default; deviations must be justified). +- **Approval State:** No UI-facing story is dispatch-ready while its capability object-map is `status: draft` or `stale` — it must be `approved` first (run `/aep-model`). +- **Ref Resolution:** Each `stories[].object_model_refs` entry (`#`) resolves — the path exists and `` appears in that map's `primary_objects`/`supporting_objects` and in `object-model.yaml` `objects[]`. +- **Noun Coverage:** Every noun foraged from `product.activities` maps to an object or is justified as implementation-only. + #### Pass 2: Technical Validation ("Can we build it correctly?") **Agents:** Generator + Evaluator + Protocol Checker