# planchi MCP — reference

This file describes **what** the planchi MCP server can do. Use it as a readable catalog or attach it in Cursor, Claude or ChatGPT.

There is **no OpenAPI** for MCP. After connecting, the live contract is `tools/list` (JSON Schema 2020-12) plus `resources/list` and `prompts/list`. Parameters, required fields and enums live there — not in this file. Visible tools follow the **token's permissions** (role + overrides, personal vs organization).

- **Endpoint:** `https://<your-domain>/mcp` (full catalog). Copilot Studio: `?catalog=copilot` or `/mcp/copilot` (70 planning tools, no timer/booking).
- **Auth:** Bearer token (`plr_…`), planchi OAuth (`pla_…`), or Entra JWT (M365 Copilot) — see [Clients and authentication](#clients-and-authentication)
- **Admin UI:** `/einstellungen/mcp` (after sign-in)
- **In-app short guide:** `/self-host/ki`

Generated from server tool names and short descriptions. If this file disagrees with `tools/list`, trust `tools/list`.

## Contents

1. [Clients and authentication](#clients-and-authentication)
2. [What MCP is](#what-mcp-is)
3. [What MCP does not do](#what-mcp-does-not-do)
4. [Ground rules](#ground-rules)
5. [Capabilities by domain](#capabilities-by-domain)
6. [Guides (resources)](#guides-resources)
7. [Prompts](#prompts)
8. [Appendix: all tools](#appendix-all-tools)

## Clients and authentication

Transport is **Streamable HTTP** on `/mcp`. Microsoft 365 Copilot / Copilot Studio in the Microsoft cloud must reach that URL **from the internet over HTTPS** — that is the operator's job (reverse proxy, firewall). Without a public HTTPS URL there is no Copilot, regardless of application code.

### Cursor, Claude, VS Code

Personal or organization **bearer token** (`plr_…`) from `/einstellungen/mcp`. Unchanged.

### ChatGPT and Copilot Studio (planchi OAuth)

planchi is the OAuth authorization server (PKCE S256, refresh). ChatGPT and Copilot Studio use **dynamic discovery** against:

- `/.well-known/oauth-protected-resource` (and the `/mcp` variant)
- `/.well-known/oauth-authorization-server`
- `/oauth/register` (DCR), `/oauth/authorize`, `/oauth/token`

New DCR clients receive a **client secret** once (`token_endpoint_auth_method=client_secret_post`). ChatGPT via client ID metadata (CIMD) stays public/PKCE without a secret.

Built-in redirect hosts (not a blanket `microsoft.com`): `chatgpt.com`, `openai.com`, `claude.ai` / `claude.com`, `teams.microsoft.com`, `powerapps.com`, `dynamics.com`, `cloud.microsoft`, `copilot.microsoft.com`, `copilotstudio.microsoft.com`, `azure-apim.net`. Extra suffixes: `McpOAuth__ExtraAllowedRedirectHostSuffixes__0` in Compose.

CORS applies **only** to `/mcp`, the OAuth well-known URLs, and `/oauth/register|token|revoke` (not the whole app). Defaults: `https://apps.powerapps.com`, `https://make.powerapps.com`, `https://teams.microsoft.com`, `https://copilotstudio.microsoft.com`. Extra origins: `McpOAuth__CorsOrigins__0=https://…`.

Copilot Studio: wizard **OAuth 2.0 → Dynamic discovery**, MCP URL `https://<host>/mcp?catalog=copilot` (70 planning tools, reports yes, timer/booking no). Alternatives: `planning`, `time`. DCR redirect is `https://global.consent.azure-apim.net/redirect/…` (not only Teams). Keep Cursor/Claude on `/mcp`.

### M365 Copilot / Entra SSO (JWT)

Alternative: Copilot obtains an **Entra access token** for the planchi API audience and sends it as Bearer to `/mcp`. planchi validates signature/`iss`/`tid`/`aud` and maps `oid` to the same user as web login (**no** account creation via MCP). Organization = last org chosen in the UI (`CurrentOrganizationId`). Personal workspace is not available on this path.

Requirements: Entra login as in [08-entra.md](08-entra.md), plus **MCP API audience** under `/admin/system` → Directory, and a prior Microsoft sign-in to planchi. Extra audiences: `McpOAuth__EntraAudiences__0`.

### Audit

Tool calls show up in Seq as `MCP tool {name} user=… org=… result=ok|denied|error` — no request bodies.

## What MCP is

MCP (Model Context Protocol) is the structured interface between planchi and an AI client (Cursor, VS Code, Claude Code, ChatGPT, Copilot Studio / M365 Copilot, …). The language model runs on the client or the AI provider, **not** on the planchi host.

The agent **reads and writes planning data** through named tools: projects, specification, milestones, tasks, time, meetings, wiki, change requests, budget, risks, stakeholders. planchi remains the source of truth — not the chat, not local TODOs, not commit messages.

Typical jobs:

- Structure a specification and slice it into milestones
- Create tasks from scope, set dependencies, schedule by capacity
- Document progress (comments, code/acceptance status, implementation texts)
- Track time, turn meetings into tasks, maintain the wiki
- Draft change requests and materialize them after customer approval
- Read or maintain budget, earned value, risks and stakeholders
- Create customer-feedback rounds, triage notes, read coverage, create a task from a note

## What MCP does not do

| Topic | Why |
|---|---|
| Upload a signed approval PDF | Customer sign-off stays in the app (milestone acceptance, change-request approval). `sign_off_milestone` is deprecated. |
| Operate the Microsoft Entra portal | Tenant, app registration and groups are set up by the operator in Entra. |
| Read secrets | No tokens, `.env`, license files or passwords via MCP. |
| Record personnel as procurement | Actual personnel cost comes from time × hourly rate. Category Personnel is rejected on `log_project_cost`. |
| Copy formal requirements into the wiki | Wiki = living knowledge; shall-statements belong in the specification. |
| Full CPM / auto-reschedule every edge | Dependencies do not reschedule by themselves unless `rescheduleFromDependencies=true` or you call `schedule_tasks` afterwards. |
| Invite guests or write as the customer on `/f/{token}` | Invites and the customer portal stay in the app. MCP is the team API. |
| Upload feedback images | Paste/file only in the UI — same as wiki/spec media. |

## Ground rules

- **Never invent IDs.** Always `list_*` / `get_*` — guessing GUIDs fails or hits the wrong object.
- **Dates:** `YYYY-MM-DD`, timestamps ISO-8601.
- **Context:** Personal token = own time and tasks. Organization token = team planning, specification, milestones, change requests. «Access denied» usually means the wrong context.
- **A requirement/criterion belongs to at most one milestone scope** per project. Call `get_scope_coverage` before `set_milestone_scope`.
- **A task is done** only when `codeStatus=Complete` **and** `acceptanceStatus=Accepted` (`OP-*` ignores acceptance). Prefer `update_task` with both fields; `complete_task` alone fails if status is missing.
- **Comment** when planning data changes (`add_task_comment`).
- **Batch, don't loop:** `create_tasks`, `delete_tasks`, `set_milestone_criteria_tasks`, `add_task_dependencies` — do not call `delete_task` 80 times.
- Write **implementation texts** on REQ/KRIT when every linked task is done — otherwise milestone review is blocked.

## Capabilities by domain

Each block: what is possible, typical flow, limits. Tool names in backticks.

### Projects and team

**Can:** Create projects and change settings (name, color, hour budget, hourly rate CHF, schedule dates, repository URL, contingency, management reserve, cost cap). List methodology packs and apply them to a project that **has no milestones yet** (e.g. HermesLite: phases, deliverables, gates). List organization members with `userId` and weekly capacity.

**Typical flow:** `list_projects` → keep `projectId` → optional `list_methodology_packs` / `apply_methodology_pack` → `list_members` for assignees.

**Note:** `update_project` only changes provided fields (partial merge). `costCap` does not grow with change requests; contingency is inside the cost baseline (BAC), management reserve stays outside.

**Tools:** `list_projects`, `create_project`, `update_project`, `list_methodology_packs`, `apply_methodology_pack`, `list_members`

### Specification (Pflichtenheft)

**Can:** Create and structure specifications (sections, requirements REQ, acceptance criteria KRIT). Extract PDF to Markdown (`extract_pdf_to_markdown`, `get_extracted_pdf_image` as needed), then `import_specification_markdown` — **no automatic REQ detection**. Set implementation texts on REQ/KRIT.

**Typical flow (PDF):** `extract_pdf_to_markdown` → images if needed → shape Markdown → `import_specification_markdown` → `get_specification`.

**Not:** Specification release via signed PDF (stays in the app). Large PDFs are often better via REST `POST /api/pdf-extracts`.

**Tools:** `list_specifications`, `get_specification`, `create_specification`, `update_specification`, `delete_specification`, `add_specification_section`, `update_specification_section`, `reorder_specification_section`, `delete_specification_section`, `add_specification_requirement`, `update_specification_requirement`, `set_requirement_implementation`, `delete_specification_requirement`, `add_specification_criterion`, `update_specification_criterion`, `set_criterion_implementation`, `delete_specification_criterion`, `extract_pdf_to_markdown`, `get_extracted_pdf_image`, `import_specification_markdown`

### Milestones and acceptance

**Can:** Create milestones, set scope from the specification, sync criteria from scope, link criteria to tasks granularly, find implementation gaps, start review, phase gates, deliverables, lock/unlock.

**Typical flow (spec → plan):** `get_scope_coverage` (plan the split: each REQ in **one** milestone only) → `create_milestone` per phase → `set_milestone_scope` → `sync_milestone_criteria_from_scope` → quick: `create_tasks_from_milestone_scope` (1 task per REQ) **or** granular `create_tasks` + `add_task_dependencies` → `set_milestone_criteria_tasks` → before review `set_implementation_texts` until `get_milestone_scope_implementation_gaps` is empty → `validate_project_setup` (early `phase=planning`, default before acceptance) → `start_milestone_review` → user uploads the sign-off PDF in the app → `lock_milestone`.

**Prompt:** `milestone_acceptance_walkthrough` / guide `plan-resuah://guides/milestone-acceptance`.

**Not:** Sign-off PDF via MCP. `sign_off_milestone` is deprecated.

**Tools:** `list_milestones`, `get_milestone`, `create_milestone`, `update_milestone`, `delete_milestone`, `add_milestone_criterion`, `update_milestone_criterion`, `delete_milestone_criterion`, `set_milestone_criterion_tasks`, `set_milestone_criteria_tasks`, `set_milestone_scope`, `sync_milestone_criteria_from_scope`, `create_tasks_from_milestone_scope`, `replace_milestone_tasks`, `get_scope_coverage`, `validate_project_setup`, `get_milestone_scope_implementation_gaps`, `set_implementation_texts`, `set_task_requirements`, `start_milestone_review`, `pass_milestone_phase_gate`, `revoke_milestone_phase_gate`, `create_milestone_deliverable`, `update_milestone_deliverable`, `delete_milestone_deliverable`, `set_milestone_deliverable_status`, `set_milestone_deliverable_link`, `sign_off_milestone`, `lock_milestone`, `unlock_milestone`

### Tasks, Gantt, Kanban

**Can:** List tasks (lean, filters) and get detail; create one or in batch (max 200, optional `requirementIds` + `idempotencyKey`); change status, assignee, estimate, dates; bulk update; schedule; add/remove dependencies; boards/columns; move; comment; delete (prefer batch).

**Done:** `codeStatus=Complete` and `acceptanceStatus=Accepted` — the card then moves to Done. `move_task` to Done requires both. `OP-*` ignores acceptance.

**Dependencies:** Edges do not auto-reschedule unless `rescheduleFromDependencies=true` or you call `schedule_tasks` afterwards. No full CPM.

**Soft-reset:** `delete_tasks` or `replace_milestone_tasks` — do not loop `delete_task`.

**Tools:** `list_tasks`, `get_task`, `create_task`, `create_tasks`, `update_task`, `bulk_update_tasks`, `schedule_task`, `schedule_tasks`, `add_task_dependency`, `add_task_dependencies`, `remove_task_dependency`, `remove_task_dependencies`, `list_boards`, `move_task`, `complete_task`, `delete_task`, `delete_tasks`, `list_task_comments`, `add_task_comment`

### Time tracking

**Can:** Read the running timer, start, switch project/task, stop; log time; edit/delete entries; timesheet and weekly report.

**Note:** Personal context tracks your own time. Organization context can see team entries in reports, depending on rights.

**Tools:** `get_current_time_status`, `start_timer`, `switch_timer`, `stop_timer`, `log_time`, `update_time_entry`, `delete_time_entry`, `get_timesheet`, `get_weekly_report`

### Meetings

**Can:** Create and maintain meetings, set participants, record decisions and action items, list open actions, create a task from an action (`create_task_from_meeting_action`).

**Tools:** `list_meetings`, `get_meeting`, `create_meeting`, `update_meeting`, `delete_meeting`, `set_meeting_participants`, `add_meeting_decision`, `add_meeting_action_item`, `create_task_from_meeting_action`, `list_open_meeting_action_items`

### Customer feedback

**Can:** Create rounds (`create_feedback_link` returns `portalUrl` `/f/{token}`), list/read notes, triage, reply, REQ/KRIT coverage, create a task from a note. «Looks correct» (`accept_feedback_scope`) only after asking the user. Guide: `plan-resuah://guides/feedback`.

**Not:** Email invite, assign reviewers, write as a guest, upload images. Not a change request — scope changes go through change requests.

**Tools:** `list_feedback_links`, `create_feedback_link`, `revoke_feedback_link`, `list_feedback`, `get_feedback`, `get_feedback_coverage`, `create_feedback`, `update_feedback`, `add_feedback_reply`, `create_task_from_feedback`, `accept_feedback_scope`, `revoke_feedback_scope`

### Wiki

**Can:** Read curated project context (`get_project_wiki_context` — call **before** estimating/planning), list/get/create/update/delete pages. `isProjectContext` marks AI home pages.

**Not:** Formal requirements — those belong in the specification. Guide: `plan-resuah://guides/wiki`.

**Tools:** `get_project_wiki_context`, `list_wiki_pages`, `get_wiki_page`, `create_wiki_page`, `update_wiki_page`, `delete_wiki_page`

### Change requests (Nachträge)

**Can:** Draft change requests (at least one requirement), set cost/funding (`estimatedCost`, `fundingSource`), submit (`submit_change_request` → PendingApproval), materialize once status is **Approved** (writes into the specification).

**Typical flow:** `create_change_request` + `add_change_request_item` → `submit_change_request` → **stop:** user exports PDF, customer signs, upload in the app «Freigabe-PDF hochladen» → only then `materialize_change_request`.

**Funding:** `AdditionalBudget` = customer pays, performance budget grows (do not also create a budget line). `Contingency` / `ManagementReserve` = draw a reserve. Guides: `plan-resuah://guides/change-requests` and `plan-resuah://guides/budget`.

**Prompt:** `change_request_walkthrough`.

**Tools:** `list_change_requests`, `get_change_request`, `create_change_request`, `update_change_request`, `delete_change_request`, `add_change_request_item`, `update_change_request_item`, `delete_change_request_item`, `submit_change_request`, `materialize_change_request`

### Cost and budget

**Can:** Read the money budget (plan vs actual per category, reserves, cost cap, additional budget from change requests). Create a new budget line (MCP cannot edit an existing line: delete + recreate). Record external cost/procurement (status drives whether it counts as actual).

**Actual personnel** always from time × hourly rate — category Personnel is rejected on `log_project_cost` (book external people as ExternalService).

Project settings (rate, cost cap, reserves, hour budget): `update_project`. Earned value: `get_earned_value`. Guide: `plan-resuah://guides/budget`.

**Tools:** `get_project_budget`, `set_project_budget_line`, `delete_project_budget_line`, `log_project_cost`, `delete_project_cost`

### Risks

**Can:** List, create, update, delete the risk register (P×I). Identification first: title + probability + impact is enough; mitigation optional until status Mitigating/Accepted.

Unmanaged high risks (`Open` and score ≥ 15) make the cockpit critical. That is **not** the same as blockers (`get_blockers_dashboard`) or contingency budget. Guide: `plan-resuah://guides/risks`.

**Tools:** `list_risks`, `create_risk`, `update_risk`, `delete_risk`

### Stakeholders

**Can:** List, create, update, delete the stakeholder register (not org members) — Power/Interest, not the member list from `list_members`.

**Tools:** `list_stakeholders`, `create_stakeholder`, `update_stakeholder`, `delete_stakeholder`

### Capacity and reports

**Can:** Per-person weekly availability (`get_resource_availability`, before scheduling), absences, Gantt, blocker dashboard, code-vs-acceptance matrix, effort plan/actual, schedule variance, earned value.

**Sprint from capacity:** `list_members` + `get_resource_availability` → `get_gantt` / `list_tasks` → `get_blockers_dashboard` → `schedule_task` / `schedule_tasks`. Prompt: `plan_sprint_from_capacity`. Guide: `plan-resuah://guides/planning`.

**Tools:** `get_resource_availability`, `add_absence`, `delete_absence`, `get_gantt`, `get_blockers_dashboard`, `get_acceptance_matrix`, `get_effort_report`, `get_schedule_variance`, `get_earned_value`

## Guides (resources)

Load via `resources/read` after connect (URI exact). Markdown guides for agents:

| URI | Content |
|---|---|
| `plan-resuah://guides/index` | Index of all guides and prompts |
| `plan-resuah://guides/overview` | Domain model |
| `plan-resuah://guides/quickstart` | First steps |
| `plan-resuah://guides/specification` | Specification, import, media, release |
| `plan-resuah://guides/milestone-acceptance` | Scope, criteria, review, lock |
| `plan-resuah://guides/planning` | Capacity, Gantt, blockers |
| `plan-resuah://guides/wiki` | Project wiki |
| `plan-resuah://guides/methodology-packs` | Packs, deliverables, phase gates |
| `plan-resuah://guides/conventions` | IDs, dates, anti-patterns |
| `plan-resuah://guides/change-requests` | Change requests including cost/funding |
| `plan-resuah://guides/budget` | Money budget, reserves, cost cap, EVM |
| `plan-resuah://guides/risks` | Risk register P×I |
| `plan-resuah://guides/feedback` | Customer feedback: rounds, notes, coverage |


## Prompts

Reusable walkthroughs (`prompts/get`). Optional parameters such as project name.

| Name | Flow |
|---|---|
| `milestone_acceptance_walkthrough` | Scope on milestone, criteria, tasks, implementation, review; sign-off in the app |
| `change_request_walkthrough` | Draft change request, submit, wait for PDF approval, materialize |
| `setup_project_from_spec` | Build milestones, scope and tasks from the specification |
| `plan_sprint_from_capacity` | Schedule open tasks into the Gantt from team availability |


## Appendix: all tools

Names as in `tools/list`. Short descriptions as returned by the server. **Parameters** only via the JSON Schema on the server.


### Projects and team

#### `list_projects`

Lists all projects in the current organization context with id, name, color, and budget settings (hour budget, hourly rate, cost cap, reserves). Use the project id for other tools; use update_project to change settings.

#### `create_project`

Creates a new project in the current context (organization or personal). Optionally seeds a methodology pack (None, HermesLite). Returns the project with its id.

#### `update_project`

Updates project settings. Only provided fields change (same merge pattern as update_milestone). Use for name/color/description, hour budget, hourly rate (CHF), schedule dates (YYYY-MM-DD), repository URL, contingency, management reserve, and cost cap. costCap is the agreed hard ceiling and does not grow with change requests; contingency is inside the cost baseline (BAC), management reserve stays outside. Returns the updated project including the money fields. See plan-resuah://guides/budget.

#### `list_methodology_packs`

Lists available methodology packs (None, HermesLite, …) that can seed milestones and deliverables.

#### `apply_methodology_pack`

Applies a methodology pack to a project that has no milestones yet. Seeds phases, deliverables, and phase gates.

#### `list_members`

Lists members of the current organization (userId, name, role, weekly capacity). userId is used as assignee for tasks. Organization context only.

### Specification (Pflichtenheft)

#### `list_specifications`

Lists the specifications (Pflichtenhefte) of a project incl. section and requirement counts. Use the id with get_specification.

#### `get_specification`

Returns a full specification incl. sections (with optional parentSectionId), requirements (code/text/priority), acceptance criteria, and signed release documents (acceptanceDocuments: active PDF cannot be deleted; revoke with reason reverts status to Draft).

#### `create_specification`

Creates an empty specification (Pflichtenheft) for a project. Add sections/requirements afterwards, or use import_specification_markdown to create everything at once. documentDate YYYY-MM-DD.

#### `update_specification`

Updates specification metadata. Only provided fields change. status Draft or Archived only — Released requires uploading a signed PDF via the app (POST /specifications/{id}/acceptance-document); use revoke endpoint to revert. documentDate YYYY-MM-DD.

#### `delete_specification`

Permanently deletes a specification incl. all sections, requirements and acceptance criteria. Fails if an active signed release PDF exists — revoke it first.

#### `add_specification_section`

Adds a section to a specification. number is free text (e.g. '3.1'). body is Markdown (PlantUML, spec-media images, :::pagebreak — plan-resuah://guides/specification). parentSectionId for a sub-section (2-level grouping). Returns the full specification.

#### `update_specification_section`

Updates a section. title is required; number/body/parentSectionId are set as given (omit to clear). body: Markdown with PlantUML/spec-media/:::pagebreak (plan-resuah://guides/specification). Returns the full specification.

#### `reorder_specification_section`

Moves a section to a new zero-based position within its specification. Returns the full specification.

#### `delete_specification_section`

Deletes a section incl. its requirements and acceptance criteria. Fails if the section has sub-sections. Returns the full specification.

#### `add_specification_requirement`

Adds a requirement to a section. code is an optional identifier (e.g. 'K-01'). priority one of Must/Should/Could. Returns the full specification.

#### `update_specification_requirement`

Updates a requirement (text, code, priority, optional global implementationText for PDF Umsetzung). priority one of Must/Should/Could. Returns the full specification.

#### `set_requirement_implementation`

Sets the implementationText (Umsetzung) for ONE requirement. For several requirements/criteria at once use set_implementation_texts instead. Returns a compact confirmation of the updated requirement.

#### `delete_specification_requirement`

Deletes a requirement. Returns the full specification.

#### `add_specification_criterion`

Adds an acceptance criterion to a section. Returns the full specification.

#### `update_specification_criterion`

Updates an acceptance criterion (text and optional global implementationText for PDF Nachweis). Returns the full specification.

#### `set_criterion_implementation`

Sets the implementationText (Nachweis) for ONE acceptance criterion (Abnahmekriterium). For several criteria/requirements at once use set_implementation_texts instead. Returns a compact confirmation of the updated criterion.

#### `delete_specification_criterion`

Deletes an acceptance criterion. Returns the full specification.

#### `extract_pdf_to_markdown`

Extracts layout-oriented Markdown from a PDF (headings, body text, lists, tables as far as possible). Does NOT detect REQs/acceptance criteria or create a specification — use this before import_specification_markdown. Input: pdfBase64 (max 15 MB decoded). For larger PDFs or Cursor MCP argument limits, prefer REST multipart POST /api/pdf-extracts with form field 'file' and Authorization: Bearer plr_… (Cookie or PAT; same response shape). Output: extractionId, markdown with image placeholders `![...](pdf-extract:{extractionId}/{index})`, meta (pageCount, title, toc), images[] metadata (deduplicated, no Base64), warnings (e.g. scan_suspected, table_lossy, images_deduplicated, repeated_header_images_omitted, images_omitted_over_cap). Images stored ~6 hours in shared DB (multi-instance safe); fetch with get_extracted_pdf_image. No OCR. See plan-resuah://guides/specification.

#### `get_extracted_pdf_image`

Returns one image from a prior extract_pdf_to_markdown (or REST POST /api/pdf-extracts) job as MCP image content (Base64). Use extractionId + image index from the extract response. Jobs expire after ~6 hours (shared DB, multi-instance safe). REST alternative: GET /api/pdf-extracts/{id}/images/{index} with Bearer PAT. After restructuring Markdown, upload images via REST POST /api/specifications/{id}/media (Bearer PAT) and replace pdf-extract: refs with spec-media:{guid}, then call import_specification_markdown.

#### `import_specification_markdown`

Creates a new specification from a Markdown document. Expected structure: optional '# Title' (overridden by the title argument); text before the first '##' becomes the introduction; '##' headings are top-level sections, '###' headings are sub-sections; a leading number in a heading (e.g. '3.1 Title') becomes the section number. Requirement tables are recognized by a header row with columns like 'ID'/'Code', 'Anforderung'/'Requirement', and 'Priorität'/'Priority' (values Muss/Soll/Kann or Must/Should/Could). Bullet lists after a line containing 'Abnahmekriterien'/'Acceptance criteria' become acceptance criteria. Supports PlantUML fences (```plantuml width=55%```), spec-media image refs, LaTeX page breaks (converted to :::pagebreak), and :::pagebreak — see plan-resuah://guides/specification. documentDate YYYY-MM-DD. Returns the full specification.

### Milestones and acceptance

#### `list_milestones`

Lists milestones of a project incl. acceptance status (Open/InReview/Accepted/Locked), acceptance date, review deadline, checklist, and task ids.

#### `get_milestone`

Returns a milestone with full detail: checklist criteria, linked tasks, and the specification scope. Scope items carry specificationRequirementId / specificationAcceptanceCriterionId plus current implementationText — use these ids directly with set_implementation_texts (no get_specification needed).

#### `create_milestone`

Creates a milestone. Optionally with free-text acceptance criteria (acceptanceCriteriaNotes, max 32000 chars), target date (targetDate, YYYY-MM-DD), and checklist (criterionTitles). description and acceptanceCriteriaNotes support Markdown with PlantUML (```plantuml width=55%```) and spec-media images from the project Pflichtenheft — see plan-resuah://guides/specification.

#### `update_milestone`

Updates a milestone. Dates in YYYY-MM-DD format. Only provided fields are changed. description and acceptanceCriteriaNotes: Markdown with PlantUML/spec-media images (plan-resuah://guides/specification).

#### `delete_milestone`

Deletes a milestone. Linked tasks remain and are unlinked.

#### `add_milestone_criterion`

Adds an acceptance criterion (checklist item) to a milestone.

#### `update_milestone_criterion`

Updates an acceptance criterion: rename and/or mark as fulfilled.

#### `delete_milestone_criterion`

Removes an acceptance criterion from the checklist.

#### `set_milestone_criterion_tasks`

Links 1-n tasks to an acceptance criterion. When linked, criterion fulfillment is computed from linked tasks' acceptanceStatus=Accepted (not manual checkbox). Tasks must belong to the criterion's milestone. Link GRANULARLY (ideally 1 criterion <-> 1-2 specific tasks) — linking one collective task to all criteria destroys acceptance granularity; validate_project_setup flags this. For MANY criteria use set_milestone_criteria_tasks (batch) instead of looping.

#### `set_milestone_criteria_tasks`

Batch variant of set_milestone_criterion_tasks: links tasks to MANY acceptance criteria in one call (max 200 items). Each item: criterionId + taskIds (empty clears links). Same granularity rule applies (ideally 1 criterion <-> 1-2 tasks). Items are processed independently: failures don't abort the batch, the response reports success or error per item. Criterion ids come from get_milestone; task ids from create_tasks_from_milestone_scope / list_tasks.

#### `set_milestone_scope`

Sets the specification scope for a milestone. kind Section is an input shortcut: on save it expands to Requirement + AcceptanceCriterion rows (visible in get_milestone and UI). Use individual Requirement/AcceptanceCriterion items for fine-grained splits. Replaces active scope items. RULE: each requirement/criterion can be in the scope of at most ONE milestone per project — plan the split with get_scope_coverage first. After create_milestone: deliverable § sections only (not Einleitung/Zweck). Then sync_milestone_criteria_from_scope. See plan-resuah://guides/milestone-acceptance.

#### `sync_milestone_criteria_from_scope`

Creates or updates milestone acceptance criteria from specification criteria in the milestone scope. Run after set_milestone_scope. See resource plan-resuah://guides/milestone-acceptance.

#### `create_tasks_from_milestone_scope`

Quick path: creates one task per requirement in the milestone scope (1:1) — each task gets title/description from the REQ, milestone assignment, and REQ link. Already-linked requirements are skipped (idempotent). For granular execution plans (several tasks per REQ, rollouts, step breakdowns) use create_tasks with requirementIds instead — do NOT delete all 1:1 tasks just to replan. Optional requirementId restricts to one requirement. With applyMilestoneDates=true created tasks get the milestone timeline. Afterwards: set_milestone_criteria_tasks; for a full soft-reset use replace_milestone_tasks.

#### `replace_milestone_tasks`

Soft-reset for milestone planning: deletes all tasks assigned to the milestone (same guards as delete_tasks — active timers block individual items; time entries stay on the project). With recreateFromScope=true, validates that the milestone scope has requirements (and dates if applyMilestoneDates) BEFORE deleting — empty scope returns deletedCount=0 + warning, no throw. Prefer create_tasks with requirementIds for granular plans instead of tear-down when possible.

#### `get_scope_coverage`

Returns the milestone-scope split of the whole specification for a project: per deliverable section every requirement and acceptance criterion with the milestone it is assigned to (or unassigned). Use BEFORE set_milestone_scope to plan the split without conflicts (each item can only be in ONE milestone scope), and afterwards to find gaps. Summary contains assigned/total counts and gapCount.

#### `validate_project_setup`

Validates the planning setup of a project and returns findings with severity (Warning/Info): milestone date order, scope requirements without linked task, duplicate titles within a milestone, criteria without task links, collective tasks covering many criteria, requirements not in any scope, missing implementation texts, unscheduled milestone tasks. phase=planning hides ImplementationGaps (still counted in stats). phase=acceptance (default) includes all findings. Done means 0 warnings.

#### `get_milestone_scope_implementation_gaps`

Lists requirements and acceptance criteria in the milestone scope that still have no implementationText (Umsetzung/Nachweis), incl. their ids and texts. Fill the gaps with set_implementation_texts, check again, then start_milestone_review / PDF export.

#### `set_implementation_texts`

Records per requirement (Anforderung) and/or acceptance criterion (Abnahmekriterium) how it was implemented (Umsetzung/Nachweis, Markdown, max 8000 chars each) — in ONE call for many items. Preferred over the single set_requirement_implementation / set_criterion_implementation tools. Ids come from get_milestone (scope items) or get_milestone_scope_implementation_gaps or get_specification. Empty/null text clears. Returns a compact per-item confirmation.

#### `set_task_requirements`

Links specification requirements to a task for traceability (independent of milestone). Use requirement ids from get_specification. Complements set_milestone_criterion_tasks for checklist fulfillment.

#### `start_milestone_review`

Sets a milestone to In Review status. Fails if required deliverables are not submitted or phase gate is not passed (when enabled). Optionally with review deadline (reviewDeadline, YYYY-MM-DD).

#### `pass_milestone_phase_gate`

Passes the phase gate on a milestone. Requires all required deliverables Status >= Submitted.

#### `revoke_milestone_phase_gate`

Revokes a passed phase gate (only while milestone is still editable and not in review/accepted/locked).

#### `create_milestone_deliverable`

Adds a methodology deliverable (Ergebnis) to a milestone checklist.

#### `update_milestone_deliverable`

Updates title, required flag, notes, or assignee of a milestone deliverable.

#### `delete_milestone_deliverable`

Deletes a milestone deliverable.

#### `set_milestone_deliverable_status`

Sets deliverable status: Open, InProgress, Submitted, Approved. Gate/review require Submitted or Approved for required items.

#### `set_milestone_deliverable_link`

Sets exactly one link on a deliverable: wiki page, project document, or external URL (or clear all by omitting all).

#### `sign_off_milestone`

Deprecated: formal acceptance requires uploading a signed acceptance PDF via the app API. This call fails; use the milestone acceptance-document upload endpoint instead.

#### `lock_milestone`

Locks an accepted milestone (status Locked). No further edits or new task assignments.

#### `unlock_milestone`

Removes lock from a milestone (back to Accepted).

### Tasks, Gantt, Kanban

#### `list_tasks`

Lists tasks in the current context (lean — preferred for agent overviews). Filters: projectId, boardId, milestoneId, withoutMilestone, assigneeUserId, unassignedOnly, priority (0-3), isDone, columnId, isOnHold, isOutOfScope, codeStatus, acceptanceStatus, acceptanceNotAccepted. Optional includePredecessors=true adds predecessorTaskIds per task. For full detail use get_task (with includeOptions=false by default).

#### `get_task`

Returns task detail incl. description, status, priority, assignee, responsibleParty (external), onHoldSince, daysOnHold, dates, estimated hours, predecessors/successors, linkedRequirements. By default includeOptions=false (agent-friendly): omits UI dropdown lists dependencyOptions/assigneeOptions/milestoneOptions. Set includeOptions=true only for editor UIs. Prefer list_tasks for overviews.

#### `create_task`

Creates a new task in a project and schedules it on the Gantt. Set startDate/endDate (YYYY-MM-DD), milestoneId (optional), assigneeUserId, estimatedHours, responsibleParty (external party e.g. partner name), and optional predecessors (predecessorTaskIds). For MANY tasks use create_tasks (batch) or create_tasks_from_milestone_scope (1 task per scope requirement) instead of looping.

#### `create_tasks`

Creates up to 200 tasks in one call (batch variant of create_task). Each item: title (required), description, startDate/endDate (YYYY-MM-DD), assigneeUserId, estimatedHours, milestoneId, responsibleParty, predecessorTaskIds, optionally requirementIds (a REQ may link to many tasks — supported), and optionally idempotencyKey (unique per project; retries return the existing task). Preferred for granular execution plans. Items are processed independently: failures don't abort the batch.

#### `update_task`

Updates an existing task. Only provided fields change; omitted values are kept. codeStatus: 0=NotStarted,1=Partial,2=Complete; acceptanceStatus: 0=Open,1=Testable,2=Accepted (ignored for OP-* tasks). Setting both to Complete+Accepted auto-moves to Done column. move_task to Done requires both. Dates YYYY-MM-DD.

#### `bulk_update_tasks`

Updates multiple tasks at once (max 500). Only provided fields change. Typical: milestoneId for bulk milestone assignment, assigneeUserId for assignment. clearAssignee/clearMilestone/clearEstimatedHours clear fields.

#### `schedule_task`

Moves/schedules an existing task to a new date range (start/end in YYYY-MM-DD) and updates the Gantt. For MANY tasks use schedule_tasks (batch) instead of looping.

#### `schedule_tasks`

Batch variant of schedule_task: schedules MANY tasks in one call (max 200 items). Each item: taskId + start/end (YYYY-MM-DD). Items are processed independently: failures don't abort the batch, the response reports the new date range or error per item. Task ids come from create_tasks_from_milestone_scope / create_tasks / list_tasks. Use get_resource_availability before scheduling.

#### `add_task_dependency`

Creates a dependency between two tasks in the same project: predecessorTaskId must finish before successorTaskId. Does NOT change dates by default. Set rescheduleFromDependencies=true for a simple FS push (successor start moved to predecessor end, duration preserved; also applies if the edge already exists). For many edges use add_task_dependencies (batch).

#### `add_task_dependencies`

Batch variant of add_task_dependency (max 200). Each item: predecessorTaskId + successorTaskId. Existing edges are skipped (idempotent) but still get FS push when rescheduleFromDependencies=true. Does not change dates unless that flag is set (simple FS push: successor start moved to predecessor end, duration preserved). No full CPM/auto-critical-path.

#### `remove_task_dependency`

Removes an existing dependency between two tasks in the same project (predecessorTaskId was required for successorTaskId).

#### `remove_task_dependencies`

Batch variant of remove_task_dependency (max 200). Missing edges are skipped.

#### `list_boards`

Lists boards in the current context with columns (board id/name and per column: columnId, name, sort order, isDone, isOnHold). Use columnId for move_task; isDone=true marks Done, isOnHold=true marks On Hold. Moving to an On Hold column sets onHoldSince automatically.

#### `move_task`

Moves a task to another board column. Moving to Done (isDone) requires codeStatus=Complete and acceptanceStatus=Accepted for feature tasks. On Hold sets onHoldSince.

#### `complete_task`

Marks a task as done by moving it to the Done column (isDone) of its board. Fails if the board has no Done column.

#### `delete_task`

Permanently deletes a task. Linked dependencies are removed; logged time entries remain (they lose task assignment but stay on the project). Fails if a timer is running on the task. Cannot be undone. For MANY tasks use delete_tasks (batch).

#### `delete_tasks`

Batch delete up to 200 tasks. Items with an active timer fail individually; other items still delete. Linked dependencies are removed; time entries stay on the project (task assignment cleared). Prefer replace_milestone_tasks for milestone soft-reset.

#### `list_task_comments`

Lists comments and links on a task (newest first). For GitHub PRs use full URLs; for commits a SHA works if the project has RepositoryUrl set.

#### `add_task_comment`

Adds a comment to a task. Optionally with a link (full PR/commit URL, or commit SHA if the project has a Git repository URL in settings).

### Time tracking

#### `get_current_time_status`

Returns current time tracking status: any running timer (project, task, start time) and total time logged today and this week. Use before starting or stopping a timer.

#### `start_timer`

Starts time tracking for a project (optionally for a specific task). Fails if a timer is already running — use switch_timer or stop_timer instead. Returns the running timer.

#### `switch_timer`

Switches time tracking to another project/task: stops any running timer (with optional note) and starts a new one immediately. If no timer is running, simply starts. Returns the new running timer.

#### `stop_timer`

Stops the currently running timer and saves the entry. Optional note describes the activity. Fails if no timer is running.

#### `log_time`

Logs a completed time entry for a project (optionally a task) after the fact. Start and end as ISO-8601 timestamps (e.g. 2026-06-20T09:00:00+02:00). Must not overlap existing entries. Returns the created entry.

#### `update_time_entry`

Corrects an existing time entry (times and/or note). Entry id from get_timesheet. Start/end as ISO-8601. Must not overlap other entries.

#### `delete_time_entry`

Permanently deletes a time entry. Entry id from get_timesheet.

#### `get_timesheet`

Returns logged time entries in a date range (from/to in YYYY-MM-DD, max 366 days) incl. individual entries and totals per project. Use for reports and to find entry ids for corrections.

#### `get_weekly_report`

Returns the weekly report for the current calendar week: logged hours per project and total sum.

### Meetings

#### `list_meetings`

Lists meeting protocols of a project (newest first). Optionally filter with from/to in YYYY-MM-DD format.

#### `get_meeting`

Returns a meeting protocol incl. participants, discussed topics, decisions, and action items (workTaskId if linked). Workflow: action item → create_task_from_meeting_action → schedule_task.

#### `create_meeting`

Creates a meeting protocol for a project. heldAt as ISO-8601 timestamp. participantUserIds from list_members; external guests via set_meeting_participants.

#### `update_meeting`

Updates meeting metadata and discussed topics. Only provided fields change; heldAt as ISO-8601.

#### `delete_meeting`

Permanently deletes a meeting protocol incl. decisions and action items. Linked tasks remain.

#### `set_meeting_participants`

Sets the participant list of a protocol (replaces existing). participantUserIds from list_members; externalParticipantNames for guests without a system account.

#### `add_meeting_decision`

Adds a decision to a protocol (append-only).

#### `add_meeting_action_item`

Adds an action item to a protocol. assigneeUserId from list_members, dueDate YYYY-MM-DD.

#### `create_task_from_meeting_action`

Creates a task from an action item and links them. columnId optional from list_boards.

#### `list_open_meeting_action_items`

Lists open action items of a project (not yet linked to a task) for follow-up.

### Wiki

#### `get_project_wiki_context`

Read this first for project delivery context: returns wiki pages marked IsProjectContext. Not a substitute for specifications, meetings, or signed documents.

#### `list_wiki_pages`

Lists wiki pages of a project (sort order, then title). Optional search filters title and Markdown body (case-insensitive). Living operational knowledge — not a substitute for specifications.

#### `get_wiki_page`

Returns a wiki page with full Markdown body. Prefer get_project_wiki_context for curated AI context pages.

#### `create_wiki_page`

Creates a wiki page for a project. Body is Markdown (images via wiki-media, PlantUML fences supported in UI). Set isProjectContext=true for AI context pages.

#### `update_wiki_page`

Updates a wiki page. Only provided fields change.

#### `delete_wiki_page`

Permanently deletes a wiki page and its uploaded images.

### Change requests (Nachträge)

#### `list_change_requests`

Lists change requests (Nachträge) for a project.

#### `get_change_request`

Returns a change request with requirements and acceptance documents. Check status and acceptanceDocuments before materialize_change_request.

#### `create_change_request`

Creates a draft change request. Add requirements with add_change_request_item. Optional targetMilestoneId auto-adds reqs to milestone scope on materialize. Optional estimatedCost (CHF) + fundingSource decide the budget effect on approval: Contingency/ManagementReserve draw reserves; AdditionalBudget raises the performance budget (do NOT also create a budget line for that money). Unspecified = document only. See plan-resuah://guides/budget and plan-resuah://guides/change-requests. description: Markdown with PlantUML/spec-media images — see plan-resuah://guides/specification.

#### `update_change_request`

Updates a draft change request (title, description, hours, cost/funding, target milestone). Optional targetMilestoneId auto-adds reqs to milestone scope on materialize. estimatedCost + fundingSource: Contingency/ManagementReserve draw reserves on approval; AdditionalBudget grows performance budget — do not also add a budget line for that amount (plan-resuah://guides/budget). Fails if status is not Draft. description: Markdown with PlantUML/spec-media images (plan-resuah://guides/specification).

#### `delete_change_request`

Permanently deletes a draft change request. Fails if status is not Draft (submitted, approved, materialized, etc.).

#### `add_change_request_item`

Adds a requirement draft to a change request (Draft status only).

#### `update_change_request_item`

Updates a requirement on a draft change request. Fails if the change request is not Draft.

#### `delete_change_request_item`

Deletes a requirement from a draft change request. Fails if the change request is not Draft.

#### `submit_change_request`

Submits a change request for customer approval (PendingApproval). Next step requires human: export PDF, customer signs, upload signed PDF in app (no MCP upload). Then materialize_change_request when status is Approved.

#### `materialize_change_request`

Transfers an approved change request into the specification (section Nachträge) and optionally milestone scope. Requires status Approved (signed PDF uploaded in app).

### Cost and budget

#### `get_project_budget`

Money budget of a project: planned vs actual per cost category, reserves and their consumption. performanceBudget = budgetLinesTotal + additionalBudget, where additional budget is the money approved through change requests with fundingSource=AdditionalBudget — it needs no budget line of its own and would be double counted if you added one. Actual = derived personnel cost (tracked hours × project hourly rate) + committed external costs; categories carry isDerived=true where the actual is calculated rather than recorded. Requested cost entries count as commitment only, cancelled ones never count. costBaseline = performanceBudget + contingency, totalBudget adds the management reserve. The optional 'costCap' object is the agreed hard ceiling: unlike the performance budget it does NOT grow with change requests, and planExceedsCap=true means more is already planned than agreed. Reserve consumption comes exclusively from approved or materialized change requests naming that funding source. Full decision tree: plan-resuah://guides/budget.

#### `set_project_budget_line`

Creates a NEW planned budget line (CHF) for a cost category — MCP cannot update an existing line (delete + recreate, or use the app). Use category Personnel for internal work; its ACTUAL is always derived from time entries, never recorded. Do not create a line for money already booked as change-request AdditionalBudget. Returns the recalculated project budget. See plan-resuah://guides/budget.

#### `delete_project_budget_line`

Deletes a planned budget line. Returns the recalculated project budget.

#### `log_project_cost`

Records an external cost / procurement item (CHF). Category Personnel is rejected: internal personnel cost is derived from time entries, so recording it would double-count — book external people as ExternalService. Status drives the actual cost: Ordered and Delivered count as actual, Requested is a commitment only, Cancelled never counts. Returns the recalculated project budget. See plan-resuah://guides/budget.

#### `delete_project_cost`

Deletes a recorded cost / procurement item. Returns the recalculated project budget.

### Risks

#### `list_risks`

Lists project risks (P×I register). Score = probability×impact (1–25). Status Open|Mitigating|Closed|Accepted. Not the same as blockers (get_blockers_dashboard) or contingency budget. Returns score, band, isUnmanagedHigh, isReviewOverdue.

#### `create_risk`

Identifies a project risk. Mitigation optional at create (identification-first). Defaults status=Open. probability/impact 1–5. Unmanaged high (Open && score≥15) drives Critical cockpit signal. See plan-resuah://guides/risks.

#### `update_risk`

Updates a risk (full payload — send current values for unchanged fields). To steer: status=Mitigating + mitigation + owner. To accept: status=Accepted + residualNotes. After mitigation works, lower probability/impact on the same row. Closed when no longer relevant. High Open → Critical signal; Mitigating high → Warn; Accepted/Closed ignored. See plan-resuah://guides/risks.

#### `delete_risk`

Permanently deletes a project risk. Prefer Closed/Accepted over delete for audit trail.

### Customer feedback

#### `list_feedback_links`

Lists feedback rounds (shareable customer links) of a project. Each item includes token and portalUrl (`/f/{token}`). Invite guests in the UI, not via MCP.

#### `create_feedback_link`

Creates a feedback round and returns token plus portalUrl. Optional milestone scopes coverage. Email invites stay in the UI.

#### `revoke_feedback_link`

Revokes a feedback round token. The customer link stops working.

#### `list_feedback`

Lists customer-feedback notes (F-nn). Prefer `feedbackLinkId` to scope to one round. Status: Open, InClarification, Implemented (please review), Declined, CustomerAccepted.

#### `get_feedback`

Returns one note including replies, linked REQ/KRIT, internal note, and decline reason.

#### `get_feedback_coverage`

REQ/KRIT coverage for a round or milestone: notes, assignees, implementation text, customer acceptance.

#### `create_feedback`

Creates a team note (author = token user). Prefer `feedbackLinkId`. Optional requirement/criterion ids. No image upload.

#### `update_feedback`

Triages a note. Status Open|InClarification|Implemented|Declined — not CustomerAccepted. Declined requires `customerVisibleReason`.

#### `add_feedback_reply`

Adds a team reply (visible on the customer link).

#### `create_task_from_feedback`

Creates a work task from a note and links them. Optional `columnId` from `list_boards`.

#### `accept_feedback_scope`

Marks a REQ or KRIT as customer-accepted (green check). Confirmer is the token user. Ask the user first.

#### `revoke_feedback_scope`

Withdraws customer acceptance. Ask the user first.

### Stakeholders

#### `list_stakeholders`

Lists project stakeholders (Power/Interest register). Not the same as org members or meeting participants. Quadrant is derived from interest×influence (threshold ≥3).

#### `create_stakeholder`

Creates a project stakeholder. interest/influence 1–5; attitude Unknown|Supportive|Neutral|Resistant. Optional ownerUserId/linkedUserId from list_members.

#### `update_stakeholder`

Updates a stakeholder. All fields required (send current values for unchanged). attitude Unknown|Supportive|Neutral|Resistant.

#### `delete_stakeholder`

Permanently deletes a project stakeholder.

### Capacity and reports

#### `get_resource_availability`

Returns resource and capacity planning for upcoming weeks: per person and calendar week, planned hours vs effective capacity (incl. absences), overbookings, and hints for unassigned or unscheduled tasks. Use before scheduling tasks. Organization context only.

#### `add_absence`

Records an absence (vacation, etc.) for an organization member. userId from list_members. Organization context only. Admins can record for anyone; members only for themselves.

#### `delete_absence`

Removes an absence by id (e.g. from get_resource_availability → absences). Organization context only.

#### `get_gantt`

Returns the Gantt view: projects with milestones and tasks including effective start/end dates, milestone acceptance progress, assignee, estimated hours, and dependencies. For a blocker summary (overdue on-hold tasks, waiting tasks, impact) use get_blockers_dashboard instead of manual arrow analysis.

#### `get_blockers_dashboard`

Blocker overview for weekly planning: KPIs (overdue blockers, due this week, waiting tasks), groups by responsible party and impact. Blocker = open task in On Hold column (Warten). Waiting = open task with at least one open on-hold predecessor (Gantt dependencies). Waiting feature tasks include codeStatus and acceptanceStatus. Impact rows list blocked successors with dual status. Requires organization context.

#### `get_acceptance_matrix`

Code vs acceptance matrix for feature tasks (excludes OP-*). Filters: projectId, milestoneId, acceptanceOpenOnly (milestone-assigned + acceptance not Accepted), waitingOnBlockerOnly. Complements get_blockers_dashboard (why waiting) with what is already implemented.

#### `get_effort_report`

Planned vs actual effort report for a project: budget, estimated vs logged hours per milestone and task, plus project time entries without task assignment. Includes a cost block (planned vs actual money, personnel derived from hours x rate plus committed external cost) when an hourly rate or budget lines exist. In organization context all team entries, otherwise own entries only.

#### `get_schedule_variance`

Planned vs actual schedule comparison for a project: variance in days per milestone and task (positive = delay). Compares planned start/end/target dates with effective Gantt dates.

#### `get_earned_value`

Earned value metrics (EVM) for a project: BAC/PV/EV/AC plus CV/CPI/SV/SPI/EAC/ETC/VAC/TCPI. The hour-based view (bacHours, cpi, spi, …) is always present: BAC = project budget hours (fallback: sum of task estimates, see budgetBasis), EV uses the 0/50/100 rule over estimated hours (done=100%, started codeStatus=50%), PV is time-proportional over the current planned task ranges — no frozen baseline, so SPI measures deviation from the CURRENT plan. The optional 'cost' object adds the same metrics in CHF as soon as an hourly rate or budget lines exist (basis=rate|budgetLines): cost BAC = budget lines + additional budget from approved change requests + contingency (management reserve stays OUTSIDE the BAC), cost AC = derived personnel cost (tracked hours × rate) + committed external costs (ordered or delivered). The BAC therefore grows with paid scope, so compare CPI over time only within an unchanged budget. 'unit' says which view leads. hasData=false means no task has an estimate. Funding model: plan-resuah://guides/budget.
