Skip to content

PM-46025: Platform RAG standup format + weekly-recap skill - #306

Draft
addisonbeck wants to merge 8 commits into
mainfrom
daily-recap-rag-weekly
Draft

addisonbeck wants to merge 8 commits into
mainfrom
daily-recap-rag-weekly

Conversation

@addisonbeck

@addisonbeck addisonbeck commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Restructures the bitwarden-retrospective-tools standup card (and its Copy
button) into Platform's RAG (red/amber/green) format, documents the RAG
heuristic and bullet-linking rules, and adds a thin weekly-recap skill that
reuses daily-recap's scripts and template instead of duplicating them.
Bumps the plugin from 1.1.0 to 1.2.0 with a matching CHANGELOG entry.

Jira: https://bitwarden.atlassian.net/browse/PM-46025 (PM-46025)

Why

PR #295 (merged) added Jira/Confluence/GitHub-search collection to
daily-recap at 1.1.0. The originally planned follow-up — a standalone
weekly-standup skill with its own RAG thresholds — was superseded after
Patrick approved adopting Platform's RAG standup format and the AI team
flagged that a weekly skill should reuse daily-recap's machinery rather
than duplicate it. This PR carries both changes together: the RAG
format/linking rework and the weekly-recap reuse.

What changed

  • Standup card → Platform RAG format: the card now renders a
    data-rag status dot (green/amber/red, driven by CSS, not emoji), a
    one-sentence BLUF line, and three labeled sections — Yesterday/Today/
    Blockers for daily recaps, Last week/This week/Blockers for weekly
    recaps — behind a shared {{STANDUP_PERIOD}} header.
    {{STANDUP_SHIPPED}} is renamed to {{STANDUP_PAST}}.
  • Copy button emits Slack-ready RAG text with links: Copy no longer
    reads li.textContent (which silently dropped every link and the
    Blockers section). It now emits a Slack RAG shortcode
    (:large_green_circle: / :large_orange_circle: / :red_circle:)
    derived from data-rag, the BLUF line, and one backtick-labeled section
    per column, with bullets converted to markdown links
    (<a> → [text](href), href parens percent-encoded) and <code> →
    backticks. Empty sections print - None. The CSP script hash was
    recomputed in the same commit, and a new test asserts the hash matches
    the <script> block so a stale hash fails CI instead of silently
    disabling Copy.
  • Documented the RAG heuristic and bullet rules: the RAG color is
    derived from in-window evidence only (returned issues, Blocked
    transitions, PRs with changes requested, open/deferred streams); any
    blocker means at least amber, and mixed signals mean amber over green.
    Bullets are outcome-first, with the Jira key or PR linked on the noun it
    refers to. Stream cards and the timeline keep their existing #1234 +
    title link pattern, unchanged.
  • New weekly-recap skill: a 48-line SKILL.md that defines only
    what differs for a week (default window: most recently completed
    Mon–Fri; "this week" = Monday through today) and reuses
    daily-recap's scripts, template, and reference docs via
    ${CLAUDE_PLUGIN_ROOT} — no files are copied. daily-recap drops its
    "this week" trigger phrase so only weekly-recap owns weekly requests.
  • 1.1.0 → 1.2.0: version bumped in plugin.json, marketplace.json,
    and the plugin README catalog table, with a CHANGELOG entry covering
    the weekly-recap skill, the RAG standup card, RAG Copy with links, and
    the bullet-rule docs. Plugin docs (description, README skill table,
    daily-recap README) were synced to match, and a cspell/Prettier drift
    introduced along the way was fixed. pnpm run lint is green on this
    branch.

Design trade-off: RAG from in-window evidence only

The RAG color has no current-state snapshot — it is derived entirely from
activity that happened inside the recap window (see ADR-097, which
resolves ADR-096's earlier deferred "weekly-standup current-state
snapshot" idea as not built). This is a known, accepted trade-off: work
that is stuck but has no in-window activity (no returned issue, no status
transition, no PR update) can be missed by the RAG signal. A prior
current-state-snapshot attempt was removed after repeated bugs rooted in a
time-semantics mismatch (ADR-096), so this PR does not reintroduce one.

Before / after: Copy output

Before (li.textContent-based): links and the Blockers section were
silently dropped, e.g.:

Shipped auth bugfix
Reviewed PR for teammate

After (Platform RAG shape — verified byte-for-byte via headless
Chrome, 457-byte output): a Slack RAG shortcode, a BLUF line, and
backtick-labeled sections with inline markdown links on the noun, matching
the format used previously by the bitwarden-standup plugin's
synthesize-standup-report skill:

:large_green_circle: Shipped the auth bugfix and unblocked review on the SDK PR.

`Yesterday:`
- Fixed the [auth bugfix](https://github.com/org/repo/pull/123)
- Reviewed [PM-12345](https://bitwarden.atlassian.net/browse/PM-12345)

`Today:`
- Continue work on [PM-12346](https://bitwarden.atlassian.net/browse/PM-12346)

`Blockers:`
- None

(Illustrative — reflects the verified output shape, not a literal
captured transcript.) Whether markdown links pasted into Slack actually
render as clickable links is still an open question, deferred to
acceptance verification for this change.

Testing

  • daily-recap.test.sh: 254/254 passed, including a new test asserting
    the CSP script hash matches the <script> block.
  • Headless Chrome verification: script runs under the new CSP hash, is
    blocked when the hash is altered, and Copy produces the expected RAG
    text byte-for-byte.
  • pnpm run lint (prettier + spelling) is green on this branch.
  • Live-run verification of both daily-recap and weekly-recap skills,
    and whether Slack renders the pasted markdown links, is deferred to the
    follow-up "Acceptance verification" task — not covered by this PR.

Related

Screenshot 2026-10-30 at 3 40 11 PM

Platform's standup format is RAG-first: a status color and one-sentence
bottom line, then Yesterday/Today/Blockers (Last week/This week/Blockers
for weekly recaps). Restructure the daily-recap template to match.

- Add `data-rag` (green/amber/red) driving a CSS status dot and
  left-border color, not emoji, so rendering doesn't depend on emoji
  support
- Add a one-sentence BLUF line
- Replace the single shipped list with three sections: past / next /
  blockers, using placeholder labels and a `{{STANDUP_PERIOD}}` header
  so a weekly recap can reuse the same template
- Rename `{{STANDUP_SHIPPED}}` to `{{STANDUP_PAST}}`
- Update render-guide.md to document the new placeholders

The `<script>` block and its CSP hash are intentionally unchanged here.
The Copy output still produces the old shape and will move to the RAG
format in a follow-up commit; until then Copy omits the Blockers
section.

Refs: PM-46025
Copy built its text from `li.textContent`, which dropped every link and
left out the Blockers section added in 09f49fd. Platform's standup is
posted to Slack, so the copied text has to match Platform's RAG
template.

- Copy now produces: a Slack RAG shortcode (`:large_green_circle:` /
  `:large_orange_circle:` / `:red_circle:` from `data-rag`), then the
  BLUF line, a blank line, and one section per standup column. Each
  section has a backtick label taken from the column's rendered `<h3>`,
  so weekly recaps get "Last week"/"This week" with no code change.
- Bullets keep their links as markdown: `<a>` becomes `[text](href)`,
  with parentheses in the href percent-encoded, and `<code>` becomes
  backticks. An empty section prints `- None`.
- The CSP `sha256` hash was recomputed for the new script.
- A test now checks that the CSP hash matches the `<script>` block, so
  a stale hash fails the test suite instead of silently disabling
  filters and Copy.
- Verified: `daily-recap.test.sh` passes (254 passed, 0 failed).
  Headless Chrome shows the script runs under the new hash, is blocked
  when the hash is altered, and Copy produces the expected text byte
  for byte.

Refs: PM-46025
…up rules

09f49fd and 204333d switched the standup card to the Platform RAG format
and made Copy emit linked text, but nothing told the skill how to fill
the new placeholders. Document that here:

- The RAG color is derived from in-window evidence only (ADR-097):
  returned issues, Blocked transitions, PRs with changes requested, and
  open or deferred streams. Any blocker means at least amber; mixed
  signals also mean amber.
- The BLUF is one HTML-escaped sentence.
- What belongs in the past, next, and blockers sections.
- Bullets are outcome-first, with each Jira key or PR linked on the
  noun it refers to.
- Stream cards and the timeline keep the `#1234` + title link pattern,
  unchanged from the standup card's rules.

Refs: PM-46025
daily-recap already handled weeks through a per-day loop, and
Platform's standup format (approved for both daily and weekly) needs
"Last week"/"This week" labels. Rather than a standalone weekly skill
with copied scripts, weekly-recap is a thin SKILL.md that defines only
what differs for a week and reuses daily-recap's scripts, template,
and references through `${CLAUDE_PLUGIN_ROOT}`. Moving the weekly
trigger phrases to weekly-recap means only one skill owns weekly
requests.

- New `weekly-recap` skill: default window is the most recently
  completed Mon-Fri week; "this week" is Monday through today; day
  boundaries use daily-recap's cutoff hour. Per-day runs of
  gather-gh-events, find-sessions, and gather-atlassian; one ranged
  gather-gh-search call. Labels Last week/This week,
  `{{STANDUP_PERIOD}}` `Week · {range}`, page file
  `recap-week-YYYY-MM-DD.html`, title text "Weekly Recap", Today
  banner skipped, a day divider per day in the timeline.
- daily-recap: drop "this week" from the description; weeks now point
  to weekly-recap, other multi-day ranges keep the per-day loop.
- render-guide: document the weekly `{{STANDUP_PERIOD}}` form.
- No scripts or template copied; `skills/weekly-recap/` holds only
  SKILL.md.

Refs: PM-46025
PM-46025: bump bitwarden-retrospective-tools from 1.1.0 to 1.2.0 in
plugin.json, marketplace.json, and the README catalog table.

This is the first of four child TODOs splitting out a previously
oversized "bump to 1.2.0 with CHANGELOG and README" task. CHANGELOG
entries, plugin description text, and README skill-list/cspell fixes
are deliberately deferred to three sibling TODOs and are not part of
this commit.
Document the weekly-recap skill, the Platform RAG standup card
format, and the standup bullet rules as a new 1.2.0 release
section in CHANGELOG.md, completing the Keep-a-Changelog record
for changes already landed on this branch.

Refs: PM-46025
plugin.json's description still described a daily-only recap from
before the weekly-recap skill existed. The plugin README's skill
table never got a weekly-recap row when that skill was added
(0a03d78). The daily-recap README also lacked a note about the
standup card's Copy button now preserving markdown links and code
instead of stripping them (204333d).

Closes out doc-sync gaps flagged by two prior sessions in this hub
chain. This is a pre-decomposed child of the larger 1.2.0 bump TODO;
sibling commits (version bump, CHANGELOG) already landed on this
branch.

Refs: PM-46025
… drift

The plugin's cspell dictionary was missing "BLUF", introduced earlier
in this chain (204333d/09f49fd's RAG standup wording) and flagged by
`pnpm run lint:spelling`. The retrospective-tools README's skill table
had also drifted out of Prettier's table-column formatting after the
weekly-recap row was added (0a03d78), tripping `lint:prettier`.

This is the fourth and final child of the 4-way split of the 1.2.0
bump/CHANGELOG/doc-sync/lint TODO chain on this branch. With this
commit, `pnpm run lint` exits clean, unblocking the PR-open TODO.

Refs: PM-46025
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Claude Code validation

Result: Pass

Validated the bitwarden-retrospective-tools plugin (manifest, versioning, structure, credentials), both changed SKILL.md files, and the changed skill support files (assets/template.html, references/render-guide.md, scripts/daily-recap.test.sh). The 1.1.0 → 1.2.0 bump is consistent across plugin.json, marketplace.json, and the README catalog table, with a matching Keep a Changelog entry; no credentials, no prompt-injection or reviewer-directed text, and every new {{STANDUP_*}} placeholder is documented in the render guide and present in the template. Five findings, none blocking — the one worth acting on before merge is an unguarded getAttribute("href") in the new Copy code.

Not covered: The template's CSP sha256- script hash could not be recomputed and compared locally — perl, openssl, and python3 are all blocked by this runner's command sandbox. The new test case [16] added in scripts/daily-recap.test.sh asserts exactly this, so a CI run of daily-recap.test.sh settles it; note that case self-skips when perl or openssl is absent.

Findings
  • ⚠️ IMPORTANT: Finding 1: getAttribute("href") is dereferenced without a null guard, so one <a> with no href throws and silently kills the whole Copy button (plugins/bitwarden-retrospective-tools/skills/daily-recap/assets/template.html:1278)

  • 🎨 SUGGESTED: Finding 2: Marketplace description still advertises a daily-only plugin after plugin.json's was rewritten to cover weekly recaps and the RAG format (.claude-plugin/marketplace.json:136)

  • 🎨 SUGGESTED: Finding 3: Dropping "this week" left no multi-day trigger in either skill's description, while the body still documents a multi-day procedure (plugins/bitwarden-retrospective-tools/skills/daily-recap/SKILL.md:5)

  • 🎨 SUGGESTED: Finding 4: A stale "this week" routing instruction now contradicts the new redirect at line 27 (plugins/bitwarden-retrospective-tools/skills/daily-recap/SKILL.md:62)

    Details and fix

    Line 27 was rewritten by this PR to send every weekly request to weekly-recap. Line 62, in the gather-gh-search.sh paragraph, still reads:

    One date is a single workday, the same window as gather-gh-events.sh. Two dates run from the cutoff on START-DATE to the cutoff on the day after END-DATE, so for "this week" or another range, run it once with both dates instead of once per day.

    So step 1 still tells daily-recap how to collect a week at the same time the date-choosing section says a week belongs to another skill. A model that reads the workflow before the date-choosing bullets can conclude daily-recap owns weeks after all.

    Fix — drop the week phrasing, since the surviving multi-day branch at line 28 is what this sentence now serves:

    ...to the cutoff on the day after END-DATE, so for a multi-day range, run it once with both dates instead of once per day.
    

    (Not posted inline: line 62 is not part of this PR's diff, so GitHub cannot anchor a comment to it.)

  • 🎨 SUGGESTED: Finding 5: The new weekly-recap row has no "See its README" link, unlike every other skill row in this plugin (plugins/bitwarden-retrospective-tools/README.md:16)

Dropped after documentation check: Three findings — raised independently by the plugin validator and both skill reviewers, one of them at CRITICAL — claimed that weekly-recap/SKILL.md:19 is self-contradictory or expands to /daily-recaps at the filesystem root, and that the ${CLAUDE_PLUGIN_ROOT} references at weekly-recap/SKILL.md:18/:29 and daily-recap/SKILL.md:27 are unusable. The plugin manifest reference contradicts all three: its "Where each variable resolves" table gives Skill, command, and agent content | Anywhere in the Markdown body, and the page states "The variables aren't present in the environment of commands Claude runs through the Bash tool, in the main session or in a subagent. In skill, command, and agent content, write the ${...} reference in the Markdown body instead, and Claude Code substitutes the path inline when it loads the content." ${CLAUDE_PLUGIN_DATA} is documented as resolving to ~/.claude/plugins/data/<id>/, "created on first reference". So weekly-recap's own body is substituted at load time and resolves correctly, while daily-recap/SKILL.md read through the Read tool is not — which is precisely the asymmetry line 19 exists to handle. The byte-identical replacement string is the point, not a bug.

Comment on lines +1276 to +1281
} else if (child.tagName === "A") {
const href = child
.getAttribute("href")
.replace(/\(/g, "%28")
.replace(/\)/g, "%29");
out += `[${collapse(child.textContent)}](${href})`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ IMPORTANT: getAttribute("href") is dereferenced without a null guard, so one <a> with no href throws and silently kills the whole Copy button.

Details and fix

child.getAttribute("href") returns null for an anchor that carries no href attribute, and .replace() on null throws a TypeError. The throw happens inside the click listener before flashCopyState runs, so the button keeps reading "Copy", nothing reaches the clipboard, and the user gets no error — the exact silent-failure mode this PR set out to fix in the old li.textContent path.

The render guide does tell the generating model to render a Jira key with a null url as plain text (references/render-guide.md:104), but that is prose guidance to an LLM, not a guarantee about the DOM. One malformed anchor takes out Copy for the entire page.

Falling through to the existing else branch degrades correctly: the anchor's text still appears in the output, just unlinked.

Suggested change
} else if (child.tagName === "A") {
const href = child
.getAttribute("href")
.replace(/\(/g, "%28")
.replace(/\)/g, "%29");
out += `[${collapse(child.textContent)}](${href})`;
} else if (child.tagName === "A" && child.hasAttribute("href")) {
const href = child
.getAttribute("href")
.replace(/\(/g, "%28")
.replace(/\)/g, "%29");
out += `[${collapse(child.textContent)}](${href})`;

Because this edits the inline <script> block, the CSP sha256- hash in the <meta> tag at line 14 has to be regenerated alongside it — the new test case [16] added in scripts/daily-recap.test.sh will fail otherwise.

validate-ai

"source": "./plugins/bitwarden-retrospective-tools",
"version": "1.1.0",
"version": "1.2.0",
"description": "Tools for looking back on your own engineering work: a daily work recap built from your Claude Code session logs, your GitHub activity, and your Jira and Confluence activity."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎨 SUGGESTED: The marketplace description still advertises a daily-only plugin after plugin.json's was rewritten to cover weekly recaps and the RAG format.

Details and fix

This PR changed plugins/bitwarden-retrospective-tools/.claude-plugin/plugin.json:4 to "a daily or weekly work recap, in Platform RAG standup format, built from…" and shipped the weekly-recap skill, but left this entry at the 1.1.0 text. The marketplace entry is what users read when browsing the catalog, so it now undersells what 1.2.0 contains.

For accuracy: byte-identical descriptions are the majority convention here but not a hard rule — 17 of 23 entries match their manifest exactly and 5 others already diverge for unrelated reasons. The point is the staleness relative to this changeset, not the mismatch per se.

Suggested change
"description": "Tools for looking back on your own engineering work: a daily work recap built from your Claude Code session logs, your GitHub activity, and your Jira and Confluence activity."
"description": "Tools for looking back on your own engineering work: a daily or weekly work recap, in Platform RAG standup format, built from your Claude Code session logs, your GitHub activity, and your Jira and Confluence activity."

validate-ai

context: fork
agent: general-purpose
description: Generates an interactive HTML recap of the user's own engineering work over the time window given as arguments, ready for standup, 1:1 prep, or sharing with colleagues. Use when the user asks what they worked on, shipped, merged, pushed, reviewed, or moved tickets and docs on over a time window ("yesterday", "this morning", "last night", "since standup", "this week", a specific date), or asks for a "recap", "daily summary", "standup notes", "morning recap", or "1:1 prep", including phrasings like "throw together what I did yesterday" or "something I can show my manager". Not for looking up a single specific PR, commit, ticket, or document, for someone else's activity, for meeting recaps, for repository-level overviews, or for critiquing how a single Claude Code session went.
description: Generates an interactive HTML recap of the user's own engineering work over the time window given as arguments, ready for standup, 1:1 prep, or sharing with colleagues. Use when the user asks what they worked on, shipped, merged, pushed, reviewed, or moved tickets and docs on over a time window ("yesterday", "this morning", "last night", "since standup", a specific date), or asks for a "recap", "daily summary", "standup notes", "morning recap", or "1:1 prep", including phrasings like "throw together what I did yesterday" or "something I can show my manager". Not for looking up a single specific PR, commit, ticket, or document, for someone else's activity, for meeting recaps, for repository-level overviews, or for critiquing how a single Claude Code session went.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎨 SUGGESTED: Dropping "this week" left no multi-day trigger in either skill's description, while the body still documents a multi-day procedure.

Details and fix

Every time cue remaining in this description is single-day: "yesterday", "this morning", "last night", "since standup", a specific date. The new weekly-recap description claims only Monday-to-Friday weeks and explicitly excludes anything shorter.

But line 28 of this file still carries the Another multi-day range: branch with full per-day collection instructions. So "recap the last three days", "what did I ship since Monday", or "this sprint" now match neither description, and the procedure at line 28 has nothing routing to it.

Add one non-week range cue to the trigger list, leaving full weeks to weekly-recap:

..."since standup", a specific date, or a date range such as "the last three days"), or asks for a...

Reference: https://code.claude.com/docs/en/skills

validate-ai

| Skill | What It Does |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `daily-recap` | Builds an interactive HTML recap of one workday from your Claude Code sessions and GitHub events, with a standup card, themed work streams, and a filterable GitHub timeline. See [its README](skills/daily-recap/README.md). |
| `weekly-recap` | A thin skill that reuses daily-recap's scripts, template, and references to build the same recap over a Monday-to-Friday workweek instead of a single day. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎨 SUGGESTED: The new weekly-recap row has no "See its README" link, unlike every other skill row in this plugin.

Details and fix

daily-recap on line 15 links to skills/daily-recap/README.md, which documents its environment variables (DAILY_RECAP_CUTOFF_HOUR, DAILY_RECAP_OUTPUT_DIR, the Atlassian tokens), where output lands, and its prerequisites. weekly-recap ships with no such file, so a user who wants to know which of those settings apply to a weekly run, or where recap-week-*.html is written, has to read the agent-facing SKILL.md.

The skill is deliberately thin, so a short README that states what it inherits from daily-recap and what differs would be enough — no need to duplicate the daily-recap content.

Add plugins/bitwarden-retrospective-tools/skills/weekly-recap/README.md and link it from this row:

| `weekly-recap` | A thin skill that reuses daily-recap's scripts, template, and references to build the same recap over a Monday-to-Friday workweek instead of a single day. See [its README](skills/weekly-recap/README.md). |

validate-ai

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant