---
name: cse-forecast
description: CSE portfolio and board forecasting. Owns complete Jira board inventory, stage movement, stalled work, ownership, data quality findings, capacity context, and guarded board actions. Use for CSE forecast, forecasting meeting, board review, pipeline review, portfolio review, stalled accounts, daily checklist, or CSE health. Default is report-only; preview or execute only when the operator explicitly asks.
argument-hint: "[date or 'today'] [optional: preview|apply]"
---

# CSE Forecast

## Compact MCP routing

Follow the shared [compact MCP routing contract](../../shared/compact-mcp-routing.md) and [read strategy](../../shared/read-strategy.md). Interactive facade tools are `cse_capabilities`, `cse_read`, `cse_apply`, `context_assemble`, and `cse_session_info`; named operations are capability ids. Call reads through `cse_read` with the capability id. `context_assemble` is primary for top-risk ticket context; board truth comes from the `jira_search` capability with a `named_query`. Every mutation goes through `cse_apply` twice: dry-run first, then the identical capability and arguments with `execute:true`, justification, and the returned `preview_digest`. Call `context_assemble` and `cse_session_info` directly when needed.

This skill is the sole CSE portfolio and board-forecasting owner. It does not own builder show-and-tell, GitHub scanning, or war-room agenda construction -- those belong to `war-room-prep`.

You do not write code or edit repository files as part of forecasting. You may update Jira only in EXECUTE mode after explicit operator intent. Default triggers never mutate.

## Intent modes

Resolve mode from the operator's language before any write-capable call:

| Mode | Intent language | Allowed side effects |
| --- | --- | --- |
| `REPORT` (default) | forecast, forecasting meeting, board/pipeline/portfolio review, stalled accounts, daily checklist, CSE health, or any generic checklist phrasing without preview/apply words | Reads, local markdown + log writes only. **No** `cse_apply`, **no** dry-runs, **no** external mutations. |
| `PREVIEW` | explicit preview language ("preview the board updates", "dry-run the forecast actions", "show proposed writes") | Full REPORT first, then dry-run each approved guarded capability. Apply nothing. |
| `EXECUTE` | explicit apply/update/execute language ("apply the forecast actions", "update the board", "execute the transitions") | Full REPORT, then for each mutation: dry-run -> apply with digest -> Jira GET-back -> receipt. |

Rules:

- Generic forecast and checklist triggers never call write capabilities, even in dry-run mode.
- Explicit preview intent may dry-run but never applies.
- Explicit execute intent still previews every mutation first; no preview digest, no write.
- If named-query pagination is incomplete, omit portfolio totals and disable PREVIEW and EXECUTE for that run.
- Retry a transient mutation once. A second failure becomes `HELD`; later actions cannot assume it succeeded.
- Stage transitions retain hold checks H1/H2/H3 below.
- Data-quality resolution and escalation invoke [`resolve-or-escalate.md`](../../shared/resolve-or-escalate.md); only that policy reads/writes `cse-dq`. Never direct-write `jira_property` for `cse-dq` from this skill.
- Stale-ticket nudges and untracked-work Slack pings are recommendation-only. Do not post them or write a nudge-tracking property entry.
- Do not claim that required transition fields, Jira Forms, or form properties are restricted to the Jira UI. If a needed Forms operation is not exposed as a named tool, report the tool-surface gap rather than inventing an API/UI limitation.

Builder-showcase prompts ("what did we build", "CSE codebase updates", "show and tell", "what shipped in git") do not route here. Tell the operator to use `war-room-prep`. Never blend a GitHub builder scan into the forecast.

## Domain Configuration

Use `cse_domain_info` for planning (engagement stage names, lifecycle, urgency, next_stage, terminal statuses, sentinel values/patterns, ADF field names, named query names, labels). Pass semantic field names, stage names, and `named_query` values to Jira capabilities; the daemon maps fields and transitions and expands JQL. Never pass Jira custom-field keys or raw transition ids.

Do not read `~/.cse-tools/config.yaml`; domain discovery stays behind `cse_domain_info`.

## Comment & message voice

Every Jira comment follows [`.agents/shared/prose-voice.md`](../../shared/prose-voice.md) -- sentence-case openers, first-person for writes, terminal punctuation on Jira. Jira comments reference people by display name only. Evidence (source, ISO date, permalink, cooldown key) goes to the `cse-dq` entity property per [`.agents/shared/audit-manifest.md`](../../shared/audit-manifest.md), never the comment body, and only through the shared resolve-or-escalate policy. Do not fork comment shapes in this skill.

Before drafting any Jira comment, follow `prose-voice.md` plus compact register rules (and the `writing` skill for the patterns to favor and the AI-slop tells to avoid).

The Jira issue page already supplies the issue key, customer name, status, and assignee. Never repeat that context for scene setting, narrate routine Jira UI mechanics, or explain forms and transitions to the owner. Post only a new decision, blocker, or concrete ask; otherwise skip the comment.

**Plain ADF comment bodies cannot render clickable mentions.** The daemon posts `jira_add_comment` and transition `comment_path` bodies as plain ADF text. Raw Jira account-id mention syntax (`@[712020:uuid]`) and wiki markup (`[~accountid:...]`) will appear as visible markup to human readers and are blocked by the daemon's pre-send validation. Refer to people by display name only (`Priya`, `John Smith`); do not use any mention syntax in plain prose.

## Setup

Before starting, source the bootstrap so `CSE_TOOLD_BIN` and `CSE_OPERATOR` resolve:

```bash
source "${PLUGIN_ROOT:-${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$PWD/plugins/cse-tools}}}/.agents/shared/skill-bootstrap.sh"
```

The daemon refreshes daemon keychain entries on its own through the headless Okta Broker; no mint step is needed here. No shell auth env vars or bearer headers are required. The local `cse-tools` MCP reads Jira/Confluence, Slack-write, Granola, and Kepler auth from the daemon; shared scripts are fallback/diagnostic only. On a 401, call `cse_session_info({"force_refresh": true})` once, follow any named operator action, and retry once. A 403 means access is blocked or revoked: surface the operator action and stop without retrying. If the MCP connection is refused, run the `cse-mcp-setup` recovery flow, retry the failed call once, and stop with `DATA UNAVAILABLE -- cse-tools daemon unreachable after recovery` if it is still refused.

Dependencies and fallbacks:

- **Read strategy:** Follow [`.agents/shared/read-strategy.md`](../../shared/read-strategy.md). Board enumeration and field state stay direct (`jira_search` with `named_query`); per-ticket blocker context for top-risk engagements goes through `context_assemble`; writes follow the intent modes above.
- **Direct-tool guardrails for this skill:** Follow [`.agents/shared/tool-frugality.md`](../../shared/tool-frugality.md). Owned tools (`jira_search`, `google_calendar_events`, `confluence_*`) return compact by default; pass `verbosity:"full"` only when you need the raw payload. Hydrate Kepler/Granola only for what you'll actually cite.
- **Jira** -- local `cse-tools` MCP capabilities (`jira_search`, `jira_get_issue`, `jira_edit_issue`, `jira_transition_issue`, `jira_add_comment`, `jira_delete_comment`, properties, user lookup), called through the compact facades.
- **Slack reads** -- local `cse-tools` MCP (`slack_read_channel`, `slack_read_thread`, `slack_search`, `slack_channels`, `slack_users`) for untracked-work detection. Slack writes are recommendation-only in this skill version.
- **Granola (recommended, optional)** -- use `granola_list_meetings` for active-workspace attendee discovery and `granola_get_meetings` for selected details. Use `granola_get_meeting_transcript` only for exact wording and the REST `granola_list_notes` path only for an explicit cross-workspace gap. Skip silently when Granola is not enrolled; `cse-tools` Kepler transcripts remain the primary source.
- **Kepler primary:** Kepler tools aggregated inside local `cse-tools` MCP for `list_customers`, `get_salesforce_account`, `semantic_context_search`, `list_communications`, and `get_communications`.
- **Orgchart roster:** `cse_orgchart_identity_snapshot` for live CSE ownership validation.
- **Capacity:** newest schema-valid `cse-bandwidth-reports/YYYY-MM-DD.evidence.json` no older than 7 days; otherwise mark capacity unavailable. Do not re-run or fork the bandwidth scoring model.

If a required surface is unavailable after preflight, mark that forecast section as `DATA UNAVAILABLE -- <surface> not connected` and continue. If Kepler is unavailable, continue without backend enrichment.

Full MCP + credential docs live in `AGENTS.md` and `README.md`.

## Data Sources

### Jira (via local `cse-tools` MCP)

- Project key, cloud ID, issue type, and custom fields are resolved by the daemon; pass semantic field names to `jira_*` tools and use `cse_domain_info` for stage/field discovery.
- Use local `cse-tools` MCP for all Jira reads and (in EXECUTE mode) writes. `jira_property` action `get` is a direct read used only inside [`resolve-or-escalate.md`](../../shared/resolve-or-escalate.md); this skill never issues `jira_property` set/delete itself.

### Gong / Communications / Salesforce / Kepler (via local `cse-tools` MCP)

Primary tools:

- `list_customers` for canonical account lookup
- `get_salesforce_account` for Salesforce record confirmation and team/ARR context
- `list_communications` (limit ~20) for Gong call discovery; `get_communications` (content_max_chars=6000) for transcript/body hydration -- `body` and `transcript` are identical strings, read one, never both
- `semantic_context_search` for CSE-grade account context packs with source IDs, match reasons, and hydration instructions

## Read pipeline

Run these steps in order. Do not calculate portfolio totals, render velocity, or enter PREVIEW/EXECUTE until pagination is complete.

1. Discover domain stage and named-query semantics with `cse_domain_info`.
2. Fully paginate `all_open`, `completed_this_week`, `stalled`, and `closed_complete_no_resolution` before deriving any count.
3. Validate assignees against the live CSE roster from `cse_orgchart_identity_snapshot`. Jira remains authoritative for ownership and status; an assignee outside the roster is a board-hygiene finding, not a silent rewrite.
4. Fetch changelog status history for every open issue with fan-out capped at 10. Time-in-stage is `UNKNOWN` when changelog evidence is unavailable; never substitute `updated`.
5. Calculate completion velocity, stage distribution, aging, next-step coverage, and prior-period movement from exact Jira data.
6. Enrich only the top 15 stalled/at-risk tickets with `context_assemble`, fan-out capped at 8. Reuse each ticket pack for blocker and next-step analysis.
7. Classify blockers as customer-dependent, internal-resource, technical, or unclear.
8. Read the newest `cse-bandwidth-reports/YYYY-MM-DD.evidence.json` only when it is no more than 7 days old and schema-valid (`schemaVersion: 1`, `generatedAt`, and `cses`). Otherwise mark capacity unavailable.
9. Render the forecast file and verify its required sections before considering any action mode.

If any board query is incompletely paginated, write a blocked/partial receipt, omit portfolio totals, and disable preview and execute modes for that run.

### Board queries

Execute board queries with domain-defined `named_query` values and bounded semantic fields. Discover available query names with `cse_domain_info`; do not embed raw JQL when a named query exists.

```jsonc
jira_search({
    named_query: "all_open",
    fields: ["summary", "status", "assignee", "updated", "stage_complete", "problem_statement", "customer_name", "team_id", "executive_sponsor", "technical_counterpart"],
    maxResults: 100
  })
```

Also run the domain-defined `completed_this_week`, `stalled`, and `closed_complete_no_resolution` queries. Derive the prior-week comparison from bounded issue history or another query returned by `cse_domain_info`; do not invent a query name.

For each board query, consume every page before calculating counts or taking action. Preserve identical `named_query`, `fields`, and `maxResults` arguments on every page; only add `arguments.nextPageToken` from the immediately preceding response. Track seen `nextPageToken` values separately per named query and allow at most 100 pages per query. A usable token is a non-empty string. Append each returned page's issues, then continue only with a usable, unseen token.

Classify the query as `INCOMPLETE` on a page error; an empty, malformed, or repeated token; `isLast:false` without a usable next token; or reaching the 100-page ceiling. Complete the query only when the response has no usable next token and does not explicitly report `isLast:false`. Never reuse a token across named queries. On `INCOMPLETE`, write the existing blocked/partial receipt, omit portfolio totals, and disable PREVIEW and EXECUTE for that run.

## Forecast sections

### Strategy

**1. Completions this week**

- Count of issues resolved in the last 7 days from the fully paginated `completed_this_week` set
- List each by customer name and engagement category

**2. Velocity trend**

- Compare this week's completion count to last week's
- Note engagements with unusually long changelog-derived time-in-stage
- If available, compare average time-in-stage across recent completions

**3. Bottleneck stage**

- Count open engagements per stage
- Identify which stage has the most items
- Flag any stage with more items than the previous week

**4. Top system constraint**

- Identify the stage with the most stalled items
- Look for common patterns: waiting on customer, waiting on internal resource, blocked by technical issue

**5. Root cause**

- For the top constraint, examine the specific blocked engagements
- For each of the top 15 stalled/at-risk tickets, call `context_assemble({entity:{kind:"ticket", ref:"CSE-XX"}, facets:["blockers","open_loops","recent_activity"], depth:"branching"})` with fan-out capped at 8. Reuse the pack; do not re-walk `slack_search` + `list_communications` by hand.
- Read each `coverage`: `slack=empty` is often a rate-limit, not silence; `kepler_comms=error` means lean on the remaining cards. Hydrate a transcript in full via `get_communications` by its card `citation` only when you'll quote it.

**6. One concrete relief action**

- Based on root cause analysis, propose one specific, assignable action

### Forecasting detail

**7. Defined outcomes**

- Check Problem Statement field for each open engagement
- Flag any that are empty or contain only placeholder text

**8. Stage clarity and expected next movement**

- Verify each open engagement has a valid workflow status
- Flag any in Qualification Review longer than 5 business days
- Expected next movement requires an explicit owner next step or scheduled milestone in ticket comments, fields, or hydrated context. Do not invent an unsupported probability score.

**9. Time-in-stage**

- For each open engagement, retrieve changelog/history explicitly (`jira_get_issue` with changelog expansion) and find the newest history item whose changed field is `status`. Calculate days from that transition timestamp; `updated` is general issue activity and must not be used as the stage-entry timestamp.
- Concretely, call `jira_get_issue({key:"CSE-XX", fields:["status"], expand:["changelog"]})` for every open key (fan-out capped at 10) and inspect the returned histories newest-first.
- If history is unavailable for a ticket, report its time-in-stage as `UNKNOWN`; do not substitute `updated`.

**10. Stalled engagements**

- List all engagements from the fully paginated `stalled` query
- Include customer name, stage, changelog days-in-stage (or UNKNOWN), days since last update, and assignee

**11. Blocker analysis**

- Reuse the top-15 `context_assemble` packs
- Categorize blockers: customer-dependent, internal-resource, technical, unclear

### System discipline

**12. Untracked work (findings + recommendations only)**

- Search Slack for CSE-related activity not reflected in Jira (`slack_search` for terms like "cse engagement", "POC", "proof of concept" in the past 7 days)
- Cross-reference customer names mentioned in Slack against open Jira engagements
- Flag any customer work happening in Slack without a corresponding Jira ticket
- Recommend a `#cse-requests` ping wording in the report; do not post Slack messages from this skill

**13. Data quality (findings; writes only via shared policy in EXECUTE)**

- Check for engagements missing required fields: Customer Name, Team ID, Problem Statement, Executive Sponsor
- Flag any engagement in Technical Discovery or later that is missing these fields
- Check for engagements where Stage Complete is set but the issue hasn't transitioned
- Workflow drift: any ticket in `Closed - Complete` with `resolution = EMPTY` is a hygiene flag -- the close transition fired but didn't capture a Resolution. These are invisible to any `resolved`/`resolutiondate` filter and silently drop out of velocity reports. Run the fully paginated `closed_complete_no_resolution` query and recommend that the assignee set Resolution on their next close. Do not auto-edit the field: setting `resolution` populates `resolutiondate` to "now", which would re-date a historical completion.
- In REPORT mode, list intended resolve/escalate actions under recommended updates without invoking the policy. In PREVIEW mode, invoke [`resolve-or-escalate.md`](../../shared/resolve-or-escalate.md) in its PREVIEW contract for `executive_sponsor` and `technical_counterpart` on open tickets: policy reads and guarded dry-runs are allowed, but no write applies. In EXECUTE mode, invoke its EXECUTE contract. The policy is the sole `cse-dq` writer in both action modes.

**Field-type handling on the missing-field scan.** Different Jira field types serialize differently and need different missing-tests. A scan that only walks ADF and string sentinels will mis-flag every set option-field as "missing". Use the type-appropriate test:

- **Single-select / radio (e.g. `account_arr_tier`)** -- value is `null` (missing) or `{id, value, ...}` (set). Missing iff `null`. Do NOT walk this for ADF text.
- **Multi-select (e.g. `stage_complete`)** -- value is `null` / `[]` (missing) or an array of option dicts (set). Missing iff `null` or empty array.
- **User picker** -- value is `null` or `{accountId, displayName, ...}`. Missing iff `null`.
- **Plain string fields** -- sentinel classification is handled by `cse_resolve_field` (via resolve-or-escalate); do not hand-implement sentinel matching.
- **ADF-textarea fields (`problem_statement`, `impact_metrics`, `postman_demonstration`, `customer_assets`)** -- `cse_resolve_field` extracts ADF text and applies sentinel tests; do not hand-implement the walk.

Inspect the live field value's shape before deciding which test applies. When a phantom gap is suspected, re-run with type-aware handling before reporting.

**14. Capacity context**

- Load the newest schema-valid `cse-bandwidth-reports/YYYY-MM-DD.evidence.json` whose filename date is within 7 days
- Summarize strapped/high-load CSEs from that artifact only
- If missing, older than 7 days, or schema-invalid, write `Capacity: UNAVAILABLE -- no fresh cse-bandwidth-reports evidence` and continue

## Action modes (after a complete report)

### REPORT (default)

```text
READ -> SYNTHESIZE -> WRITE FILE -> VERIFY
```

Stop after writing `forecasts/YYYY-MM-DD.md` and the canonical log. List recommended board updates without dry-running or applying them. ACTIONS EXECUTED must be empty (or state `none -- REPORT mode`).

### PREVIEW (explicit preview intent only)

```text
REPORT -> DRY-RUN EACH APPROVED CAPABILITY -> PROPOSED ACTIONS
```

Dry-run each approved guarded capability through `cse_apply` with outer `execute: true` and justification, omitting underlying `arguments.execute` so the named write dry-runs and returns `preview_digest`. Do not apply. Collect digests under `PROPOSED ACTIONS`.

### EXECUTE (explicit apply/update/execute intent only)

```text
REPORT -> DRY-RUN -> APPLY WITH DIGEST -> GET-BACK -> RECEIPT
```

For every approved mutation:

1. Preview via two-phase `cse_apply` (omit underlying `arguments.execute`).
2. Apply with the same capability and mutation args plus underlying `arguments.execute: true` and the returned `arguments.preview_digest`, outer `execute: true`, and justification (>=16 trimmed chars).
3. GET-back with `cse_read` / `jira_get_issue` to verify the landed status, fields, or newest comment as applicable.
4. Record success under ACTIONS EXECUTED only after GET-back confirms. If GET-back fails to confirm, list under HELD.

#### A. Stage transitions (EXECUTE only)

For every open engagement where `stage_complete = Yes`, execute the forward transition unless a hold condition applies:

```js
cse_apply({
  capability: "jira_transition_issue",
  arguments: {
    key: "CSE-XX",
    stage: "<next stage from cse_domain_info>"
  },
  execute: true,
  justification: "Preview the forward stage transition for a complete gate"
})
cse_apply({
  capability: "jira_transition_issue",
  arguments: {
    key: "CSE-XX",
    stage: "<next stage from cse_domain_info>",
    execute: true,
    preview_digest: "<preview_digest from preview>"
  },
  execute: true,
  justification: "Advance a forecast-verified engagement whose stage gate is complete"
})
```

Then GET-back:

```js
cse_read({
  capability: "jira_get_issue",
  arguments: { key: "CSE-XX", fields: ["status", "stage_complete"] }
})
```

Hold conditions:

- **H1 (missing prerequisite field):** the destination stage requires a field that is still empty or a `[Needs ... input]` placeholder. Example: Pilot Scoping requires a named Executive Sponsor; Pilot Validation requires a Technical Counterpart. When H1 fires, attempt shared-policy research (step B) first in EXECUTE mode. If research fails to produce a value, skip the transition and include it in HELD with the reason.
- **H2 (owner said hold):** the most recent comment from the engagement assignee (within the last 3 business days) explicitly says the ticket should not move yet (e.g. "closing gaps before pushing wide", "waiting on customer").
- **H3 (customer-unresponsive signal):** `list_communications` for the customer shows no inbound message in the last 10 business days AND the last outbound message explicitly asks for customer response. Flag as HELD even if `stage_complete = Yes`.

Use exact business-day thresholds for classification only. Jira comments should follow `.agents/shared/prose-voice.md`: use natural elapsed-time phrasing (`a few days`, `nearly two weeks`, `a couple weeks`) and avoid generic `Customer` as the prose subject.

For every open engagement where `stage_complete` is NOT set but the forecast identified the engagement as ready to move (e.g. live sandbox, demo delivered, POC signed off) based on the Postman Demonstration / comments / Gong evidence: set `stage_complete = Yes` first via two-phase `cse_apply` on `jira_edit_issue`, GET-back, then run the transition. Cite the evidence in ACTIONS EXECUTED.

#### B. Data-quality resolution and escalation (EXECUTE only; shared policy)

For every data-quality gap surfaced in section 13 -- missing Executive Sponsor, Technical Counterpart, or any value matching a sentinel -- invoke the shared policy at `.agents/shared/resolve-or-escalate.md`. The policy calls `cse_resolve_field`, which performs sentinel classification, ADF text extraction, stage-gate skip, and role list only (`escalation_team` is null; do not pass `team_id`); do not hand-implement sentinel matching. The policy is the only writer of `cse-dq`.

When that policy requires a visible escalation, the policy authors the body and owns its guarded comment and property calls. Do not duplicate those calls or supply an ad-hoc comment path from this skill.

Fields to request through the policy: `executive_sponsor`, `technical_counterpart`.

The policy handles:

- Resolution chain (Gong 90d -> Salesforce -> Slack).
- Field write + prose-voice comment when the rubric warrants it. Evidence, source dates, and permalinks live on the `cse-dq` entity property, outside the visible comment body.
- When writing, keep the field value under 255 characters; use `Primary Name, Title (+ supporting names)` and truncate supporting list if needed.
- Escalation: one batched comment per ticket referencing the account team by display name. `cse_resolve_field` returns classification/stage-gate/role list only -- `escalation_team` is null and `team_id` must not be passed; fall back to the ticket assignee (display name only) per shared policy. Do not invent a `team_id` argument on `list_customers`.
- Stage gate: escalation is suppressed for tickets in `Submitted` or `Qualification Review`.
- Idempotency: 7-day cooldown on re-escalating the same set of unresolved fields.

Do NOT write placeholder text back into a field if resolution fails. The policy handles that by escalating instead.

Customer-unresponsive signals (hold condition H3) are NOT handled by the data-quality policy. Keep them in HELD with their evidence and as a recommendation; do not post an ad-hoc comment or create a parallel audit/property path.

#### C. Recommendation-only hygiene (all modes)

List these under recommended board updates / PROPOSED ACTIONS. Do not execute them in this skill version:

- Stale-ticket nudge comments (no update in 7+ days despite recent Slack activity)
- Untracked-work Slack pings in `#cse-requests`
- Any direct stale-ticket `cse-dq` property write (removed; never restore without a dedicated idempotency contract)

#### D. Reporting

Every action executed (transition, field update, comment) must be listed in the ACTIONS EXECUTED block only after GET-back confirmation. Every action that was held must be listed in the HELD block with the hold-condition code (H1/H2/H3) and the evidence.

## Output Format

Write `forecasts/YYYY-MM-DD.md` using today's date (America/Los_Angeles). Create the directory if needed.

Each run also writes its complete report and action receipts to the canonical producer log `~/.config/cse-tools/logs/cse-forecast-YYYYMMDD-HHMM.log`, using the run start time in America/Los_Angeles. Create the log directory if needed. This filename and the `ACTIONS EXECUTED` / `HELD` blocks are the stable producer contract consumed by `morning-digest`; write a log even when the run stops after a recovery or auth failure.

Produce the report in this exact structure:

```
CSE FORECAST - [DATE]
=====================
Mode: [REPORT | PREVIEW | EXECUTE]
Pagination: [COMPLETE | BLOCKED -- <query> incomplete]

MOVEMENT SINCE LAST FORECAST
----------------------------
Completed: [count]
Advanced: [count]
Reverted: [count]
Newly opened: [count]
[supporting lists; omit numeric totals when Pagination is BLOCKED]

PORTFOLIO BY STAGE
------------------
[stage | count | aging notes]
[omit totals when Pagination is BLOCKED]

TIME-IN-STAGE
-------------
[table: Customer | Stage | Days | Source=changelog|UNKNOWN]

EXPECTED NEXT MOVEMENT
----------------------
[table: Customer | Next step or milestone | Evidence]
[no unsupported probability scores]

STALLED AND AT-RISK
-------------------
[table: Customer | Stage | Blocker Category | Owner | Evidence gap]

TOP SYSTEM CONSTRAINT
---------------------
[description]
Relief action: [specific, assignable action]

OWNERSHIP / UNTRACKED / DATA QUALITY
------------------------------------
Ownership exceptions: [...]
Untracked work: [CLEAN / ISSUES FOUND] (recommendations only)
Data quality: [CLEAN / ISSUES FOUND]
Capacity: [summary from fresh cse-bandwidth-reports evidence | UNAVAILABLE]

RECOMMENDED BOARD UPDATES
-------------------------
[transitions, field fills, comments -- not applied in REPORT]
[in PREVIEW, mirror PROPOSED ACTIONS with digests]

PROPOSED ACTIONS (PREVIEW only)
-------------------------------
[capability | key | preview_digest | intended effect]

ACTIONS EXECUTED
----------------
Transitions ([count]):
  [CSE-XX Customer: From Stage -> To Stage (evidence; GET-back confirmed)]

Field updates ([count]):
  [CSE-XX Customer <field>: <new value> (source; GET-back confirmed)]

Comments posted ([count]):
  [CSE-XX Customer: asked <assignee display name> -- <question>]

HELD
----
[CSE-XX Customer: intended action, hold code H1/H2/H3 or error, evidence]

SUMMARY
-------
Overall health: [RED / YELLOW / GREEN]
Priority action: [single most important thing to do today]

TICKET LINKS
------------
[CSE-XX -- https://postmanlabs.atlassian.net/browse/CSE-XX -- Customer (one-line role)]

DL -- [operator] only
---------------------
[sensitive exec/customer context]
```

The TICKET LINKS block is the operator's quick-jump index. List every ticket that appeared in ACTIONS EXECUTED, HELD, or recommended updates, one per line, in the order they were touched. Include the full browse URL inline so the line is copy-pasteable.

## Behavioral Rules

- Never fabricate data. If a query returns no results or fails, say so explicitly.
- If an MCP tool is unavailable, skip that section and note it as `DATA UNAVAILABLE -- [tool name] not connected`.
- Use actual customer names and Jira issue keys in the report.
- Keep analysis concise. Evidence over opinion.
- If the forecast reveals no issues in a section, say "No issues found" with the data that supports that conclusion.
- REPORT and PREVIEW never mutate external systems. EXECUTE actions run without asking for per-ticket confirmation once the operator requested execute intent; hold conditions H1/H2/H3 are the only reason to skip an approved action.
- Field updates must cite a source (Gong call ID, Slack permalink, Salesforce account, or Almanac brief). No free-text inference.
- Comments that reference someone on Jira must use their display name only; never use `@[accountId]`, `[~...]`, or any other mention markup in Jira comment bodies.
- Retry a failing Jira mutation once on transient error. If it fails twice, list the action under HELD with the error text instead of retrying further.
- Never scan GitHub, rank commit counts, or treat code output as forecast health or employee performance.
- Never write `cse-dq` except by invoking `resolve-or-escalate.md`.
- Never claim unsupported API or UI limits for transition-screen fields or Jira Forms; report tool-surface gaps instead.
