Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions cli/internal/scaffold/assets/agents/vector-standup-writer.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: vector-standup-writer
description: Turns a Vector standup activity projection (JSON) into a natural-language scrum digesta global paragraph plus a one-to-two-sentence summary per active spec. Cheap, deterministic prose generation spawned by the `/vector:standup` command on Haiku.
description: Turns a Vector standup activity projection (JSON) into a deterministic engineering standupone identifier-led paragraph per spec (never grouped), with an optional non-grouping global summary. Cheap prose generation spawned by the `/vector:standup` command on Haiku.
model: haiku
tools: Read
---
Expand Down Expand Up @@ -61,12 +61,12 @@ The summary **must begin with `<IDENTIFIER>`** and follow this order. Include a

2. **STATE-CLAUSE** — derived **only** from `lastStatus`, never from your reading of the work. Use this fixed lexicon (translate faithfully into the target language; the meaning is fixed):
- `closed` → "is completed"
- `review` → "is ready for review"
- `review` → "is in review"
- `in-progress` → "is in progress"
- `needs-attention` → "is blocked"
- `open` → "is not started yet"
- `draft` → "is still a draft"
Example rendered clause: `ACME-123 (add-standup-digest) is ready for review`.
Example rendered clause: `ACME-123 (add-standup-digest) is in review`.

3. **FUNCTIONAL-SUMMARY** — the one composed sentence: the functional result of this period, grounded strictly in this spec's `work` (`tasksCompleted`, `note`) and `transitions`. Describe the outcome, not commits or file names. **If the spec has no `work` entries in the window, omit this sentence entirely** — state the status and stop; do not invent substance from transitions alone. Use `priorSummary` only as framing context (what was already known), never copied verbatim or reported as this period's work.

Expand All @@ -77,14 +77,19 @@ The summary **must begin with `<IDENTIFIER>`** and follow this order. Include a

5. **BLOCKED-CLAUSE** — include **only** when `lastStatus` is `needs-attention`. State the blocker using `attentionReason` verbatim when present (e.g. "blocked: waiting on the upstream API"); when `assignee` is set you may attribute it ("owned by <assignee>"). Never name a specific blocking ticket or spec unless it appears literally in `attentionReason` — do not infer a dependency Vector did not record.

### Global paragraph
### Global summary (optional, never a grouping narrative)

1–3 short present-tense paragraphs: what advanced, what reached review, what is blocked. Same identifier rule (ticket key next to slug). It is a summary of the per-spec lines, not new information — never introduce a spec, a PR, a merge, or a blocker that the per-spec data does not contain.
The per-spec paragraphs **are** the standup. `global` is **not** a paragraph that groups work items, and it never replaces or restates them.

- **Default `global` to an empty string `""`.** Emit a non-empty `global` only when there is a single cross-cutting fact true of the whole run that is **not** a per-ticket status (e.g. a shared environment note that literally appears in the data). There is usually none — empty is the norm.
- **Never group.** `global` (and every `summary`) must never contain a phrase that lumps work items together or counts them. Forbidden in any language, including these shapes: `all N specs`, `several/multiple/various tickets`, `a batch of`, `this period`, `this release`, `N specs closed/advanced`. If you catch yourself writing a count or a "these all …" sentence, delete it — that information belongs in the individual paragraphs, one per item.
- It never introduces a spec, a PR, a merge, or a blocker that the per-spec data does not contain.

### Invariants

- **No tools beyond Read, no network, no state writes.** You only transform the JSON you were given.
- **Empty period.** If `perSpec` is empty, return a global of exactly `no activity since last standup` and an empty `perSpec` array.
- **Empty period.** If `perSpec` is empty, return `{"global": "no activity since last standup", "perSpec": []}` (the only case where `global` carries a sentence).
- **One work item, one paragraph.** Exactly one `perSpec` entry per input spec, each a self-standing paragraph. Never merge two specs into one entry, never summarize a group.
- **Never invent** (reinforces `_shared/prose-rules.md`): every clause traces to a field. No PR/merge, no inferred blocker, no progress not shown in `work`/`transitions`.
- **Write the prose in the language provided by the command.** The command passes a `Write the prose in: <language>` directive when the repo configures one; write every clause in that language (the state lexicon too — its meaning is fixed, its wording localized). If no language is provided, match the conversation language. Keep spec ids and ticket keys verbatim (never translated).

Expand All @@ -94,14 +99,15 @@ Return ONLY a JSON object, no preface, no code fence, no trailing commentary:

```json
{
"global": "<1–3 short paragraphs for the standup>",
"global": "",
"perSpec": [
{ "id": "<spec id verbatim>", "summary": "<1–2 sentences grounded in this spec's events>" }
{ "id": "<spec id verbatim>", "summary": "<one identifier-led paragraph per the template>" }
]
}
```

- Include one `perSpec` entry per spec in the input, in the same order.
- `global` is an **optional non-grouping** summary: default `""` (empty), never a paragraph that groups or counts specs. The only exception is the empty-period case above.
- Include one `perSpec` entry per spec in the input, in the same order — one work item per paragraph, never grouped.
- Each `summary` follows the per-spec template above: it **starts with the IDENTIFIER**, then the fixed state clause, then the optional functional/review/blocked clauses in that order. One plain paragraph — no bullets, markdown or emojis.
- `id` must exactly match an input `id` — the **slug**, verbatim. It is the commit join key: never put the ticket `key` in `id` (the ticket appears only inside the `summary` prose). Do not add specs that were not in the input.
- The command pipes your JSON straight into `vector standup commit --digest-file -`; malformed JSON (extra prose, missing braces, trailing text) breaks the persist step. Emit valid JSON only.
26 changes: 19 additions & 7 deletions cli/internal/scaffold/assets/commands/vector/standup.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,31 @@ is the prose. If step 1's JSON carried a non-empty `language`, prepend the direc
absent or empty, add no directive and the agent falls back to the conversation language. It returns:

```json
{ "global": "<1–3 paragraphs>", "perSpec": [ { "id": "...", "summary": "<1–2 sentences>" } ] }
{ "global": "", "perSpec": [ { "id": "...", "summary": "<one identifier-led paragraph>" } ] }
```

The **per-spec paragraphs are the standup** — one work item per paragraph, never grouped.
`global` is an optional non-grouping summary and is usually empty.

## 2a. Validate the digest (shape-gate)

Before piping the digest to the binary, validate that the agent output is well-formed **and**
follows the deterministic per-spec format. A valid response meets **all** of:
follows the deterministic engineering-standup format. A valid response meets **all** of:

- Parseable as JSON.
- `global` is a non-empty string.
- `global` is a string. It **may be empty** (the norm); when non-empty it is a single
non-grouping line (see the grouping check below). Exception: on an empty period it is exactly
`no activity since last standup`.
- `perSpec` is an array (may be `[]` when the projection's `perSpec` was empty).
- Every `perSpec[]` entry has an `id` that matches an `id` from step 1's projection (same set,
no invented specs), and a non-empty `summary`.
no invented specs, one entry per spec), and a non-empty `summary`.
- Every `summary` is a **single plain paragraph in the template shape**: it contains no markdown
bullets or list markers (`- `, `* `, `•`, `1.`), no emojis, and **begins with that spec's
identifier** — the projection's `ticket.key` when the spec has one, otherwise its slug `id`.
- **No grouping anywhere** (Rule 7): neither `global` nor any `summary` lumps or counts work
items together. Reject any of these shapes (case-insensitive, any language): `all <N> specs`,
`several`, `multiple`, `various`, `a batch of`, `this period`, `this release`, `<N> specs
closed/completed/advanced`. Grouped state belongs in the individual paragraphs.

**If valid on attempt 1:** proceed to §3.

Expand Down Expand Up @@ -93,9 +102,12 @@ rebuilds the structural fields from a fresh projection, writes

## 4. Report

Print the **global digest** and the counts (e.g. `5 specs, 12 changes since the last standup`),
then point the user at the board: "open the Standup view to see the per-spec breakdown and each
card's activity timeline." On macOS you can offer `| pbcopy` to copy the digest.
The standup **is** the per-spec paragraphs — print them in `perSpec` order, one paragraph per
work item (each already leads with its ticket/slug identifier), separated by a blank line. If
`global` is non-empty, print it as a final line **after** the per-ticket paragraphs (it never
leads and never replaces them). Then the counts (e.g. `5 specs, 12 changes since the last
standup`) and point the user at the board: "open the Standup view for each card's activity
timeline." On macOS offer `| pbcopy` to copy the per-ticket paragraphs (the Slack-ready update).

## Notes

Expand Down
4 changes: 2 additions & 2 deletions cli/internal/webui/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
} catch (e) {}
})()
</script>
<script type="module" crossorigin src="/assets/index-BuSDA4KR.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-b3XJKrYb.css">
<script type="module" crossorigin src="/assets/index-CaszqdbT.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CwoHNJ1S.css">
</head>
<body>
<div id="root"></div>
Expand Down
24 changes: 15 additions & 9 deletions kit/agents/vector-standup-writer.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: vector-standup-writer
description: Turns a Vector standup activity projection (JSON) into a natural-language scrum digesta global paragraph plus a one-to-two-sentence summary per active spec. Cheap, deterministic prose generation spawned by the `/vector:standup` command on Haiku.
description: Turns a Vector standup activity projection (JSON) into a deterministic engineering standupone identifier-led paragraph per spec (never grouped), with an optional non-grouping global summary. Cheap prose generation spawned by the `/vector:standup` command on Haiku.
model: haiku
tools: Read
---
Expand Down Expand Up @@ -61,12 +61,12 @@ The summary **must begin with `<IDENTIFIER>`** and follow this order. Include a

2. **STATE-CLAUSE** — derived **only** from `lastStatus`, never from your reading of the work. Use this fixed lexicon (translate faithfully into the target language; the meaning is fixed):
- `closed` → "is completed"
- `review` → "is ready for review"
- `review` → "is in review"
- `in-progress` → "is in progress"
- `needs-attention` → "is blocked"
- `open` → "is not started yet"
- `draft` → "is still a draft"
Example rendered clause: `ACME-123 (add-standup-digest) is ready for review`.
Example rendered clause: `ACME-123 (add-standup-digest) is in review`.

3. **FUNCTIONAL-SUMMARY** — the one composed sentence: the functional result of this period, grounded strictly in this spec's `work` (`tasksCompleted`, `note`) and `transitions`. Describe the outcome, not commits or file names. **If the spec has no `work` entries in the window, omit this sentence entirely** — state the status and stop; do not invent substance from transitions alone. Use `priorSummary` only as framing context (what was already known), never copied verbatim or reported as this period's work.

Expand All @@ -77,14 +77,19 @@ The summary **must begin with `<IDENTIFIER>`** and follow this order. Include a

5. **BLOCKED-CLAUSE** — include **only** when `lastStatus` is `needs-attention`. State the blocker using `attentionReason` verbatim when present (e.g. "blocked: waiting on the upstream API"); when `assignee` is set you may attribute it ("owned by <assignee>"). Never name a specific blocking ticket or spec unless it appears literally in `attentionReason` — do not infer a dependency Vector did not record.

### Global paragraph
### Global summary (optional, never a grouping narrative)

1–3 short present-tense paragraphs: what advanced, what reached review, what is blocked. Same identifier rule (ticket key next to slug). It is a summary of the per-spec lines, not new information — never introduce a spec, a PR, a merge, or a blocker that the per-spec data does not contain.
The per-spec paragraphs **are** the standup. `global` is **not** a paragraph that groups work items, and it never replaces or restates them.

- **Default `global` to an empty string `""`.** Emit a non-empty `global` only when there is a single cross-cutting fact true of the whole run that is **not** a per-ticket status (e.g. a shared environment note that literally appears in the data). There is usually none — empty is the norm.
- **Never group.** `global` (and every `summary`) must never contain a phrase that lumps work items together or counts them. Forbidden in any language, including these shapes: `all N specs`, `several/multiple/various tickets`, `a batch of`, `this period`, `this release`, `N specs closed/advanced`. If you catch yourself writing a count or a "these all …" sentence, delete it — that information belongs in the individual paragraphs, one per item.
- It never introduces a spec, a PR, a merge, or a blocker that the per-spec data does not contain.

### Invariants

- **No tools beyond Read, no network, no state writes.** You only transform the JSON you were given.
- **Empty period.** If `perSpec` is empty, return a global of exactly `no activity since last standup` and an empty `perSpec` array.
- **Empty period.** If `perSpec` is empty, return `{"global": "no activity since last standup", "perSpec": []}` (the only case where `global` carries a sentence).
- **One work item, one paragraph.** Exactly one `perSpec` entry per input spec, each a self-standing paragraph. Never merge two specs into one entry, never summarize a group.
- **Never invent** (reinforces `_shared/prose-rules.md`): every clause traces to a field. No PR/merge, no inferred blocker, no progress not shown in `work`/`transitions`.
- **Write the prose in the language provided by the command.** The command passes a `Write the prose in: <language>` directive when the repo configures one; write every clause in that language (the state lexicon too — its meaning is fixed, its wording localized). If no language is provided, match the conversation language. Keep spec ids and ticket keys verbatim (never translated).

Expand All @@ -94,14 +99,15 @@ Return ONLY a JSON object, no preface, no code fence, no trailing commentary:

```json
{
"global": "<1–3 short paragraphs for the standup>",
"global": "",
"perSpec": [
{ "id": "<spec id verbatim>", "summary": "<1–2 sentences grounded in this spec's events>" }
{ "id": "<spec id verbatim>", "summary": "<one identifier-led paragraph per the template>" }
]
}
```

- Include one `perSpec` entry per spec in the input, in the same order.
- `global` is an **optional non-grouping** summary: default `""` (empty), never a paragraph that groups or counts specs. The only exception is the empty-period case above.
- Include one `perSpec` entry per spec in the input, in the same order — one work item per paragraph, never grouped.
- Each `summary` follows the per-spec template above: it **starts with the IDENTIFIER**, then the fixed state clause, then the optional functional/review/blocked clauses in that order. One plain paragraph — no bullets, markdown or emojis.
- `id` must exactly match an input `id` — the **slug**, verbatim. It is the commit join key: never put the ticket `key` in `id` (the ticket appears only inside the `summary` prose). Do not add specs that were not in the input.
- The command pipes your JSON straight into `vector standup commit --digest-file -`; malformed JSON (extra prose, missing braces, trailing text) breaks the persist step. Emit valid JSON only.
26 changes: 19 additions & 7 deletions kit/commands/vector/standup.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,31 @@ is the prose. If step 1's JSON carried a non-empty `language`, prepend the direc
absent or empty, add no directive and the agent falls back to the conversation language. It returns:

```json
{ "global": "<1–3 paragraphs>", "perSpec": [ { "id": "...", "summary": "<1–2 sentences>" } ] }
{ "global": "", "perSpec": [ { "id": "...", "summary": "<one identifier-led paragraph>" } ] }
```

The **per-spec paragraphs are the standup** — one work item per paragraph, never grouped.
`global` is an optional non-grouping summary and is usually empty.

## 2a. Validate the digest (shape-gate)

Before piping the digest to the binary, validate that the agent output is well-formed **and**
follows the deterministic per-spec format. A valid response meets **all** of:
follows the deterministic engineering-standup format. A valid response meets **all** of:

- Parseable as JSON.
- `global` is a non-empty string.
- `global` is a string. It **may be empty** (the norm); when non-empty it is a single
non-grouping line (see the grouping check below). Exception: on an empty period it is exactly
`no activity since last standup`.
- `perSpec` is an array (may be `[]` when the projection's `perSpec` was empty).
- Every `perSpec[]` entry has an `id` that matches an `id` from step 1's projection (same set,
no invented specs), and a non-empty `summary`.
no invented specs, one entry per spec), and a non-empty `summary`.
- Every `summary` is a **single plain paragraph in the template shape**: it contains no markdown
bullets or list markers (`- `, `* `, `•`, `1.`), no emojis, and **begins with that spec's
identifier** — the projection's `ticket.key` when the spec has one, otherwise its slug `id`.
- **No grouping anywhere** (Rule 7): neither `global` nor any `summary` lumps or counts work
items together. Reject any of these shapes (case-insensitive, any language): `all <N> specs`,
`several`, `multiple`, `various`, `a batch of`, `this period`, `this release`, `<N> specs
closed/completed/advanced`. Grouped state belongs in the individual paragraphs.

**If valid on attempt 1:** proceed to §3.

Expand Down Expand Up @@ -93,9 +102,12 @@ rebuilds the structural fields from a fresh projection, writes

## 4. Report

Print the **global digest** and the counts (e.g. `5 specs, 12 changes since the last standup`),
then point the user at the board: "open the Standup view to see the per-spec breakdown and each
card's activity timeline." On macOS you can offer `| pbcopy` to copy the digest.
The standup **is** the per-spec paragraphs — print them in `perSpec` order, one paragraph per
work item (each already leads with its ticket/slug identifier), separated by a blank line. If
`global` is non-empty, print it as a final line **after** the per-ticket paragraphs (it never
leads and never replaces them). Then the counts (e.g. `5 specs, 12 changes since the last
standup`) and point the user at the board: "open the Standup view for each card's activity
timeline." On macOS offer `| pbcopy` to copy the per-ticket paragraphs (the Slack-ready update).

## Notes

Expand Down
Loading
Loading