Claude/tooling/allowlist script - #2
Open
jeffoberlander wants to merge 50 commits into
Open
Conversation
Migrate to Cloud Run with GCP Secret Manager and Cloud Storage
add cloudbuild.yaml
Add Google OAuth2 authentication and CI/CD pipeline
Tasks that would round down to 0 days should be counted as 0.5 days minimum because some effort was expended!
Fixed 0.5 day rounding on 0 day round down tasks
- Add /history page showing completed retrospectives grouped by quarter - Add /api/history?project=<key> endpoint returning quarter-grouped data - Add History button next to Analytics button on the main page - Add History link to the Analytics page nav - Store epicSummary when saving a retrospective (getEpicAndIssues already runs in the /run handler so no extra Jira call is needed) - Add getRetrospectivesByProject() to storage layer for project-scoped queries - Add src/domain/history.ts with groupByQuarter() pure helper - Add 7 unit tests covering groupByQuarter() edge cases - Wire up vitest as the test runner
- /api/history now accepts project=all (or no param) to return all projects - Each entry now includes jiraUrl and projectName in the API response - History table shows epic title as the linked text (not the key) pointing to the correct Jira URL - Removed redundant separate Epic key + Retrospective Name columns; single Epic column now carries the full title linked to Jira - Cross-project view adds a Project column showing the team/workspace name - Project selector defaults to All Projects with individual projects below - Empty state updated: tells users retros appear after generation on the home page, with a direct link back - Back link from All Projects view goes to / rather than /?project=all
getFullYear/getMonth are local-time and can misassign generatedAt values near quarter boundaries when the server timezone differs from UTC. Switch all date reads in getQuarterLabel and groupByQuarter to the UTC equivalents (getUTCFullYear, getUTCMonth) so quarter assignment is consistent with the ISO timestamps stored in retrospectives.json.
updateNavLinks() was inside the projects.length > 0 guard, leaving the History and Analytics hrefs stuck at '#' when the project list is empty. Hoist updateNavLinks() out of the guard so the links always resolve; loadEpics() remains gated on projects being available.
…rospective-page BTT-551 Add a "History" Retrospective page per project space
… to new retrospective documents
…development-permissions-sesn_01Vgf6729N66QXr6HpKJZprL BTT-554 Add productdevelopment@curiouslearning.org editor permissions to retrospective documents
In both the project-specific and All Projects History views, split the existing Epic column into: - Epic: shows the epic key (e.g. BTT-123) as a Jira link - Summary: new column immediately to the right showing the epic's summary text (epicSummary)
…-column-to-history BTT-555 Add "Summary" column to History table
- Add overwriteDoc() to google.ts that clears and rewrites an existing Google Doc without creating a new one, moving it, or re-sharing it. Formatting passes (styles, bold labels, bullets, Jira hyperlinks) are shared with createDoc via the new private applyDocFormatting helper. - Update buildRetrospective() to accept an optional existingDocumentUrl. When provided the doc ID is extracted from the URL and overwriteDoc is called; the same URL is returned so the History entry stays unchanged. - Update POST /run to accept existing_document_url in the request body and forward it to buildRetrospective. - On the main page, epics that already have a retrospective now show a 'Regenerate Retrospective' button alongside the existing doc link. Clicking it calls the /run endpoint with the existing document URL, overwrites the doc in-place, and updates the displayed timestamp. - Add tests for overwriteDoc covering content deletion, insertion, skipping empty-doc deletion, no documents.create call, and no re-sharing via permissions.create.
…n /run Before passing existing_document_url to buildRetrospective, load the stored retrospective for the given epic_key and reject with HTTP 400 if the supplied URL does not match. This prevents the destructive overwrite path from being triggered against arbitrary document URLs that were not created by this application for the requested epic.
…retrospective BTT-553 Allow retrospective to be re-generated from the main page
- Add addWorkingDays and advanceToMonday helpers to workingDays.ts - Add anticipatedCompletion.ts domain module with computeAnticipatedCompletionDate and formatDateMMDDYYYY - Add getOpenTicketCount to jira.ts (queries To Do / In Progress / Review/QA statuses) - Add computeProjectAverageCycleTime to analytics.ts (last 90 days of completed issues) - Update /api/epics endpoint to include anticipatedCompletionDate per epic - Update main page UI to display 'Anticipated completion date:' below the tasks completed line - Add 12 tests covering the new domain logic
…ate display - calculateCycleTimesForIssues: replace serial for-loop with bounded concurrency (5 parallel changelog fetches) to unblock /api/epics - computeProjectAverageCycleTime: add 5-minute TTL in-process cache keyed by projectKey+daysBack so repeated /api/epics calls skip the fetch entirely - progressHtml zero-task branch: append anticipatedDateHtml so epics with no tracked tasks still render the anticipated completion date when available
…d-completion-date BTT-560 Add "Anticipated completion date" to open epic cards
computeProjectAverageCycleTime can fail (Jira API error, project with no
completed-issue history). Previously it shared a Promise.all with
getOngoingEpics, so any failure killed the entire response and the browser
received {error:...} instead of an array, causing 'epics.map is not a
function'. Now the call falls back to 0 on error, epics always load, and
the anticipated date is simply omitted when cycle-time data is unavailable.
…d-completion-date BTT-560 Fix "epics.map is not a function" error on main page load
Two bugs in getOpenTicketCount caused every /api/epics request to 500: 1. fields: [] is rejected by the Jira Cloud search API; changed to fields: ["status"] to match the working pattern in getEpicProgress. Switched from result.total (unreliable with maxResults:0) to result.issues.length for consistency. 2. Any Jira error from getOpenTicketCount propagated uncaught through the per-epic Promise.all and killed the entire response. Added .catch(() => 0) at the call site so a failure omits the anticipated date for that epic without taking down the rest of the page.
…d-completion-date BTT-560 Fix getOpenTicketCount crashing /api/epics with 500
The redeploy command in README pointed at an image tag that does not exist -- cloudbuild.yaml only ever tags $COMMIT_SHA, so following the doc verbatim failed with "Image not found". Look up the image the service is currently running instead. Also document that a new Secret Manager version replaces the entire payload rather than appending, since retrospective-allowed-emails holds a comma-separated list and the old example invited overwriting it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Managing app access was a four-step gcloud procedure documented in prose: read the secret, append to the comma-separated list, write the whole value back, then redeploy the running image to force it live. Every step had a way to go wrong that silently revokes access -- writing just the new address drops everyone else, echo leaves a newline inside the last address, and the image reference has to be looked up because cloudbuild.yaml only ever tags $COMMIT_SHA. Put those commands in one place instead. scripts/allowlist.sh does the read-modify-write with dedupe, a --dry-run preview, verification that the new version matches what was written, and guards against emptying the list or removing your own account. .claude/skills/allowlist/SKILL.md points Claude at the script so "add Jan to the app" is a one-line request rather than a procedure to re-derive. README now points at the script and keeps the explanation of why the read-modify-write is necessary. Also drops the last :latest reference, in the manual deploy snippet -- that tag is never published either. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
secretAccessor grants only secretmanager.versions.access -- it does not imply writing a version or even listing them. So the role we hand new developers so they can run the app locally is exactly the role that lets someone run `list` and then fail on `add`, with a raw gcloud PERMISSION_DENIED as the only clue. Spell the full set out in README, and name the specific missing role at each point of failure in the script. Redeploying additionally needs run.developer plus serviceAccountUser on the runtime service account (959872421018-compute@developer), because deploying a service that runs as a service account requires acting as it. Also fixes a race the permission testing turned up: write_version verified by reading `latest`, which immediately after a write can still return the previous version -- reporting a successful write as a failure. It now reads back the exact version it just created, and if that ever does mismatch, says the version was created rather than implying nothing happened. read_version also keeps stderr out of the payload, so a gcloud warning can no longer be parsed as an email address. Co-Authored-By: Claude Opus 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.
Added a script to allow someone with the right GCP permissions to modify the Google Secret list of authorized users