scope-architect · diff
git:20260911.2291e00 to git:20260911.073a17f
1 added, 1 removed. Audit A to A.
---
name: scope-architect
description: "Use this skill to map the vertical scopes of a feature — Shape Up's \"map the scopes\" (step 8) as committed, mechanically enforceable contracts. Triggers on: \"map the scopes\", \"write the scope contracts\", \"scope contract\", \"the discovered tasks don't fit any scope\", \"re-slice the substrate\" (operations map-scopes). Writes the committed scopes/*.md contracts by import-graph slicing along business flow, with write-whitelist substrates and e2e fixtures. NOT for decomposing a pitch into tasks (ba-pitch-analyzer) or cutting scope at ship time (scope-hammer)."
---
# Scope Architect (pure worker v1.0)
**Slice by flow, never by directory — and write it as a contract a hook can enforce.**
Groups a feature's tasks into independent, vertically-sliced **scopes** and writes each as a
committed contract (`shapeup/<slug>/scopes/<scope-id>.md`) the rest of the
harness enforces mechanically: the sandbox hook denies writes outside a substrate, t0-verify
runs the fixtures, the evaluator asserts only against the affordance manifest. This skill is
the **sole writer** of scope contracts — a distinct authority from the planner (task
decomposition) and a distinct failure mode (directory-thinking, PA1) deserving its own
the ship report's census table.
## Input contract — the WorkOrder
| Field | What it is |
|---|---|
| `operation` | `map-scopes` — the only operation this skill has. It covers first slicing after the board exists, folding discovered items in, and re-slicing a stuck scope; the payload says which of those you are doing |
| `payload.feature` / `payload.spec_folder` | Slug + committed spec (read ux-behavior.md for manifests; usecases for flows) |
- | `payload.breadboard` | When present, every U# the spec places is one manifest entry's `source`; record which scopes deliver each V# slice in `scope-summary.md` |
+ | `payload.breadboard` | When present, every U# the spec places is one manifest entry's `source`; record which scopes deliver each V# slice in `scope-board.md` (your write surface — `scope-summary.md` is the planner's) |
| `payload.tasks[]` | The board's tasks with their touched files — the slicing INPUT only. Each carries `use_case_refs`; those UC ids are what you write into the contract. Never copy a task id into a contract |
| `substrate.allowed` | `scopes/*.md` + `scope-board.md` — your ONLY write surface |
## Core process
```
1 SLICE build an import/business-flow graph over the tasks' touched files (grep heuristic
is fine; AST is an optimization). One scope = one call chain: the UI screen + the
API route + the use case + the repository it drives. Scopes aligning 1:1 with a
top-level directory (all-frontend, all-backend) FAIL — that is layer-thinking.
2 CLASSIFY topology_type: LAYER_CAKE (thin balanced UI+backend) | ICEBERG (complexity on one
side) | CHOWDER (true strays with no shared flow — the one deliberate exception)
3 CONTRACT per scope, write scopes/<scope-id>.md — MARKDOWN (ADR-0001): frontmatter for
scalars and [a, b] lists, a `## Affordances` table for affordance_manifest, and a
short `## Why this slice` paragraph. A reviewer must be able to read the substrate
in a PR; regeneration preserves prose under headings you do not own.
scope_id, topology_type — the stable join key is the scope
use_cases[] — the UC ids this scope implements.
THE ONLY LINK YOU WRITE TO THE
WORK: never task ids. The contract
is committed and the board is not,
so a TASK-NNN here dangles on
every other clone (spec-lint
TIER-DIRECTION reds it). The
scope's tasks are re-derived from
the board's own use_case_refs
covers[] — optional REQ-ids from
requirements.md this scope answers
for; stable, never renumbered
depends_on[] — scope_ids this scope builds AFTER.
This is the build ORDER — declare
it whenever one scope consumes
another's output, or the two race
allowed_file_substrate[] — exact globs; the sandbox hook's
write-whitelist; wrong here =
a legitimate ESCALATE later
shared_substrate[] — files ≥2 scopes both touch;
every write there forces a full
seesaw run at the next gate
affordance_manifest — from ux-behavior.md state
tables: every interactive
element as {test_id, role} +
required_states [idle, loading,
success, error, empty]
+ `source` — the U# the
ux-behavior row cites
e2e_verification_fixtures[] — the command(s)/spec file(s)
that drive this scope
end-to-end (T0 layer); too
speculative to fixture → mark
TBD and flag it, never invent
a fixture for unbuilt behavior
hill_phase: "UPHILL_UNKNOWN" — ALWAYS; phase is derived from
T0/T1/seesaw facts later,
never authored
4 LINT node "${CLAUDE_PLUGIN_ROOT}/kernel/harness.mjs" verify spec --slug <slug>
→ PA1 (directory alignment), PA2 (>~15 files), DISJOINT (undeclared overlap),
SCOPE-ANCHOR (empty/unresolvable use_cases), TIER-DIRECTION (a task id in a
committed contract), SCOPE-DEPS (depends_on naming a scope that isn't here).
Fix reds by re-slicing, not by silencing.
5 BOARD regenerate scope-board.md — a VIEW of the contracts, nothing more:
| scope_id | topology | use_cases | depends_on | files | lint |
Every column restates a field the contract already declares, so the board can be thrown
away and rebuilt. Do NOT add a `wave` column: waves are Kahn levels of `depends_on` and
`probe resume` derives them at dispatch — a hand-written copy of a derived value drifts,
which is exactly why `unlocks` stopped being authored. The BUILD ORDER lives in each
contract's `depends_on`; the board only shows it.
A `TASK-` id anywhere in a contract or the board — a column, a cell, or a sentence in
the prose — is spec-lint TIER-DIRECTION red. The board is committed; ids are not.
```
**Folding in a discovered item:** it joins the nearest scope only if the flow matches (extend that
substrate minimally); otherwise propose a NEW scope — never silently widen an existing one.
**Re-slicing a stuck scope:** re-run step 1 on just that scope's task+file set → N new contracts;
mark the old one `superseded_by: [ids]` — never delete (branch and T0 history stay attributable).
## Anti-rationalization table
| Excuse | Reality |
|---|---|
| "The discovered item obviously fits scope A" | Run the flow match. 'Obviously' is how substrates silently widen. |
| "One scope per directory is cleaner" | That's PA1 — a layer, not a flow. A scope must ship something a user can do. |
| "I'll widen the substrate a little so the doer stops escalating" | A wide substrate is no substrate. Split or add a shared_substrate entry, deliberately. |
| "This scope looks downhill, I'll set the phase" | hill_phase is UPHILL_UNKNOWN at write, always. Facts move dots, not authors. |
| "The old contract is superseded, delete it" | supersede-never-delete. History must stay attributable. |
| "Both scopes implement that UC, the tasks will sort themselves out" | They will not — both scopes get every task of that UC and three of four writes get denied. Give each scope its own use cases, or say so in deviations[] so the board can be stamped. |
| "I'll list the task ids so the contract says what it builds" | The board is gitignored and renumbers per machine; the contract is committed. Cite the UCs — the tasks are re-derived from them. |
| "Build order is obvious from the slice, I'll leave depends_on empty" | Nothing infers it any more. An undeclared edge means the two scopes are released into the same wave and race. |
| "Fixtures can come later, leave the field empty" | Fixture at contract time or an explicit TBD flag — silence is how T0 goes blind. |
## Output contract — the WorkResult
**Escalation rule.** If you return `status: "escalated"`, the **first** entry in `deviations[]`
must be the blocker: one specific, answerable question plus the context needed to answer it.
Nothing else in the envelope carries it — there is no `escalates[]` field — so a vague entry, or
the question buried under other notes, reaches the human as "something went wrong" and costs a
round. Write it so someone without your context can answer it in one reply.
`scopes/*.md` + `scope-board.md` in your substrate, then
`.shapeup/<slug>/results/<order-suffix>.json`: `status`, `artifacts[]` (the contracts
written/superseded), `deviations[]` (e.g. a discovered item implying a new UC — the planner's
territory — and any lint warn left standing, with why). You never touch task files,
`tasks/_index.md`, spec docs, or run-state.
## Verification checklist
- [ ] Every scope crosses layers or is declared CHOWDER; spec-lint PA1 = 0 red
- [ ] Every scope names ≥1 `use_cases` that resolves on disk, and NO contract carries a task id
- [ ] Every scope that consumes another's output declares it in `depends_on`
- [ ] Substrates disjoint except declared shared_substrate (DISJOINT = 0 red)
- [ ] Every interactive element in scope screens appears in exactly one affordance_manifest
- [ ] Every U# the spec places is some manifest entry's `source`
- [ ] Every scope has fixtures or an explicit TBD flag
- [ ] Every hill_phase written is UPHILL_UNKNOWN; superseded contracts kept
- [ ] The WorkResult validates against `work-result.schema.json`
## Invocation
```bash
# Orchestrated — compile-order --operation map-scopes --worker scope-architect …
/scope-architect --order .shapeup/checkout-vnpay/orders/map-scopes.json
# Standalone shims (compile the same envelope)
/scope-architect --map shapeup/checkout-vnpay/
/scope-architect --map --split cart-creation shapeup/checkout-vnpay/ # re-slice one scope
```