Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 39a5122f68 |
@@ -1,39 +0,0 @@
|
|||||||
# Block PRs that add prd-new-*.md files directly to main.
|
|
||||||
#
|
|
||||||
# prd-new-*.md files are placeholders — they must go through a PR so
|
|
||||||
# the post-merge prd-number workflow can assign a sequential number and
|
|
||||||
# rename the file. A direct push or a PR that slips through without
|
|
||||||
# triggering the check would leave an un-numbered PRD on main.
|
|
||||||
|
|
||||||
name: prd-check
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- 'docs/prds/prd-new-*.md'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
no-prd-new-on-main:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Fail if prd-new-*.md files are present in the diff
|
|
||||||
run: |
|
|
||||||
base="${{ github.event.pull_request.base.sha }}"
|
|
||||||
head="${{ github.event.pull_request.head.sha }}"
|
|
||||||
new_prds=$(git diff --name-only --diff-filter=A "$base" "$head" \
|
|
||||||
| grep -E '^docs/prds/prd-new-.+\.md$' || true)
|
|
||||||
if [ -n "$new_prds" ]; then
|
|
||||||
echo "ERROR: PRs to main must not add prd-new-*.md files directly."
|
|
||||||
echo "These files must be merged via a feature branch so the"
|
|
||||||
echo "prd-number workflow can assign a sequential number on merge:"
|
|
||||||
echo "$new_prds"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "OK: no prd-new-*.md files added in this PR."
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
# Assign sequential numbers to prd-new-*.md files on merge to main.
|
|
||||||
#
|
|
||||||
# When a PR merges to main and includes prd-new-*.md files this workflow:
|
|
||||||
# 1. Finds the next available NNNN number by scanning existing PRDs.
|
|
||||||
# 2. Renames each prd-new-*.md to NNNN-<slug>.md.
|
|
||||||
# 3. Updates the title header (# PRD prd-new: → # PRD NNNN:).
|
|
||||||
# 4. Flips Status: Draft → Active when the merge commit also touched
|
|
||||||
# files outside docs/prds/ (i.e. the implementation shipped together
|
|
||||||
# with the PRD).
|
|
||||||
# 5. Commits the renaming back to main.
|
|
||||||
#
|
|
||||||
# No-op if the push contained no prd-new-*.md files.
|
|
||||||
|
|
||||||
name: prd-number
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- 'docs/prds/prd-new-*.md'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
assign-numbers:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 2
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: "3.12"
|
|
||||||
|
|
||||||
- name: Configure git
|
|
||||||
run: |
|
|
||||||
git config user.name "github-actions[bot]"
|
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
||||||
|
|
||||||
- name: Assign PRD numbers
|
|
||||||
run: |
|
|
||||||
python3 - <<'EOF'
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
prds_dir = Path("docs/prds")
|
|
||||||
|
|
||||||
# Files added in the latest commit (HEAD vs HEAD~1).
|
|
||||||
result = subprocess.run(
|
|
||||||
["git", "diff", "--name-only", "--diff-filter=A", "HEAD~1", "HEAD"],
|
|
||||||
capture_output=True, text=True, check=True,
|
|
||||||
)
|
|
||||||
added = [Path(p) for p in result.stdout.splitlines()]
|
|
||||||
new_prds = [p for p in added if p.parent == prds_dir
|
|
||||||
and re.match(r"prd-new-.+\.md$", p.name)]
|
|
||||||
|
|
||||||
if not new_prds:
|
|
||||||
print("No prd-new-*.md files added in this commit — nothing to do.")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
# Determine whether non-PRD files were also changed (for Status flip).
|
|
||||||
all_changed = subprocess.run(
|
|
||||||
["git", "diff", "--name-only", "HEAD~1", "HEAD"],
|
|
||||||
capture_output=True, text=True, check=True,
|
|
||||||
).stdout.splitlines()
|
|
||||||
non_prd_changed = any(
|
|
||||||
not f.startswith("docs/prds/") for f in all_changed
|
|
||||||
)
|
|
||||||
|
|
||||||
# Find next available number.
|
|
||||||
existing = sorted(
|
|
||||||
int(m.group(1))
|
|
||||||
for p in prds_dir.glob("*.md")
|
|
||||||
if (m := re.match(r"^(\d{4})-", p.name))
|
|
||||||
)
|
|
||||||
next_num = (max(existing) + 1) if existing else 1
|
|
||||||
|
|
||||||
for prd_path in sorted(new_prds):
|
|
||||||
slug = re.sub(r"^prd-new-", "", prd_path.stem)
|
|
||||||
new_name = f"{next_num:04d}-{slug}.md"
|
|
||||||
new_path = prds_dir / new_name
|
|
||||||
print(f" {prd_path.name} → {new_name}")
|
|
||||||
|
|
||||||
content = prd_path.read_text()
|
|
||||||
|
|
||||||
# Update title header.
|
|
||||||
content = re.sub(
|
|
||||||
r"^(#\s+PRD\s+)prd-new(:)",
|
|
||||||
rf"\g<1>{next_num:04d}\2",
|
|
||||||
content,
|
|
||||||
count=1,
|
|
||||||
flags=re.MULTILINE,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Conditionally flip Status.
|
|
||||||
if non_prd_changed:
|
|
||||||
content = re.sub(
|
|
||||||
r"(\*\*Status:\*\*\s*)Draft",
|
|
||||||
r"\g<1>Active",
|
|
||||||
content,
|
|
||||||
count=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
new_path.write_text(content)
|
|
||||||
subprocess.run(["git", "rm", str(prd_path)], check=True)
|
|
||||||
subprocess.run(["git", "add", str(new_path)], check=True)
|
|
||||||
next_num += 1
|
|
||||||
|
|
||||||
subprocess.run(
|
|
||||||
["git", "commit", "-m", "ci(prd): assign sequential numbers to new PRDs"],
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
subprocess.run(["git", "push"], check=True)
|
|
||||||
EOF
|
|
||||||
@@ -36,11 +36,10 @@ the container lifecycle and the copying of skills and env vars into it.
|
|||||||
|
|
||||||
- Three kinds of doc, each with its own conventions in-folder; see
|
- Three kinds of doc, each with its own conventions in-folder; see
|
||||||
`docs/README.md` for when to write which:
|
`docs/README.md` for when to write which:
|
||||||
- **PRDs** (`docs/prds/`) — one feature per file. While a PR is open
|
- **PRDs** (`docs/prds/`) — one feature per file, numbered
|
||||||
the file is named `prd-new-<kebab>.md`; CI assigns a sequential
|
`NNNN-kebab.md`. A `Status:` line tracks lifecycle: Draft → Active
|
||||||
number on merge to `main` and renames it. A `Status:` line tracks
|
(shipped to `main`) → Superseded/Retargeted. Format in
|
||||||
lifecycle: Draft → Active (shipped to `main`) →
|
`docs/prds/README.md`.
|
||||||
Superseded/Retargeted. Format in `docs/prds/README.md`.
|
|
||||||
- **Research notes** (`docs/research/`) — opinionated investigations;
|
- **Research notes** (`docs/research/`) — opinionated investigations;
|
||||||
unnumbered kebab-case, freeform and verdict-first. See
|
unnumbered kebab-case, freeform and verdict-first. See
|
||||||
`docs/research/README.md`.
|
`docs/research/README.md`.
|
||||||
|
|||||||
+4
-7
@@ -7,12 +7,9 @@ document vs. a research note or a decision record).
|
|||||||
|
|
||||||
## Naming and numbering
|
## Naming and numbering
|
||||||
|
|
||||||
New PRDs use a `prd-new-<kebab-title>.md` placeholder name while the PR
|
`NNNN-kebab-title.md`, zero-padded and sequential (`0024-…`, `0025-…`).
|
||||||
is open. On merge to `main` a CI workflow assigns the next sequential
|
Numbers are never reused; gaps are fine (there is no 0005). The number
|
||||||
number (`0024-…`, `0025-…`), renames the file, and updates the title
|
is assigned at creation and stays fixed for the life of the doc.
|
||||||
header. Numbers are never reused; gaps are fine.
|
|
||||||
|
|
||||||
Once numbered, the filename stays fixed for the life of the doc.
|
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
@@ -26,7 +23,7 @@ The `Status:` line near the top tracks the PRD's lifecycle:
|
|||||||
## Format
|
## Format
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
# PRD prd-new: <short title> ← placeholder; CI fills in the number on merge
|
# PRD NNNN: <short title>
|
||||||
|
|
||||||
- **Status:** Draft
|
- **Status:** Draft
|
||||||
- **Author:** <who>
|
- **Author:** <who>
|
||||||
|
|||||||
Reference in New Issue
Block a user