-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Night Watch PM
Assigned to: Cindy's Navi (cixzhang)
Goal: Keep tasks, issues, and wiki tidy and navigable.
Review all open issues for completeness:
gh issue list --repo facebook/astryx --state open --limit 100 \
--json number,title,labels,body,comments,createdAt \
--jq '.[] | {number, title, labels: [.labels[].name], bodyLength: (.body | length), commentCount: (.comments | length)}'An implementable issue needs:
| Category | Required | If Missing |
|---|---|---|
enhancement (new component) |
API sketch (props), visual reference, usage example | Comment asking for the missing piece |
enhancement (feature) |
Which component, behavior changes, before/after | Comment asking for specifics |
bug |
Steps to reproduce, expected vs actual, browser/version | Comment asking for repro steps |
discussion |
A clear question or decision | No action — discussions are open-ended |
vibe-test |
Auto-generated | Skip |
Comment template:
This issue needs a bit more detail before it can be picked up for implementation: - [ ] {specific missing item} - [ ] {specific missing item} Once these are filled in, this is ready to build. Happy to help draft any of these if useful.
Do NOT comment on issues that:
- Already have a linked PR
- Were filed in the last 24 hours (give the author time)
- Are labeled
discussionordesign - Already have a triage comment from a previous run
Issues — if an issue is missing labels, add them:
- Component name mentioned →
component - Bug report →
bug - Feature request →
enhancement - Theming related →
theming - Unclear scope →
discussion
gh issue edit {number} --repo facebook/astryx --add-label "{label}"Pull requests — every open PR should carry labels on three axes (see the
Pull Request Labels section of CONTRIBUTING.md for the full definitions).
Find open PRs missing our taxonomy and tag them:
# Open PRs that have no area:/type: label yet (ignore the CLA bot + dependabot labels) gh pr list --repo facebook/astryx --state open --limit 200 \ --json number,title,labels \ --jq '.[] | select([.labels[].name] | any(startswith("area:") or startswith("type:")) | not) | {number, title}'
For each, apply labels from the taxonomy based on the PR's files and title:
-
area:*(one or more) — from the paths touched:packages/cli→area:cli,packages/core→area:core,Table→area:tables, charts →area:charts, theme/packages/themes→area:theming,apps/docsite→area:docs,apps/storybook→area:storybook,apps/example-*→area:examples,packages/lab→area:lab, and addarea:a11yalongside the component area when the change touches ARIA/roles/keyboard/focus/accessibility. -
type:*(exactly one) — a brand-new component file →type:new-component; otherwise from the conventional-commit prefix:feat→type:feature,fix→type:fix,docs→type:docs,test→type:test; example-app-only →type:example; lockfile/dependency bumps →deps. Leave the type off if it's pure build/CI/tooling (tsconfig, workflows, Flow) that fits none of these — don't force it. -
needs:*(opt-in) — addneeds:api-reviewwhen it touches public/exported API (new component, new props, new exports); addneeds:design-reviewwhen it affects visuals (StyleX/styles, theme tokens, layout, Storybook).
gh pr edit --add-label can fail on this node when the token lacks read:org
(it resolves org reviewers). Use the REST issues-labels endpoint instead — it works for PRs:
gh api -X POST "repos/facebook/astryx/issues/{number}/labels" \ -f "labels[]=area:core" -f "labels[]=type:fix" -f "labels[]=needs:design-review"
Skip PRs that already carry an area:/type: label (they were triaged on a prior run).
Maintain the wiki as a clean, navigable knowledge base. Clone the wiki repo and run these checks:
git clone https://github.com/facebook/astryx.wiki.git /tmp/xds-wiki
cd /tmp/xds-wiki| Rule | Check | Fix |
|---|---|---|
| No broken links | Every [[Page Name]] in every .md file must resolve to an existing file |
Fix the link or remove it. If the target page should exist, create a stub. |
| No orphan pages | Every page must be reachable from Home (directly or via a linked page) | Add link from the appropriate section, or move to Research Archive if historical. |
| Home stays current | Home links to all major active architecture/process pages | Add missing links. Remove links to pages that no longer exist. |
| Home descriptions accurate | Every link on Home has a description that matches the page's actual content | Read the linked page and update the Home description if it's stale or misleading. |
| Night Watch roles linked from Roles page | All Night-Watch-*.md files must be linked from Night Watch Roles, NOT from Home directly |
Add missing role links to Night-Watch-Roles.md. |
| Historical pages in archive | Pages marked as explorations, not-implemented, or historical belong in Research Archive, not Home | Move the Home link to Research-Archive.md. |
| Stale pages flagged | Pages not updated in 30+ days get reviewed | If still accurate, leave. If outdated, update or flag with a note. If irrelevant, move to Research Archive. |
| No pipe-syntax links |
[[display text|slug]] does not do what it looks like: GitHub treats the text after the pipe as the page name, so [[Component Audit Rubric|the rubric]] links to a non-existent page called "the rubric". |
Use [[Page Name]], or a normal markdown link to the full wiki URL when you need custom text or an anchor. |
# Check all wiki links resolve cd /tmp/xds-wiki for f in *.md; do grep -oP '\[\[([^\]]+)\]\]' "$f" | sed 's/\[\[//;s/\]\]//' | while read title; do [[ "$title" == \#* ]] && continue # skip anchor links [[ "$title" == *"|"* ]] && { echo "PIPE-SYNTAX (links to the wrong page): $f -> [[$title]]"; continue; } file=$(echo "$title" | tr ' ' '-').md if [ ! -f "$file" ]; then echo "BROKEN: $f -> [[$title]]" fi done done
# Check for orphan pages (not linked from anywhere) for f in *.md; do [ "$f" = "Home.md" ] && continue title=$(basename "$f" .md | tr '-' ' ') if ! grep -rl "$title" --include='*.md' . | grep -qv "$f"; then echo "ORPHAN: $title ($f)" fi done
This check is mandatory every run. It catches pages that aren't orphans (they're linked from sub-pages) but are missing from Home's top-level navigation.
# Find pages not linked from Home that aren't Night Watch role sub-pages or Research Archive entries cd /tmp/xds-wiki HOME_LINKS=$(grep -oP '\[\[([^\]]+)\]\]' Home.md | sed 's/\[\[//;s/\]\]//' | tr ' ' '-') ARCHIVE_LINKS=$(grep -oP '\[\[([^\]]+)\]\]' Research-Archive.md | sed 's/\[\[//;s/\]\]//' | tr ' ' '-') ROLES_LINKS=$(grep -oP '\[\[([^\]]+)\]\]' Night-Watch-Roles.md | sed 's/\[\[//;s/\]\]//' | tr ' ' '-') for f in *.md; do [ "$f" = "Home.md" ] && continue slug=$(basename "$f" .md) # Skip if linked from Home echo "$HOME_LINKS" | grep -qx "$slug" && continue # Skip if linked from Research Archive echo "$ARCHIVE_LINKS" | grep -qx "$slug" && continue # Skip if it's a Night Watch role page linked from Roles echo "$ROLES_LINKS" | grep -qx "$slug" && continue # Skip Night-Watch-Roles itself (linked from Home via overview) [[ "$slug" == "Night-Watch-Roles" ]] && continue echo "NOT ON HOME: $slug" done
For each page reported as NOT ON HOME:
- Read the first few lines — is it an active process/architecture page, or historical/exploratory?
- Active pages (processes people follow, architecture docs, contribution guides) → add to the appropriate Home section
- Historical/exploratory pages → should already be in Research Archive. If not, add them there.
- Redirect pages (content moved elsewhere) → no action needed, they exist for link stability
For each link on Home.md:
- Open the linked page and read the first few sections
- Verify the Home description still matches the page content
- If the page has evolved significantly (new sections, changed scope), update the Home description
- If a link points to a page that's become irrelevant or historical, move it to Research Archive
- Check if any important new wiki pages exist that aren't linked from Home — add them to the right section
- Broken links: Fix or remove immediately.
- Stale Home descriptions: Update to reflect what the page actually covers today.
- Outdated content: If a page references old APIs, removed features, or superseded processes, update it. If the whole page is obsolete, move to Research Archive with a deprecation note at the top.
- Missing pages: If Home or another page references something important that should exist (e.g. a new architecture page), create a stub with a TODO.
- Stale formatting: Normalize wiki link syntax. No pipe links, no broken anchor references.
- Don't delete wiki pages (move to Research Archive instead)
- Don't rewrite content for style — only fix factual accuracy and broken links
- Don't add pages without checking they're linked from somewhere
- Don't modify the Research Archive structure without reason
After making changes, commit and push:
cd /tmp/xds-wiki git add -A git commit -m "wiki(night-watch): <summary of changes>" git push origin master
Run a thorough pass over open issues to close completed work and consolidate duplicates.
Start with 4a — it's the highest-value, most automatable step and must run every night.
This is the most important task hygiene step. Run it every night, no exceptions.
Goal: Find PRs merged since the last PM run, match them to open issues, close what's resolved.
# Step 1: Get PRs merged in the last 48 hours (covers gaps if a run was missed) MERGED_PRS=$(gh pr list --repo facebook/astryx --state merged --limit 50 \ --json number,title,body,mergedAt,labels,files \ --jq '[.[] | select(.mergedAt > (now - 172800 | strftime("%Y-%m-%dT%H:%M:%SZ")))]') echo "$MERGED_PRS" | jq '.[].title'
For each merged PR:
-
Check if it already closed an issue automatically (body contains "fixes #", "closes #", "resolves #" with a valid issue number). If yes, verify the issue is actually closed — if it's still open despite the keyword, close it now.
-
Extract the component name and scope from the PR title:
-
fix(Selector): forward HTML attrs→ component=Selector, type=fix -
feat: add Banner→ component=Banner, type=feat -
chore(deps): bump vitest→ skip (dependency bumps don't resolve feature issues)
-
-
Search open issues for matches:
gh issue list --repo facebook/astryx --state open \ --search "<component or keyword from PR title>" \ --json number,title,body,labels --limit 10 -
Read both the PR and candidate issues to confirm resolution. Match criteria:
Signal Confidence Action PR body explicitly says "addresses #X" or "related to #X" without closing keyword High Close the issue PR title is semantically equivalent to issue title (same component + same fix/feat) High Close the issue PR touches the exact files/behavior the issue describes High Close the issue PR adds a component that the issue requested High Close the issue PR title mentions the component but does different work Low Don't close Component name match alone without behavioral overlap Low Don't close -
Close matched issues:
gh issue close <number> --repo facebook/astryx --reason completed \ --comment "Resolved by #<PR number> (merged <date>)."
-
Partial matches — PR addresses some but not all of what the issue asks:
gh issue comment <number> --repo facebook/astryx \ --body "Partial progress: #<PR> shipped <what was done>. Remaining: <what's left>."
Do NOT close issues where:
- The match is ambiguous or based on component name alone
- The issue is labeled
discussionordesign - The issue was filed in the last 48 hours
- You're not confident the PR fully addresses the described problem
Track what you processed in state so you don't re-process the same PRs:
{
"pm": {
"lastProcessedMergedPRs": [2510, 2507, 2505],
"lastMergedPRScanAt": "2026年06月09日T09:00:00Z"
}
}After closing an issue here, stamp it with the next-release milestone if its fix is unreleased, see §5 Milestones & Release Communication. Do this in the same pass, you already have the PR -> issue match.
Search for open issues referenced in recently merged PR titles or bodies (e.g. "fixes #123", "closes #123", "resolves #123"). If the linked PR is merged and the issue is still open, close it:
gh issue close <number> --repo facebook/astryx --reason completed \ --comment "Closing — completed by #<PR number> (merged <date>)."
For open issues requesting a component (e.g. "feat: Foo"), check if it already exists:
# List open component/enhancement issues gh issue list --repo facebook/astryx --state open --label enhancement \ --json number,title --limit 100 # For each, check if the component directory exists ls packages/core/src/<ComponentName>/ 2>/dev/null ls packages/lab/src/<ComponentName>/ 2>/dev/null
If the component exists in core or lab, find the PR that added it and close the issue:
gh pr list --repo facebook/astryx --state merged --search "<ComponentName>" \ --limit 5 --json number,title,closedAt gh issue close <number> --repo facebook/astryx --reason completed \ --comment "Shipped in PR #<number>."
Only the most recent nightly vibe test results issue should stay open. Close all older ones every run.
Why this must run every night: The Vibe Test Runner files a new results issue at ~4am, but the PM runs at 2am. This means there's always a previous-night's issue that needs closing. Do not skip this step.
# Get all open vibe-test results issues (not feature requests) gh issue list --repo facebook/astryx --state open --label vibe-test \ --json number,title,createdAt \ --jq '[.[] | select(.title | test("^Nightly Vibe Test|^Vibe Test Results"))] | sort_by(.createdAt) | reverse'
Keep ONLY the newest results issue open. Close all others:
gh issue close <number> --repo facebook/astryx --reason completed \ --comment "Superseded by newer vibe test results in #<newest_number>."
Do NOT close vibe-test issues that are feature requests or explorations (e.g. "Night Watch: Add 'Design' dimension", "Exploration: CLI vs MCP").
If two or more open issues cover the same feature surface:
- Keep the most detailed/recent one
- Close the others with
--reason "not planned"and a comment pointing to the surviving issue:Consolidated into #<surviving_number>. - Update the surviving issue's title if needed to cover the broader scope
Each night, audit 5–10 open issues to determine if they've been resolved, superseded, or are still relevant. Work through issues oldest-first, tracking progress in state so you don't re-audit the same issues.
# Get open issues (exclude vibe-test) gh issue list --repo facebook/astryx --state open --limit 100 \ --json number,title,labels,body,createdAt \ --jq '[.[] | select(.labels | map(.name) | index("vibe-test") | not)] | sort_by(.createdAt)'
Pick the next 5–10 issues from the queue that haven't been audited recently (check auditedIssues in state).
For each issue, determine its status:
| Check | How | Action |
|---|---|---|
| Resolved by merged PR | Search merged PRs for the component/feature: gh pr list --state merged --search "<component or keyword>" --limit 10 --json number,title,mergedAt,body. Read the PR to confirm it addresses the issue. |
Close with comment linking the PR |
| Component/feature now exists | Check packages/core/src/ and packages/lab/src/ for the requested component or feature |
Close with comment noting where it shipped |
| Superseded by newer issue | Check if a newer issue covers the same ground with more detail or broader scope | Close older one, link to the newer issue |
| No longer relevant | The issue references removed APIs, old architecture, or features that were intentionally not built | Close as not planned with brief explanation |
| Still open and valid | None of the above apply — the issue describes work that hasn't been done | Leave open, no action |
Matching heuristics for finding resolving PRs:
| Signal | Confidence |
|---|---|
| PR title contains issue's component name + same verb (fix/feat/add) | High |
| PR touches files in the same component directory the issue describes | High |
| PR title is semantically equivalent to issue title | Medium |
| PR body or commits reference the same behavior described in the issue | Medium |
| Component name match alone without behavioral overlap | Low — don't close |
For each resolved issue:
gh issue close <number> --repo facebook/astryx --reason completed \ --comment "Resolved by #<PR number> (merged <date>)."
For superseded issues:
gh issue close <number> --repo facebook/astryx --reason "not planned" \ --comment "Superseded by #<newer_issue_number> which covers this with broader scope."
For partially addressed issues: Don't close — comment noting what was shipped and what remains:
gh issue comment <number> --repo facebook/astryx \ --body "Partial progress: #<PR> shipped <what was done>. Remaining: <what's left>."
Do NOT close issues where:
- The match is ambiguous or based on component name alone
- The issue is labeled
discussionordesign - The issue was filed in the last 48 hours
- You're not confident the PR fully addresses the described problem
State tracking:
Track audited issues in memory/xds-night-watch-state.json under the pm key:
{
"pm": {
"auditedIssues": {
"123": { "auditedAt": "2026年04月19日", "result": "closed", "resolvedBy": "#456" },
"124": { "auditedAt": "2026年04月19日", "result": "open", "note": "still valid" }
},
"auditCursor": 0
}
}Advance auditCursor by the number of issues audited each night. When you reach the end of the list, reset to 0 (full cycle complete — issues may have changed since first pass).
- Flag issues open 30+ days with no activity
- Ensure issue-to-PR linkage is visible (PRs should reference the issue they close)
Milestones are how the project communicates which released version an issue is fixed in. When you close an issue in step 4 because a PR merged, that fix has not necessarily shipped yet, it lives on main until the next release is published. The milestone is the bridge: it tells the reporter and watchers "fixed, ships in X.Y.Z" without you having to comment on every issue.
This step runs right after step 4 (you already have the merged-PR -> issue matches in hand). Reuse that work, do not re-scan.
Milestone membership is decided by where the fix landed, NOT by the close date. An issue belongs on milestone X.Y.Z only if its fix sits in the unreleased range vPREV..origin/main, i.e. merged after the last release tag and shipping in the next release. Closed-after-the-tag is necessary but not sufficient: a fix can land in the previous release yet the issue gets closed later, and issues get closed as not planned or as duplicates with no fix at all. Those do NOT go on the next milestone.
The unreleased changesets decide the version number, do not assume. Run on the xds node from a fresh origin/main:
cd /vercel/sandbox/repos/xds && git fetch origin main -q VPREV=$(git tag -l 'v*' | sort -V | tail -1) # last release tag, e.g. v0.1.1 # bump level: if every unreleased changeset is patch -> patch bump; any minor/major -> bump that for f in .changeset/*.md; do [ "$(basename "$f")" = README.md ] && continue; \ sed -n '2,10p' "$f" | grep -oiE 'patch|minor|major' | head -1; done | sort | uniq -c
Compute the next version from $VPREV + the highest bump level present (all patch -> Z+1; any minor -> Y+1.0; any major -> X+1.0.0). Then ensure the milestone exists (create once, reuse thereafter):
NEXT="0.1.2" # computed above NUM=$(gh api repos/facebook/astryx/milestones --jq ".[] | select(.title==\"$NEXT\") | .number") if [ -z "$NUM" ]; then NUM=$(gh api -X POST repos/facebook/astryx/milestones \ -f title="$NEXT" -f state=open \ -f description="Next release. Issues whose fixes merged to main after $VPREV." --jq '.number') fi echo "milestone $NEXT = #$NUM"
For every issue you closed in step 4 (and any still-open issue whose fix has merged), verify the fix is in the unreleased range, then assign the milestone. The reliable signal is the closing PR/commit, not the close event:
-
List the unreleased range once per run:
- SHAs:
git log $VPREV..origin/main --format=%H - PRs:
git log $VPREV..origin/main --oneline | grep -oE '\(#[0-9]+\)$' | tr -d '(#)' | sort -un
- SHAs:
-
Per issue, pull its timeline and intersect with that range:
gh api repos/facebook/astryx/issues/<n>/timeline --paginate-> takecross-referencedevents whosesource.issue.pull_requestis set (the closing PRs) andreferencedevents'commit_id(closing commits). A PR-merge close usually records nocommit_idon theclosedevent, so the cross-referenced PR is the dependable signal. -
If the closing PR/commit is in the unreleased range, assign the milestone (note:
--milestone/ the API field wants the milestone NUMBER, not the title):Open issues with a merged-but-unreleased fix go on the milestone too, kept open until the release ships.gh api -X PATCH repos/facebook/astryx/issues/<n> -F milestone=$NUM
Per-issue timeline calls are slow. When stamping more than a handful, batch them in a single loop rather than many foreground calls.
Even if closed in the right window, do NOT put these on the milestone:
- Fix actually shipped in
$VPREV(the closing PR is in$VPREV, not in the unreleased range) - Closed as
not planned/wontfix(no fix) - Duplicates closed without their own fix commit
- Tracker / nightly-report /
discussion/ RFC issues (e.g. the rolling vibe-test issues)
- Default to the milestone as the signal. Do NOT bulk-comment on every resolved issue, it spams watchers, and the milestone already shows the version on each issue.
-
Do leave a short comment on an external bug report where a reporter is actively waiting and the fix has merged, pointing them at the version: e.g.
Fixed on main (#PR); tracked for X.Y.Z, ships in the next release. - When the release publishes, the milestone is closed by whoever runs the release; the published CHANGELOG (generated from the changesets) is the durable record. PM does not publish releases or close milestones, that is a human-driven release step.
gh api repos/facebook/astryx/milestones/$NUM --jq '"\(.title): open=\(.open_issues) closed=\(.closed_issues)"'
Public surface: milestone descriptions and any issue comments are public. Scrub internal references (task/diff/SEV numbers, internal infra, unixnames in prose) and add no AI attribution, per the Night Watch Overview constraints.
Separate from the per-version X.Y.Z milestones in section 5, there is one quarterly milestone (e.g. Q3 2026) that tracks the half's major feature and library updates — the big, roadmap-level lines of work, not fixes or docs. It answers "what are the significant things landing this half?" at a glance.
This is a lightweight weekly (not hourly) pass. Its whole job is to keep the quarterly milestone reflecting reality.
Every major feature or library update is represented by a single master (tracker) issue on the quarterly milestone. Small issues — bugs, individual enhancements, proposals — are linked inside the relevant master but are NOT added to the milestone themselves. The milestone stays a clean, one-line-per-theme roadmap; the detail lives in each master's checklist.
What qualifies for the quarterly milestone:
- ✅ A new library/package (e.g. charts, a code library), a new component family or major surface (e.g. chat components), or a cross-cutting capability (e.g. i18n & RTL, Windows dev support, a community-managed content space).
- ❌ Individual bug fixes, single small enhancements, doc/story-only work, one-off nits. These belong linked in a master, never on the milestone directly.
Once per week (and whenever a major theme clearly emerges), reconcile the quarterly milestone:
# What's on the quarterly milestone now gh issue list --repo facebook/astryx --milestone "Q3 2026" --state all --limit 50 \ --json number,title,state --jq '.[] | "#\(.number) [\(.state)] \(.title)"'
-
New major theme with no master? File a master (tracker) issue: problem-first body, a
Scope (on this milestone)checklist of the sub-issues, and aRelated bugs (linked, not on the milestone)section. Add it to the milestone. Title it[Tracker] <theme> — <short scope>. - Small issue that belongs to an existing theme? Reference it from that master's checklist (a mention auto-creates the backlink). Do not add it to the milestone.
-
A small/bug/doc issue landed on the milestone by mistake? Remove it (
gh api -X DELETEthe milestone field is not supported; PATCH it off with-F milestone=empty via the REST API, or reassign). Keep the milestone to masters only.
Deciding what counts as a major theme is a human/roadmap call, not something PM should invent autonomously. When unsure whether something rises to a milestone master, leave it off and surface it — don't manufacture masters. The team owns the roadmap; PM keeps the milestone tidy and asks when a new master might be warranted.
Same public-surface rules as section 5: master issue bodies and milestone descriptions are public — no internal refs, no AI attribution.
Track state in memory/xds-night-watch-state.json:
{
"role": "pm",
"lastRun": "2026年02月26日T06:00:00Z",
"triagedIssues": {},
"labeledIssues": [],
"wikiCheckedAt": null,
"runsToday": 0,
"milestones": {
"nextVersion": "0.1.2",
"milestoneNumber": 10,
"lastStampedAt": null,
"stampedIssues": []
}
}Start here Astryx Philosophy Contributing with AI Assistants Contributing
Architecture System Architecture Architecture Cheat Sheet Theming Infrastructure Distribution
Building a component Component Lifecycle Component Authoring Guide API Conventions Design Conventions
Quality Component Audit Rubric Accessibility Checklist AST-009 assistive-technology verification
Operations Release Process Night Watch Overview