Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-2358Done

`ai:plan` (3/4) — generation, sprint planning, the pre-plan read and the AI-access probe

The third ai:plan card: the jobs that build a plan from nothing, and the two reads that sit beside them.

OperationService methodToday
POST /api/ai/plan/generateaiGenerationService.startGenerationnothing
POST /api/ai/plan/sprintaiSprintPlanningService.submitSprintPlannothing
POST /api/ai/plan/sprint/[jobId]/reviewaiSprintPlanningService.reviewSprintPlannothing
POST /api/ai/plan/sprint/approveaiSprintPlanningService.approveSprintPlanreaches a gate indirectly
GET/PATCH /api/ai/pre-planaiPreplanService.getPreplanState, saveDesignChoicenothing
GET /api/ai/accessbillingService.getAiAccessForContextnothing

Generation is the heaviest planning job the product runs, and both it and sprint planning reach no gate at all today.

The two that need a decision rather than a copy

  • /api/ai/pre-plan has a READ and a WRITE on the same route. The write (saveDesignChoice) is a planning act → ai:plan. The read returns the project's pre-plan tier documents, which is closer to browsing than to spending — gate the read on project:browse and say so in the row, rather than making the whole route one key because it is one file.
  • /api/ai/access answers is AI available here, and is there credit — it is the probe the UI uses to decide whether to offer a planning affordance at all. Gating it on ai:plan means an actor who may not plan cannot even discover that planning exists; gating it on project:browse means the answer is readable and the ACT is still refused. Take project:browse, and record why in the inventory row: a capability probe that refuses to answer teaches the client nothing and pushes the discovery into a failed write.

Approving a sprint plan creates real work items. approveSprintPlan is the one write here whose output is durable project data, so its gate must be asserted before anything is materialized, not after the plan is read back.

Acceptance criteria

  • startGeneration, submitSprintPlan, reviewSprintPlan, approveSprintPlan and saveDesignChoice each assert assertPermission(projectId, ctx, 'ai:plan') before submitting or writing.
  • getPreplanState and getAiAccessForContext each assert project:browse, and both inventory rows are corrected from ai:plan with the reason written in.
  • A test proves approveSprintPlan refuses a viewer BEFORE any work item is created — assert on the absence of the rows, not only on the thrown error.
  • A test proves a workspace member with no project membership cannot generate a plan on an open project.
  • ai:plan does NOT flip to enforcement: 'enforced' here — the close-out card owns the flip.
  • The inventory rows record what landed, including the two corrections.
  • The guard's pinned counts are re-derived by running it on this branch.
  • pnpm test tests/permissions/ tests/services/ is green.

Context refs

  • lib/services/aiGenerationService.ts, lib/services/aiSprintPlanningService.ts, lib/services/aiPreplanService.ts — none holds an assertion today.
  • lib/services/billingService.tsgetAiAccessForContext, and whatever scope it already resolves.
  • app/api/ai/pre-plan/route.ts — the read and the write that need different keys.
  • docs/decisions/member-facing-permissions.md — the role assignment for ai:plan.
  • The role-set seam — the prerequisite.
  • ai:plan (1/4) · ai:plan (2/4) — the sibling cards using the same gate shape.