Add sprint summary PDF report (standalone) - #96
Merged
Conversation
For a user-picked date range and set of changes, generates a downloadable PDF: per change, git-derived authorship (primary author = most recent commit touching the change's directory -- the most representative single answer given this repo's own squash-merge convention), dates, task completion, a plain-text "Why" excerpt; plus aggregate statistics (total changes, tasks completed within the range, a per-author breakdown). New git primitive (getChangeAuthorship) -- no prior code in this repo extracted author identity, only timestamps. New pdfkit dependency (pure JS, no browser/native deps, npm audit --omit=dev confirmed clean). First non-JSON response this server's REST layer has ever sent, mirroring static.ts's existing raw-Buffer pattern. Manually verified against real data: generated an actual PDF for five of this session's own real archived changes and confirmed correct authorship, dates, and statistics. VS Code command comes next, as its own change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CI caught this on PR #96 (Extension integration and package job): "Activating extension ... failed: Invalid URL". Repro'd locally -- pdfkit's package.json "exports" resolves a bare `import` to its ESM build (js/pdfkit.node.mjs), which uses real `import.meta.url` syntax. esbuild cannot preserve that when bundling the extension host to a single CJS file (`format: "cjs", platform: "node"` in build-options.mjs) and substitutes an empty object, so pdfkit's top-level `new URL("./data/...", undefined)` throws at module load -- which crashed the whole extension's activation, not just sprint-report code, since core's index.ts barrel pulls pdfkit in for every consumer. Fix: require("pdfkit") via a createRequire, forcing resolution through the package's `require` condition (js/pdfkit.js) instead -- a genuine CommonJS build that reads `__filename` rather than `import.meta.url`, which esbuild *can* shim correctly for a node/cjs bundle. `__filename` is unavailable in this file's own plain-ESM runtime (server/core, unbundled), hence the `typeof __filename !== "undefined"` guard -- `typeof` never throws on an undeclared identifier. Verified: rebuilt dist/extension.js and loaded it in plain Node with a stubbed `vscode` module -- activation now succeeds. All previously passing core/server/webui/extension suites still pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5 tasks
VeryComplexAndLongName
added a commit
that referenced
this pull request
Aug 27, 2026
Second half of the sprint-report feature (add-sprint-report-pdf, PR #96, shipped the standalone-only half). New Command Palette command "OpenSpec UI: Generate Sprint Report (PDF)": reuses pickChangesForTimeline unchanged, adds two validated YYYY-MM-DD date prompts (VS Code has no native date picker, and this needs a real user-specified range, not an auto-derived one), calls buildSprintReport/renderSprintReportPdf from @openspec-ui/core directly (per ADR-0001 -- no server/REST round-trip), then showSaveDialog -> workspace.fs.writeFile -> a confirmation message with an "Open" action. This is also the first code path that actually executes renderSprintReportPdf from inside the bundled extension -- which re-exposed the pdfkit/esbuild bundling hazard PR #96's CI caught (esbuild's CJS bundle can't shim pdfkit's ESM build's import.meta.url). The createRequire-based workaround from that PR fixed activation but silently stopped esbuild from inlining pdfkit at all (a local variable named `require`, however constructed, isn't recognized as a bundleable require once esbuild renames it during bundling -- confirmed by inspecting the built dist/extension.js, which dropped from 2.8MB to 635KB and left a real runtime `require("pdfkit")` call). Since `vsce package --no-dependencies` ships no node_modules, that would have failed at runtime with "Cannot find module 'pdfkit'" the first time this command actually ran. Reverted sprint-report-pdf.ts to a plain `import PDFDocument from "pdfkit"` (correct as-is for every plain-Node/ESM consumer -- server, core's own tests) and instead fixed the extension-only bundling problem in the extension's own esbuild config: an `alias` mapping "pdfkit" to its real CommonJS build file at bundle time (build-options.mjs), which genuinely inlines it and reads __filename instead of import.meta.url. Verified: rebuilt dist/extension.js and loaded it in plain Node with a stubbed vscode module -- activation succeeds, and the bundle contains pdfkit's real CommonJS build inline (grep for ICC_PROFILE_PATH's pathToFileURL(__filename) form, not import_meta2.url). Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
getChangeAuthorship— no prior code in this repo extracted author name/email, only timestamps. Primary author = most recent commit touching the change's directory (most representative given this repo's own squash-merge convention);contributorslists every distinct author.pdfkitdependency (pure JS, no browser/native deps) — confirmed via grep across all 6package.jsonfiles that no PDF library existed anywhere before this.npm audit --omit=dev --audit-level=high(this repo's actual CI gate) reports zero vulnerabilities with it installed.application/pdf+content-disposition: attachment), mirroringstatic.ts's existing raw-Buffer pattern rather than inventing a JSON-base64 convention.add-sprint-report-vscode-command).@openspec-ui/core0.28.0 -> 0.29.0,@openspec-ui/server1.9.0 -> 1.10.0,@openspec-ui/webui1.14.0 -> 1.15.0 (all minor).Verification note
Manually generated a real PDF against this repository's own actual data: five of this session's real archived changes, confirmed correct authorship, dates, task counts, and aggregate statistics (
filereports "PDF document, version 1.3, 2 page(s)").Test plan
getChangeAuthorshiptests against a real temp git repo fixture (primary author, contributors, empty-authorship cases)buildSprintReporttests: aggregation, date-range task filtering, a change kept despite starting before the range, author ranking, empty reportrenderSprintReportPdftests: real PDF magic bytes/trailer, empty reportcontent-type/content-disposition, 400 on a missing date rangesprint-report-client.test.tsnpm run typecheck,npm run lint(includinglint:english),npm run testworkspace-widenpm audit --omit=dev --audit-level=highopenspec change validate --strict add-sprint-report-pdf🤖 Generated with Claude Code