Skip to content

Navigation Menu

Sign in
Sign up

Night Watch PM

Cindy Zhang edited this page Aug 11, 2026 · 5 revisions

Night Watch: PM Role

Assigned to: Cindy's Navi (cixzhang) Goal: Keep tasks, issues, and wiki tidy and navigable.


Hourly Checklist

1. Issue Triage — Ensure Implementability

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 discussion or design
  • Already have a triage comment from a previous run

2. Issue & PR Labeling

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/cliarea:cli, packages/corearea:core, Tablearea:tables, charts → area:charts, theme/packages/themesarea:theming, apps/docsitearea:docs, apps/storybookarea:storybook, apps/example-*area:examples, packages/labarea:lab, and add area:a11y alongside 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: feattype:feature, fixtype:fix, docstype:docs, testtype: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) — add needs:api-review when it touches public/exported API (new component, new props, new exports); add needs:design-review when 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).

3. Wiki Hygiene

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

Heuristics

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.

Link Validation Script

# 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

Home Completeness Check

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:

  1. Read the first few lines — is it an active process/architecture page, or historical/exploratory?
  2. Active pages (processes people follow, architecture docs, contribution guides) → add to the appropriate Home section
  3. Historical/exploratory pages → should already be in Research Archive. If not, add them there.
  4. Redirect pages (content moved elsewhere) → no action needed, they exist for link stability

Home Page Audit

For each link on Home.md:

  1. Open the linked page and read the first few sections
  2. Verify the Home description still matches the page content
  3. If the page has evolved significantly (new sections, changed scope), update the Home description
  4. If a link points to a page that's become irrelevant or historical, move it to Research Archive
  5. Check if any important new wiki pages exist that aren't linked from Home — add them to the right section

What to Clean Up

  • 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.

What NOT to Do

  • 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

4. Task Hygiene — Issue Gardening

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.

4a. Recently Merged PRs → Close Resolved Issues (MANDATORY)

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:

  1. 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.

  2. 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)
  3. 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
  4. 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
  5. Close matched issues:

    gh issue close <number> --repo facebook/astryx --reason completed \
     --comment "Resolved by #<PR number> (merged <date>)."
  6. 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 discussion or design
  • 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.

4b. Explicit PR References (Cleanup)

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>)."

4c. Component Feature Issues — Check if Shipped

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>."

4d. Superseded Vibe Test Results (MANDATORY)

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").

4e. Consolidate Duplicates

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

4f. Issue Audit — Batch Resolution Check (5–10 per night)

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 discussion or design
  • 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).

4g. Stale Issues

  • Flag issues open 30+ days with no activity
  • Ensure issue-to-PR linkage is visible (PRs should reference the issue they close)

5. Milestones & Release Communication

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.

Core principle

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.

5a. Determine the next version and ensure its milestone exists

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"

5b. Stamp newly-resolved issues onto the milestone

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
  • Per issue, pull its timeline and intersect with that range: gh api repos/facebook/astryx/issues/<n>/timeline --paginate -> take cross-referenced events whose source.issue.pull_request is set (the closing PRs) and referenced events' commit_id (closing commits). A PR-merge close usually records no commit_id on the closed event, 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):
    gh api -X PATCH repos/facebook/astryx/issues/<n> -F milestone=$NUM
    Open issues with a merged-but-unreleased fix go on the milestone too, kept open until the release ships.

Per-issue timeline calls are slow. When stamping more than a handful, batch them in a single loop rather than many foreground calls.

5c. Exclude non-release items

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)

5d. Communicate "fixed, pending release"

  • 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.

Verify

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.


6. Quarterly Milestone — Major Features & Library Updates

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.

Core principle

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.

6a. Keep masters current

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 a Related 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 DELETE the milestone field is not supported; PATCH it off with -F milestone= empty via the REST API, or reassign). Keep the milestone to masters only.

6b. Delegate theme decisions to the team

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.


State

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": []
 }
}

Clone this wiki locally

AltStyle によって変換されたページ (->オリジナル) /