mirror of
https://github.com/BradGroux/veritas-kanban.git
synced 2026-08-28 02:44:59 +00:00
ci: reserve release-grade testing for milestones (#1228)
This commit is contained in:
parent
415f4077b9
commit
49e25838e1
13 changed files with 204 additions and 408 deletions
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -28,7 +28,7 @@ Describe the tests you ran to verify your changes. Provide instructions so revie
|
|||
**Verification tier:**
|
||||
|
||||
- [ ] Documentation or static checks only
|
||||
- [ ] Focused changed-package tests
|
||||
- [ ] Explicit focused diagnostic (manual workflow dispatch)
|
||||
- [ ] Full milestone gate (`ci:full`, critical security, integration, or release)
|
||||
|
||||
**Why this tier is sufficient:** Explain the changed behavior, covered failure
|
||||
|
|
@ -46,7 +46,7 @@ modes, and why broader gates are or are not required.
|
|||
- [ ] Separable follow-up work is linked instead of folded into this PR
|
||||
- [ ] My code follows the style guidelines of this project
|
||||
- [ ] I have performed a self-review of my own code
|
||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||
- [ ] I have added or updated coverage for the next declared test milestone
|
||||
- [ ] I have updated the documentation accordingly
|
||||
- [ ] My changes generate no new warnings
|
||||
- [ ] Any breaking changes have been documented in the PR description
|
||||
|
|
|
|||
129
.github/workflows/ci.yml
vendored
129
.github/workflows/ci.yml
vendored
|
|
@ -13,7 +13,7 @@ on:
|
|||
test_scope:
|
||||
description: Unit-test verification tier
|
||||
required: true
|
||||
default: focused
|
||||
default: full
|
||||
type: choice
|
||||
options:
|
||||
- focused
|
||||
|
|
@ -24,10 +24,7 @@ on:
|
|||
type: string
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
checks: read
|
||||
pull-requests: read
|
||||
|
||||
concurrency:
|
||||
group: >-
|
||||
|
|
@ -81,7 +78,6 @@ jobs:
|
|||
scripts/check-security-gates.test.mjs
|
||||
scripts/check-tracked-ignore.test.mjs
|
||||
scripts/select-ci-test-scope.test.mjs
|
||||
scripts/verify-full-suite-job-evidence.test.mjs
|
||||
|
||||
- name: Guard delivery cadence
|
||||
run: node scripts/check-delivery-cadence.mjs
|
||||
|
|
@ -95,126 +91,6 @@ jobs:
|
|||
- name: Reject tracked files covered by ignore rules
|
||||
run: node scripts/check-tracked-ignore.mjs
|
||||
|
||||
- name: Find reviewed full-suite evidence
|
||||
id: reviewed_full
|
||||
if: github.event_name == 'push'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
reviewed_full=false
|
||||
reviewed_pr=
|
||||
reviewed_head=
|
||||
reviewed_mode=
|
||||
associated_prs="$(
|
||||
gh api \
|
||||
-H 'Accept: application/vnd.github+json' \
|
||||
"repos/${GITHUB_REPOSITORY}/commits/${GITHUB_SHA}/pulls" \
|
||||
2>/dev/null ||
|
||||
printf '[]'
|
||||
)"
|
||||
merged_pr="$(
|
||||
jq -c \
|
||||
--arg merge_sha "$GITHUB_SHA" \
|
||||
'[.[] | select(
|
||||
.merged_at != null and
|
||||
.base.ref == "main" and
|
||||
.merge_commit_sha == $merge_sha
|
||||
)] | first // empty' \
|
||||
<<<"$associated_prs"
|
||||
)"
|
||||
|
||||
if [[ -n "$merged_pr" ]]; then
|
||||
reviewed_pr="$(jq -r '.number' <<<"$merged_pr")"
|
||||
reviewed_head="$(jq -r '.head.sha' <<<"$merged_pr")"
|
||||
check_runs="$(
|
||||
gh api \
|
||||
-H 'Accept: application/vnd.github+json' \
|
||||
"repos/${GITHUB_REPOSITORY}/commits/${reviewed_head}/check-runs?per_page=100" \
|
||||
2>/dev/null ||
|
||||
printf '{"check_runs":[]}'
|
||||
)"
|
||||
|
||||
full_suite_check_id="$(
|
||||
jq -r '
|
||||
[
|
||||
.check_runs[] |
|
||||
select(
|
||||
.name == "Workspace Unit Tests" and
|
||||
.status == "completed" and
|
||||
.conclusion == "success" and
|
||||
.app.slug == "github-actions"
|
||||
)
|
||||
] |
|
||||
sort_by(.completed_at) |
|
||||
last |
|
||||
.id // empty
|
||||
' <<<"$check_runs"
|
||||
)"
|
||||
full_suite_job="$(
|
||||
if [[ -n "$full_suite_check_id" ]]; then
|
||||
gh api \
|
||||
-H 'Accept: application/vnd.github+json' \
|
||||
"repos/${GITHUB_REPOSITORY}/actions/jobs/${full_suite_check_id}" \
|
||||
2>/dev/null ||
|
||||
printf '{}'
|
||||
else
|
||||
printf '{}'
|
||||
fi
|
||||
)"
|
||||
coverage_check_id="$(
|
||||
jq -r '
|
||||
[
|
||||
.check_runs[] |
|
||||
select(
|
||||
.name == "Critical Path Coverage" and
|
||||
.status == "completed" and
|
||||
.conclusion == "success" and
|
||||
.app.slug == "github-actions"
|
||||
)
|
||||
] |
|
||||
sort_by(.completed_at) |
|
||||
last |
|
||||
.id // empty
|
||||
' <<<"$check_runs"
|
||||
)"
|
||||
coverage_job="$(
|
||||
if [[ -n "$coverage_check_id" ]]; then
|
||||
gh api \
|
||||
-H 'Accept: application/vnd.github+json' \
|
||||
"repos/${GITHUB_REPOSITORY}/actions/jobs/${coverage_check_id}" \
|
||||
2>/dev/null ||
|
||||
printf '{}'
|
||||
else
|
||||
printf '{}'
|
||||
fi
|
||||
)"
|
||||
|
||||
if node scripts/verify-full-suite-job-evidence.mjs <<<"$full_suite_job" &&
|
||||
node scripts/verify-full-suite-job-evidence.mjs coverage <<<"$coverage_job"; then
|
||||
if git cat-file -e "${reviewed_head}^{commit}" 2>/dev/null ||
|
||||
git fetch --no-tags origin "$reviewed_head"; then
|
||||
if git merge-base --is-ancestor "$reviewed_head" "$GITHUB_SHA"; then
|
||||
reviewed_full=true
|
||||
reviewed_mode=ancestor
|
||||
elif [[ "$(git rev-parse "${reviewed_head}^{tree}")" == \
|
||||
"$(git rev-parse "${GITHUB_SHA}^{tree}")" ]]; then
|
||||
reviewed_full=true
|
||||
reviewed_mode=identical-tree
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
{
|
||||
echo "reviewed_full=$reviewed_full"
|
||||
echo "reviewed_pr=$reviewed_pr"
|
||||
echo "reviewed_head=$reviewed_head"
|
||||
echo "reviewed_mode=$reviewed_mode"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Select verification tier
|
||||
id: scope
|
||||
shell: bash
|
||||
|
|
@ -222,9 +98,6 @@ jobs:
|
|||
CI_EVENT_NAME: ${{ github.event_name }}
|
||||
CI_MANUAL_SCOPE: ${{ inputs.test_scope || '' }}
|
||||
CI_PR_LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }}
|
||||
CI_REVIEWED_FULL: ${{ steps.reviewed_full.outputs.reviewed_full || 'false' }}
|
||||
CI_REVIEWED_PR: ${{ steps.reviewed_full.outputs.reviewed_pr || '' }}
|
||||
CI_REVIEWED_MODE: ${{ steps.reviewed_full.outputs.reviewed_mode || '' }}
|
||||
PR_BASE_SHA: ${{ github.event.pull_request.base.sha || '' }}
|
||||
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || '' }}
|
||||
PUSH_BEFORE_SHA: ${{ github.event.before || '' }}
|
||||
|
|
|
|||
61
.github/workflows/desktop-artifacts.yml
vendored
61
.github/workflows/desktop-artifacts.yml
vendored
|
|
@ -1,31 +1,9 @@
|
|||
name: Desktop Artifacts
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'desktop/**'
|
||||
- 'server/**'
|
||||
- 'web/**'
|
||||
- 'shared/**'
|
||||
- 'docs/DESKTOP-RELEASE.md'
|
||||
- 'scripts/desktop-after-pack.mjs'
|
||||
- 'scripts/prepare-desktop-release.mjs'
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- '.github/workflows/desktop-artifacts.yml'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'desktop/**'
|
||||
- 'docs/DESKTOP-RELEASE.md'
|
||||
- 'scripts/desktop-after-pack.mjs'
|
||||
- 'scripts/prepare-desktop-release.mjs'
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- '.github/workflows/desktop-artifacts.yml'
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
|
|
@ -38,8 +16,20 @@ env:
|
|||
jobs:
|
||||
mac-unsigned:
|
||||
name: Unsigned macOS Artifact
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
contains(github.event.pull_request.labels.*.name, 'ci:full')
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- name: Record milestone selection
|
||||
run: |
|
||||
{
|
||||
echo "### Unsigned macOS artifact milestone"
|
||||
echo
|
||||
echo "- Trigger: \`$GITHUB_EVENT_NAME\`"
|
||||
echo "- Reason: explicit \`ci:full\` or manual milestone"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
|
|
@ -78,8 +68,20 @@ jobs:
|
|||
|
||||
linux-unsigned:
|
||||
name: Unsigned Linux Artifacts
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
contains(github.event.pull_request.labels.*.name, 'ci:full')
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Record milestone selection
|
||||
run: |
|
||||
{
|
||||
echo "### Unsigned Linux artifact milestone"
|
||||
echo
|
||||
echo "- Trigger: \`$GITHUB_EVENT_NAME\`"
|
||||
echo "- Reason: explicit \`ci:full\` or manual milestone"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
|
|
@ -122,8 +124,21 @@ jobs:
|
|||
|
||||
windows-unsigned:
|
||||
name: Unsigned Windows Artifacts
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
contains(github.event.pull_request.labels.*.name, 'ci:full')
|
||||
runs-on: windows-2025
|
||||
steps:
|
||||
- name: Record milestone selection
|
||||
shell: bash
|
||||
run: |
|
||||
{
|
||||
echo "### Unsigned Windows artifact milestone"
|
||||
echo
|
||||
echo "- Trigger: \`$GITHUB_EVENT_NAME\`"
|
||||
echo "- Reason: explicit \`ci:full\` or manual milestone"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
|
|
|
|||
37
.github/workflows/docker-image.yml
vendored
37
.github/workflows/docker-image.yml
vendored
|
|
@ -1,32 +1,9 @@
|
|||
name: Docker Image Contract
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- Dockerfile
|
||||
- .dockerignore
|
||||
- package.json
|
||||
- pnpm-lock.yaml
|
||||
- pnpm-workspace.yaml
|
||||
- server/**
|
||||
- shared/**
|
||||
- web/**
|
||||
- scripts/check-docker-image.mjs
|
||||
- .github/workflows/docker-image.yml
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- Dockerfile
|
||||
- .dockerignore
|
||||
- package.json
|
||||
- pnpm-lock.yaml
|
||||
- pnpm-workspace.yaml
|
||||
- server/**
|
||||
- shared/**
|
||||
- web/**
|
||||
- scripts/check-docker-image.mjs
|
||||
- .github/workflows/docker-image.yml
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
|
@ -39,8 +16,20 @@ concurrency:
|
|||
jobs:
|
||||
contract:
|
||||
name: Build, Size, and Runtime Contract
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
contains(github.event.pull_request.labels.*.name, 'ci:full')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Record milestone selection
|
||||
run: |
|
||||
{
|
||||
echo "### Docker image milestone"
|
||||
echo
|
||||
echo "- Trigger: \`$GITHUB_EVENT_NAME\`"
|
||||
echo "- Reason: explicit \`ci:full\` or manual milestone"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Build production image
|
||||
|
|
|
|||
28
.github/workflows/scheduled-qa.yml
vendored
28
.github/workflows/scheduled-qa.yml
vendored
|
|
@ -1,6 +1,9 @@
|
|||
name: Scheduled QA
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
schedule:
|
||||
- cron: '17 8 * * 1'
|
||||
workflow_dispatch:
|
||||
|
|
@ -36,9 +39,21 @@ env:
|
|||
jobs:
|
||||
playwright:
|
||||
name: Playwright E2E
|
||||
if: >-
|
||||
github.event_name != 'pull_request' ||
|
||||
contains(github.event.pull_request.labels.*.name, 'ci:full')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Record milestone selection
|
||||
run: |
|
||||
{
|
||||
echo "### Playwright E2E milestone"
|
||||
echo
|
||||
echo "- Trigger: \`$GITHUB_EVENT_NAME\`"
|
||||
echo "- Reason: explicit \`ci:full\`, scheduled, or manual milestone"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
|
|
@ -81,11 +96,24 @@ jobs:
|
|||
|
||||
k6:
|
||||
name: k6 Load Smoke
|
||||
if: >-
|
||||
github.event_name != 'pull_request' ||
|
||||
contains(github.event.pull_request.labels.*.name, 'ci:full')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
K6_PROFILE: ${{ github.event_name == 'workflow_dispatch' && inputs.load_profile || 'smoke' }}
|
||||
steps:
|
||||
- name: Record milestone selection
|
||||
run: |
|
||||
{
|
||||
echo "### k6 milestone"
|
||||
echo
|
||||
echo "- Trigger: \`$GITHUB_EVENT_NAME\`"
|
||||
echo "- Profile: \`$K6_PROFILE\`"
|
||||
echo "- Reason: explicit \`ci:full\`, scheduled, or manual milestone"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
|
|
|
|||
16
AGENTS.md
16
AGENTS.md
|
|
@ -126,9 +126,11 @@ Do not run `npm install`, `yarn`, or `bun install`. If lockfile conflicts arise,
|
|||
- At the 45-minute delivery checkpoint, if the issue is not pull-request ready, stop adding scope
|
||||
and report the concrete cause. Split independent remaining work into linked issues, or continue
|
||||
only when the next step is required to preserve correctness of the current behavior.
|
||||
- During implementation, run the narrowest useful loop: type-check touched packages, lint changed
|
||||
files, and run focused tests for changed behavior and high-risk edges.
|
||||
- Run focused Vitest slices with
|
||||
- During ordinary implementation, use source inspection, changed-file formatting/linting, and
|
||||
touched-package type checking. Do not run workspace unit, coverage, E2E, desktop packaging, or
|
||||
Docker contract tests between implementation PRs.
|
||||
- When a maintainer explicitly declares a focused diagnostic milestone, run the exact Vitest slice
|
||||
once with
|
||||
`pnpm --filter <package> exec vitest run <exact-test-files>`. Do not use
|
||||
`pnpm --filter <package> test -- <test-files>` or
|
||||
`pnpm --filter <package> test -- --run <test-files>`; package wrappers can ignore that file
|
||||
|
|
@ -137,9 +139,11 @@ Do not run `npm install`, `yarn`, or `bun install`. If lockfile conflicts arise,
|
|||
Rerun only the checks affected by the later change.
|
||||
- Use the complete workspace suite once at an explicit integration, critical-security, or release
|
||||
milestone. Pull-request label `ci:full`, scheduled CI, and manual full dispatch are the
|
||||
authoritative broad gates.
|
||||
- Trust `scripts/select-ci-test-scope.mjs` and the `Select Test Scope` job to choose the required
|
||||
CI tier. Do not add broader local gates merely to duplicate CI.
|
||||
authoritative broad gates. Critical coverage, unsigned desktop artifacts, and the Docker image
|
||||
contract run only at those milestones.
|
||||
- Trust `scripts/select-ci-test-scope.mjs` and the `Select Test Scope` job to record the required
|
||||
CI tier. Ordinary pull requests and `main` pushes select no workspace tests. Do not add local
|
||||
test gates merely to duplicate a future milestone.
|
||||
- Do not wait for optional desktop packaging, artifact previews, or release workflows when the
|
||||
change does not touch their product boundary. They are evidence only when declared relevant.
|
||||
- Add enough regression coverage to prove the behavior and its meaningful failure modes. Test
|
||||
|
|
|
|||
|
|
@ -73,16 +73,17 @@ veritas-kanban/
|
|||
|
||||
2. Make your changes — write code, add tests, update docs.
|
||||
|
||||
3. Run touched-package type checking, changed-file linting, and focused tests
|
||||
before committing:
|
||||
3. Run touched-package type checking and changed-file linting before
|
||||
committing. Ordinary implementation pull requests do not run workspace
|
||||
tests:
|
||||
|
||||
```bash
|
||||
pnpm --filter @veritas-kanban/server typecheck
|
||||
pnpm exec eslint server/src/path/to/changed.ts
|
||||
pnpm --filter @veritas-kanban/server exec vitest run src/path/to/changed.test.ts
|
||||
```
|
||||
|
||||
Use direct `exec vitest run` invocation for exact-file slices. Do not use
|
||||
At an explicitly declared focused diagnostic milestone, use direct
|
||||
`exec vitest run` invocation for one exact-file slice. Do not use
|
||||
`pnpm --filter <package> test -- <test-files>` or
|
||||
`pnpm --filter <package> test -- --run <test-files>` as a focused command.
|
||||
Package wrappers can ignore that file boundary and expand into the entire
|
||||
|
|
@ -91,7 +92,8 @@ veritas-kanban/
|
|||
Build `@veritas-kanban/shared` first and type-check its known consumers when
|
||||
a shared contract changes. Use `pnpm test` at an explicit integration,
|
||||
critical-security, or release milestone, or when a maintainer explicitly
|
||||
selects the `ci:full` gate.
|
||||
selects the `ci:full` gate. Critical coverage, E2E, desktop packaging, and
|
||||
Docker contracts follow the same milestone boundary.
|
||||
|
||||
4. Commit using [conventional commits](#commit-conventions).
|
||||
|
||||
|
|
@ -109,8 +111,9 @@ This cadence extends the deterministic CI selector delivered in
|
|||
verification effort becomes larger than the changed behavior.
|
||||
- Do not rerun an unchanged passing check after documentation, comments, or
|
||||
formatting-only edits.
|
||||
- Treat `Select Test Scope` as the CI authority. Focused, full, and no-test
|
||||
selections are recorded in the job summary.
|
||||
- Treat `Select Test Scope` as the CI authority. Ordinary pull requests and
|
||||
`main` pushes select no workspace tests; manual focused diagnostics and full
|
||||
milestone selections are recorded in the job summary.
|
||||
- Do not wait for optional desktop artifacts, packaging previews, or release
|
||||
workflows unless the pull request changes that product boundary.
|
||||
- Test the behavior and meaningful failure modes. Do not use raw test count as
|
||||
|
|
@ -129,14 +132,16 @@ can rebase on the exact result.
|
|||
1. Merge first branch to `main`
|
||||
2. Confirm the required GitHub checks for that pull request
|
||||
3. Rebase the next branch on the updated `main`
|
||||
4. Run only the focused checks affected by conflict resolution
|
||||
4. Inspect conflict resolution and run changed-file static checks
|
||||
5. Merge the next branch
|
||||
|
||||
The complete build, workspace suite, integration suite, and applicable E2E or
|
||||
artifact gates run once at the declared milestone. They are not repeated after
|
||||
every unrelated merge.
|
||||
The complete workspace suite, coverage, integration, E2E, desktop artifact,
|
||||
and Docker gates run once at the declared milestone. They are not repeated
|
||||
after every unrelated merge.
|
||||
|
||||
**Why:** Parallel branches often introduce integration issues that are hidden when batch-merging. Sequential merges with testing between each merge catch these immediately.
|
||||
**Why:** Sequential merges keep conflicts attributable without paying the
|
||||
release-certification cost after every independent change. The declared
|
||||
milestone verifies the integrated candidate once.
|
||||
|
||||
### One Agent Per File Rule
|
||||
|
||||
|
|
@ -192,7 +197,7 @@ owner explicitly requires it.
|
|||
Before merging, verify the checks selected for the changed product boundary:
|
||||
|
||||
- [ ] **Selected CI tier:** Every required check started for the pull request is green.
|
||||
- [ ] **Focused local evidence:** Changed behavior and meaningful failure modes are covered.
|
||||
- [ ] **Implementation evidence:** The diff and applicable static checks support the changed behavior.
|
||||
- [ ] **Shared contracts, when changed:** New types are exported and known consumers type-check.
|
||||
- [ ] **Configuration, when changed:** Ports, URLs, timeouts, environment variables, CSP, and CORS behave in the affected modes.
|
||||
- [ ] **Frontend integration, when changed:** HTTP calls use shared helpers and location-sensitive behavior avoids hardcoded hosts.
|
||||
|
|
@ -211,8 +216,9 @@ Before merging, verify the checks selected for the changed product boundary:
|
|||
|
||||
### Testing Requirements
|
||||
|
||||
Run browser or API smoke tests only when the change affects that product
|
||||
boundary. Choose the smallest runtime check that proves the behavior:
|
||||
Run browser or API smoke tests only at an explicit integration or release
|
||||
milestone when the change affects that product boundary. Choose the smallest
|
||||
runtime check that proves the behavior:
|
||||
|
||||
- **Server or API changes:** Exercise the changed endpoint and its meaningful auth or failure path. Add a health check only when startup or routing changed.
|
||||
- **Web changes:** Open the changed route and verify its primary interaction, keyboard flow, and failure state.
|
||||
|
|
@ -279,10 +285,10 @@ docs: update README with deployment instructions
|
|||
3. **Open a PR** against `main`.
|
||||
4. **Fill out the PR template** — describe changes, link related issues, include screenshots for UI changes.
|
||||
5. **Ensure the selected PR CI tier passes** — all checks started for the pull
|
||||
request must be green. The scope selector runs related tests for affected
|
||||
workspaces and records its base/head evidence in the job summary. Use
|
||||
`ci:full` for release candidates, critical integration/security boundaries,
|
||||
or other changes that require an explicit complete-suite gate.
|
||||
request must be green. The scope selector records affected workspaces but
|
||||
defers their tests on ordinary pull requests. Use `ci:full` for release
|
||||
candidates, critical integration/security boundaries, or other changes that
|
||||
require an explicit complete-suite gate.
|
||||
6. **Request review** — a maintainer will review and may request changes.
|
||||
7. **Address feedback** — push additional commits as needed.
|
||||
8. **Merge** — once approved, a maintainer will merge.
|
||||
|
|
@ -336,32 +342,24 @@ Follow the existing conventions in `.eslintrc.*`, `.prettierrc`, and `tsconfig.j
|
|||
|
||||
### CI tiers
|
||||
|
||||
| Trigger | Stable checks | Scope |
|
||||
| ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
||||
| Documentation-only pull request or merge | Static gates; unit-test jobs record skip decisions | No workspace unit suite |
|
||||
| Ordinary code pull request | `Lint & Type Check`, `Changed Tests`, `Build`, `Security Audit` | Vitest `related` coverage for affected server, web, CLI, or MCP workspaces |
|
||||
| Ordinary code merge to `main` | Default static gates plus `Changed Tests` | Related coverage limited to affected workspaces |
|
||||
| Pull request with `ci:full`, or a CI selector/workflow control change | Default checks plus `Workspace Unit Tests` | Complete workspace, desktop readiness regressions, and exact dual-storage parity |
|
||||
| Merge whose reviewed head already passed `Workspace Unit Tests` | Static gates; both unit-test tiers record skip decisions | Reuses exact successful head evidence when that head is an ancestor of the merge commit |
|
||||
| Nightly 08:00 UTC or manual `CI` dispatch with `test_scope=full` | Static gates, `Workspace Unit Tests`, `Build`, `Security Audit` | Complete authoritative workspace suite |
|
||||
| Manual `CI` dispatch with `test_scope=focused` and optional `base_sha` | Static gates plus the selected unit-test tier | Classifies `base_sha...HEAD` (or `HEAD^...HEAD`) and stays focused unless CI controls changed |
|
||||
| Desktop/package/release-workflow pull request, relevant `main` push, or manual `Desktop Artifacts` dispatch | Unsigned macOS, Linux, and Windows artifact jobs | Cross-platform packaging |
|
||||
| Trigger | Stable checks | Scope |
|
||||
| ---------------------------------------------------------------------- | --------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
||||
| Documentation-only pull request or merge | Static gates; test jobs record skip decisions | No workspace tests |
|
||||
| Ordinary code pull request or merge to `main` | `Lint & Type Check`, `Build`, `Security Audit`, scope recording | No workspace tests or coverage; affected packages remain visible |
|
||||
| Pull request with `ci:full` | Default checks plus every milestone test and artifact gate | Complete unit, coverage, desktop, Docker, and applicable integration gates |
|
||||
| Nightly 08:00 UTC or manual `CI` dispatch with `test_scope=full` | Static gates plus complete workspace and coverage gates | Authoritative recurring or operator-selected milestone |
|
||||
| Manual `CI` dispatch with `test_scope=focused` and optional `base_sha` | Static gates plus `Changed Tests` | Explicit diagnostic slice for affected workspaces; no coverage ratchet |
|
||||
| Manual `Desktop Artifacts` or `Docker Image Contract` dispatch | Selected artifact or container contract | Explicit operator milestone outside a pull request |
|
||||
|
||||
`Select Test Scope` is the decision record for each run. Its summary names the
|
||||
event, exact base/head range, changed-path count, selected tier, affected
|
||||
workspaces, and why `Changed Tests` or `Workspace Unit Tests` ran or skipped.
|
||||
The selector fails safe to the complete suite for unknown non-documentation
|
||||
paths and for changes to:
|
||||
|
||||
- GitHub Actions workflows and the selector itself
|
||||
- full-suite evidence validation
|
||||
|
||||
Shared contracts, package manifests, lockfiles, storage implementations,
|
||||
desktop source, and known-workspace deletions select focused affected
|
||||
workspaces. Build and typecheck remain whole-repository gates on every ordinary
|
||||
code pull request. The full workspace suite runs at scheduled, explicit
|
||||
`ci:full`, critical integration/security, and release milestones instead of
|
||||
being repeated for every source slice.
|
||||
desktop source, and known-workspace deletions are recorded as affected
|
||||
workspaces without launching tests. Build and typecheck remain
|
||||
whole-repository gates on every ordinary code pull request. The full workspace
|
||||
suite and release-grade artifact gates run at scheduled, explicit `ci:full`,
|
||||
critical integration/security, and release milestones.
|
||||
|
||||
Run the selector contract locally with:
|
||||
|
||||
|
|
@ -372,11 +370,11 @@ pnpm test:ci-scope
|
|||
Release validation remains the final authority: clean-clone build, full unit
|
||||
and integration suites, applicable E2E, and signed artifact verification.
|
||||
|
||||
The operational target for the default pull-request tier is under 15 minutes,
|
||||
with no desktop packaging. This is a target rather than an SLA; dependency
|
||||
installation and hosted-runner availability still vary. Behavior changes
|
||||
should include coverage reachable from the changed source so Vitest's related
|
||||
test selection can execute it.
|
||||
The operational target for the default pull-request tier is under 10 minutes,
|
||||
with no workspace tests, coverage, or desktop/container packaging. This is a
|
||||
target rather than an SLA; dependency installation and hosted-runner
|
||||
availability still vary. Behavior changes should include coverage that the
|
||||
next declared milestone can exercise.
|
||||
|
||||
Optional `Desktop Artifacts`, packaging previews, and release workflows are not
|
||||
merge blockers outside their path boundary. If one starts without providing
|
||||
|
|
|
|||
|
|
@ -50,9 +50,11 @@ live repository install into a production-only dependency state.
|
|||
|
||||
## GitHub Workflows
|
||||
|
||||
`Desktop Artifacts` runs on desktop/package/release-workflow pull requests,
|
||||
after server/web/shared/desktop changes merge to `main`, and on manual
|
||||
dispatch. It builds unsigned artifacts on:
|
||||
`Desktop Artifacts` runs only for a pull request carrying `ci:full` or through
|
||||
manual dispatch. Ordinary pull requests and `main` pushes do not package
|
||||
desktop applications. A release candidate keeps `ci:full` applied through its
|
||||
final synchronization so the artifacts correspond to the reviewed head. The
|
||||
workflow builds unsigned artifacts on:
|
||||
|
||||
- `macos-15`: DMG, ZIP, blockmap, and update YAML.
|
||||
- `ubuntu-24.04`: x64 AppImage, deb, rpm, blockmap, and update YAML.
|
||||
|
|
@ -187,7 +189,8 @@ policy is tracked in
|
|||
- Run `pnpm desktop:package:windows:unsigned` on Windows or the
|
||||
`Desktop Artifacts` Windows job and inspect preview artifact names. This is
|
||||
not a v6 GA release gate.
|
||||
- Run `Desktop Artifacts` and download the uploaded DMG/ZIP/update metadata.
|
||||
- Apply `ci:full` to the release-candidate pull request, then download the
|
||||
uploaded DMG/ZIP/update metadata from its `Desktop Artifacts` run.
|
||||
- Edit `docs/releases/vX.Y.Z.md`, run
|
||||
`pnpm validate:release -- --version X.Y.Z`, and publish that exact file with
|
||||
`gh release create --notes-file` or `gh release edit --notes-file`. Do not
|
||||
|
|
|
|||
|
|
@ -55,7 +55,11 @@ Documentation freshness: 2026-08-22 for Veritas Kanban 6.1.1.
|
|||
|
||||
## Final Release Validation Commands
|
||||
|
||||
Run once from the clean 6.1.1 release candidate:
|
||||
Apply `ci:full` to the release pull request and keep it applied through the
|
||||
final candidate synchronization. That single milestone runs the complete
|
||||
workspace suite, critical-path coverage, unsigned desktop artifacts, and
|
||||
Docker image contract. Run the following commands once from the clean 6.1.1
|
||||
release candidate:
|
||||
|
||||
```bash
|
||||
pnpm install --frozen-lockfile
|
||||
|
|
@ -128,8 +132,9 @@ the Homebrew cask uses the independently verified published ZIP checksum.
|
|||
- [x] Native About, copied support information, the desktop bridge, and updater
|
||||
fallback consume one authoritative version/build/channel/OS/architecture
|
||||
record (#1005).
|
||||
- [x] Pull-request verification uses documentation-only, focused, or full test
|
||||
scope, while explicit and milestone release gates remain full (#1000).
|
||||
- [x] Ordinary pull-request verification records affected workspaces without
|
||||
running tests; manual focused diagnostics and explicit `ci:full`,
|
||||
scheduled, or release milestones own the test suites (#1000, #1227).
|
||||
- [x] Published release notes are sourced from
|
||||
`docs/releases/vX.Y.Z.md`, use one full-width Markdown line per paragraph
|
||||
or list item, reject blockquotes and overlong prose blocks, and are
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@
|
|||
Review date: 2026-06-04
|
||||
|
||||
The scheduled QA workflow runs heavier browser and load-test coverage outside
|
||||
the fast pull-request path. Pull requests stay limited to lint, typecheck,
|
||||
workspace unit tests, build, production dependency audit, and the desktop
|
||||
artifact gate when relevant.
|
||||
the fast pull-request path. Ordinary pull requests stay limited to lint,
|
||||
typecheck, build, production dependency audit, security gates, and test-scope
|
||||
recording. Workspace tests, coverage, desktop artifacts, and Docker contracts
|
||||
run only at scheduled, manual, or `ci:full` milestones.
|
||||
|
||||
The 2026-06-04 audit found the workflow failing before job creation because
|
||||
job-level `env` used the `runner.temp` context. GitHub does not expose the
|
||||
|
|
@ -30,6 +31,7 @@ Workflow file:
|
|||
|
||||
Triggers:
|
||||
|
||||
- Pull request carrying `ci:full`, rerun on each synchronized candidate head.
|
||||
- Weekly schedule: Monday at 08:17 UTC.
|
||||
- Manual dispatch: `workflow_dispatch`.
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,9 @@ export const CADENCE_CONTRACTS = {
|
|||
pattern: /one independently shippable behavior/i,
|
||||
},
|
||||
{
|
||||
description: 'narrowest useful implementation loop',
|
||||
pattern: /run the narrowest useful loop/i,
|
||||
description: 'ordinary implementation defers workspace tests',
|
||||
pattern:
|
||||
/Do not run workspace unit, coverage, E2E, desktop packaging, or Docker contract tests between implementation PRs/i,
|
||||
},
|
||||
{
|
||||
description: '45-minute split or escalate delivery checkpoint',
|
||||
|
|
@ -62,7 +63,8 @@ export const CADENCE_CONTRACTS = {
|
|||
},
|
||||
{
|
||||
description: 'affected-boundary runtime smoke tests',
|
||||
pattern: /Run browser or API smoke tests only when the change affects/i,
|
||||
pattern:
|
||||
/Run browser or API smoke tests only at an explicit integration or release milestone/i,
|
||||
},
|
||||
],
|
||||
'.github/PULL_REQUEST_TEMPLATE.md': [
|
||||
|
|
@ -75,8 +77,8 @@ export const CADENCE_CONTRACTS = {
|
|||
pattern: /\*\*Verification tier:\*\*/i,
|
||||
},
|
||||
{
|
||||
description: 'focused changed-package test tier',
|
||||
pattern: /Focused changed-package tests/i,
|
||||
description: 'explicit focused diagnostic tier',
|
||||
pattern: /Explicit focused diagnostic/i,
|
||||
},
|
||||
{
|
||||
description: 'full milestone gate tier',
|
||||
|
|
@ -271,8 +273,7 @@ export function findUnsafeCanonicalCadenceStatements(files) {
|
|||
|
||||
export function findAmbiguousFocusedTestCommands(files) {
|
||||
const violations = [];
|
||||
const pattern =
|
||||
/\bpnpm\s+(?:--filter(?:=|\s+)|-F\s+)\S+\s+(?:run\s+)?test\s+--(?=\s)/gi;
|
||||
const pattern = /\bpnpm\s+(?:--filter(?:=|\s+)|-F\s+)\S+\s+(?:run\s+)?test\s+--(?=\s)/gi;
|
||||
|
||||
for (const [file, content] of Object.entries(files)) {
|
||||
const normalized = normalizeWhitespace(content);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { execFileSync } from 'node:child_process';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { appendFile } from 'node:fs/promises';
|
||||
import path, { matchesGlob } from 'node:path';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const COVERAGE_POLICY = JSON.parse(
|
||||
|
|
@ -13,15 +13,6 @@ const COVERAGE_POLICY = JSON.parse(
|
|||
);
|
||||
const WORKSPACE_NAMES = COVERAGE_POLICY.packages.map(({ id }) => id);
|
||||
|
||||
const FULL_SUITE_PATH_PATTERNS = [
|
||||
/^\.github\/workflows\//,
|
||||
/^scripts\/select-ci-test-scope(?:\.test)?\.mjs$/,
|
||||
/^scripts\/verify-full-suite-job-evidence(?:\.test)?\.mjs$/,
|
||||
/^scripts\/(?:run-coverage|check-coverage-(?:policy|ratchets))(?:\.test)?\.mjs$/,
|
||||
/^docs\/testing\/critical-path-coverage\.json$/,
|
||||
/^(?:server|web|cli|mcp|desktop)\/vitest\.config\.ts$/,
|
||||
];
|
||||
|
||||
const ALL_WORKSPACE_PATH_PATTERNS = [
|
||||
/^shared\//,
|
||||
/^package\.json$/,
|
||||
|
|
@ -32,19 +23,6 @@ const ALL_WORKSPACE_PATH_PATTERNS = [
|
|||
/^tsconfig(?:\.[^/]+)?\.json$/,
|
||||
];
|
||||
|
||||
const CRITICAL_COVERAGE_PATHS = Object.fromEntries(
|
||||
COVERAGE_POLICY.packages.map((packagePolicy) => [
|
||||
packagePolicy.id,
|
||||
[
|
||||
...packagePolicy.boundaries.flatMap(({ include }) => include),
|
||||
...(packagePolicy.runner?.testFiles ?? []).map(
|
||||
(testFile) => `${packagePolicy.id}/${testFile}`
|
||||
),
|
||||
...(packagePolicy.runner?.triggerPatterns ?? []),
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
const DOCUMENTATION_PATH_PATTERNS = [
|
||||
/\.md$/i,
|
||||
/^docs\//,
|
||||
|
|
@ -64,12 +42,6 @@ function normalizeFiles(files) {
|
|||
return [...new Set(files.map((file) => file.trim()).filter(Boolean))].sort();
|
||||
}
|
||||
|
||||
function summarizePaths(files) {
|
||||
const visible = files.slice(0, 5);
|
||||
const remainder = files.length - visible.length;
|
||||
return `${visible.join(', ')}${remainder > 0 ? `, and ${remainder} more` : ''}`;
|
||||
}
|
||||
|
||||
export function isDocumentationPath(file) {
|
||||
return DOCUMENTATION_PATH_PATTERNS.some((pattern) => pattern.test(file));
|
||||
}
|
||||
|
|
@ -78,10 +50,6 @@ export function isDependencyFreeScopeControlPath(file) {
|
|||
return DEPENDENCY_FREE_SCOPE_CONTROL_PATH_PATTERNS.some((pattern) => pattern.test(file));
|
||||
}
|
||||
|
||||
export function requiresFullSuite(file) {
|
||||
return FULL_SUITE_PATH_PATTERNS.some((pattern) => pattern.test(file));
|
||||
}
|
||||
|
||||
export function affectedWorkspaces(files) {
|
||||
const selected = new Set();
|
||||
|
||||
|
|
@ -96,15 +64,9 @@ export function affectedWorkspaces(files) {
|
|||
return WORKSPACE_NAMES.filter((name) => selected.has(name));
|
||||
}
|
||||
|
||||
export function coverageWorkspaces(files, scope = 'focused') {
|
||||
export function coverageWorkspaces(_files, scope = 'focused') {
|
||||
if (scope === 'full') return [...WORKSPACE_NAMES];
|
||||
if (scope === 'none') return [];
|
||||
|
||||
return WORKSPACE_NAMES.filter((workspace) =>
|
||||
files.some((file) =>
|
||||
CRITICAL_COVERAGE_PATHS[workspace].some((pattern) => matchesGlob(file, pattern))
|
||||
)
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
export function classifyCiTestScope({
|
||||
|
|
@ -113,9 +75,6 @@ export function classifyCiTestScope({
|
|||
labels = [],
|
||||
changedFiles = [],
|
||||
deletedFiles = [],
|
||||
reviewedFullSuite = false,
|
||||
reviewedPullRequest = '',
|
||||
reviewedFullSuiteMode = '',
|
||||
}) {
|
||||
const files = normalizeFiles(changedFiles);
|
||||
const deleted = normalizeFiles(deletedFiles);
|
||||
|
|
@ -148,46 +107,20 @@ export function classifyCiTestScope({
|
|||
};
|
||||
}
|
||||
|
||||
if (eventName === 'push' && reviewedFullSuite) {
|
||||
const prSuffix = reviewedPullRequest ? ` for PR #${reviewedPullRequest}` : '';
|
||||
const evidence =
|
||||
reviewedFullSuiteMode === 'identical-tree'
|
||||
? 'has the exact Git tree published by this squash merge'
|
||||
: 'is an ancestor of this merge commit';
|
||||
if (eventName === 'workflow_dispatch' && manualScope === 'focused') {
|
||||
if (packages.length === 0) {
|
||||
return {
|
||||
scope: 'none',
|
||||
packages: [],
|
||||
files,
|
||||
reason: 'The manually selected range does not affect a testable workspace.',
|
||||
};
|
||||
}
|
||||
return {
|
||||
scope: 'none',
|
||||
packages: [],
|
||||
scope: 'focused',
|
||||
packages,
|
||||
files,
|
||||
reason: `The reviewed head${prSuffix} already passed Workspace Unit Tests and Critical Path Coverage, and ${evidence}.`,
|
||||
};
|
||||
}
|
||||
|
||||
const unclassifiedDeletedCodePaths = deleted.filter(
|
||||
(file) =>
|
||||
!isDocumentationPath(file) &&
|
||||
!isDependencyFreeScopeControlPath(file) &&
|
||||
affectedWorkspaces([file]).length === 0
|
||||
);
|
||||
if (unclassifiedDeletedCodePaths.length > 0) {
|
||||
return {
|
||||
scope: 'full',
|
||||
packages: WORKSPACE_NAMES,
|
||||
files,
|
||||
reason: `Deleted non-documentation paths outside a known workspace fail safe to the full suite: ${summarizePaths(
|
||||
unclassifiedDeletedCodePaths
|
||||
)}`,
|
||||
};
|
||||
}
|
||||
|
||||
const fullSuitePaths = files.filter(requiresFullSuite);
|
||||
if (fullSuitePaths.length > 0) {
|
||||
return {
|
||||
scope: 'full',
|
||||
packages: WORKSPACE_NAMES,
|
||||
files,
|
||||
reason: `CI control paths require the full suite before the selector change can take effect: ${summarizePaths(
|
||||
fullSuitePaths
|
||||
)}`,
|
||||
reason: `Manual dispatch explicitly requested focused diagnostics for: ${packages.join(', ')}.`,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -207,41 +140,18 @@ export function classifyCiTestScope({
|
|||
packages: [],
|
||||
files,
|
||||
reason:
|
||||
'Only documentation or dependency-free delivery-cadence controls checked by the selector changed.',
|
||||
};
|
||||
}
|
||||
|
||||
const unclassifiedCodePaths = files.filter(
|
||||
(file) =>
|
||||
!isDocumentationPath(file) &&
|
||||
!isDependencyFreeScopeControlPath(file) &&
|
||||
affectedWorkspaces([file]).length === 0
|
||||
);
|
||||
if (unclassifiedCodePaths.length > 0) {
|
||||
return {
|
||||
scope: 'full',
|
||||
packages: WORKSPACE_NAMES,
|
||||
files,
|
||||
reason: `Non-documentation paths outside a known test workspace fail safe to the full suite: ${summarizePaths(
|
||||
unclassifiedCodePaths
|
||||
)}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (packages.length === 0) {
|
||||
return {
|
||||
scope: 'none',
|
||||
packages: [],
|
||||
files,
|
||||
reason: 'No testable workspace changed.',
|
||||
'Only documentation or dependency-free policy controls changed; workspace tests are reserved for explicit milestones.',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
scope: 'focused',
|
||||
scope: 'none',
|
||||
packages,
|
||||
files,
|
||||
reason: `Run Vitest related coverage for affected workspace packages: ${packages.join(', ')}.`,
|
||||
reason:
|
||||
packages.length > 0
|
||||
? `Ordinary changes defer workspace tests and coverage to an explicit milestone. Affected packages: ${packages.join(', ')}.`
|
||||
: 'This ordinary change is not an explicit test milestone; workspace tests and coverage are deferred.',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -337,9 +247,6 @@ async function main() {
|
|||
headSha: process.env.CI_HEAD_SHA || '',
|
||||
manualScope: process.env.CI_MANUAL_SCOPE || '',
|
||||
labels: parseLabels(process.env.CI_PR_LABELS || '[]'),
|
||||
reviewedFullSuite: process.env.CI_REVIEWED_FULL === 'true',
|
||||
reviewedPullRequest: process.env.CI_REVIEWED_PR || '',
|
||||
reviewedFullSuiteMode: process.env.CI_REVIEWED_MODE || '',
|
||||
};
|
||||
|
||||
if (!input.eventName) {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {
|
|||
githubOutputLines,
|
||||
isDependencyFreeScopeControlPath,
|
||||
isDocumentationPath,
|
||||
requiresFullSuite,
|
||||
} from './select-ci-test-scope.mjs';
|
||||
|
||||
test('classifies documentation-only pull requests without unit tests', () => {
|
||||
|
|
@ -37,39 +36,40 @@ test('dependency-free cadence controls do not trigger workspace unit tests', ()
|
|||
assert.equal(isDependencyFreeScopeControlPath('scripts/check-delivery-cadence.mjs'), true);
|
||||
});
|
||||
|
||||
test('cadence controls do not widen a focused workspace change', () => {
|
||||
test('cadence controls do not turn an ordinary workspace change into a test milestone', () => {
|
||||
const result = classifyCiTestScope({
|
||||
eventName: 'pull_request',
|
||||
changedFiles: ['scripts/check-delivery-cadence.mjs', 'server/src/routes/tasks.ts'],
|
||||
});
|
||||
|
||||
assert.equal(result.scope, 'focused');
|
||||
assert.equal(result.scope, 'none');
|
||||
assert.deepEqual(result.packages, ['server']);
|
||||
});
|
||||
|
||||
test('selects affected workspaces for ordinary code changes', () => {
|
||||
test('records affected workspaces without testing an ordinary code change', () => {
|
||||
const result = classifyCiTestScope({
|
||||
eventName: 'pull_request',
|
||||
changedFiles: ['web/src/App.tsx', 'server/src/routes/tasks.ts', 'README.md'],
|
||||
});
|
||||
|
||||
assert.equal(result.scope, 'focused');
|
||||
assert.equal(result.scope, 'none');
|
||||
assert.deepEqual(result.packages, ['server', 'web']);
|
||||
});
|
||||
|
||||
test('selects coverage only for changed critical boundaries and all packages for full scope', () => {
|
||||
test('selects critical coverage only for the full milestone scope', () => {
|
||||
const files = [
|
||||
'server/src/storage/file-storage.ts',
|
||||
'web/src/components/Board.tsx',
|
||||
'mcp/src/tools/tasks.ts',
|
||||
];
|
||||
|
||||
assert.deepEqual(coverageWorkspaces(files), ['server', 'mcp']);
|
||||
assert.deepEqual(coverageWorkspaces(files), []);
|
||||
assert.deepEqual(coverageWorkspaces(files, 'focused'), []);
|
||||
assert.deepEqual(coverageWorkspaces(files, 'none'), []);
|
||||
assert.deepEqual(coverageWorkspaces(files, 'full'), ['server', 'web', 'cli', 'mcp', 'desktop']);
|
||||
});
|
||||
|
||||
test('reruns coverage when governed tests, schemas, or shared permissions change', () => {
|
||||
test('defers governed critical-path coverage until a milestone', () => {
|
||||
assert.deepEqual(
|
||||
coverageWorkspaces([
|
||||
'server/src/__tests__/provider-completion-service.test.ts',
|
||||
|
|
@ -78,7 +78,7 @@ test('reruns coverage when governed tests, schemas, or shared permissions change
|
|||
'web/src/__tests__/useWebSocket.test.ts',
|
||||
'mcp/src/__tests__/task-tools.test.ts',
|
||||
]),
|
||||
['server', 'web', 'mcp']
|
||||
[]
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ test('ci:full overrides a documentation-only pull request', () => {
|
|||
assert.equal(result.scope, 'full');
|
||||
});
|
||||
|
||||
test('selects the full suite only for CI control paths', () => {
|
||||
test('CI control paths require an explicit milestone instead of auto-running the full suite', () => {
|
||||
const paths = [
|
||||
'.github/workflows/ci.yml',
|
||||
'scripts/select-ci-test-scope.mjs',
|
||||
|
|
@ -108,12 +108,11 @@ test('selects the full suite only for CI control paths', () => {
|
|||
eventName: 'pull_request',
|
||||
changedFiles: [file],
|
||||
});
|
||||
assert.equal(result.scope, 'full', file);
|
||||
assert.equal(requiresFullSuite(file), true, file);
|
||||
assert.equal(result.scope, 'none', file);
|
||||
}
|
||||
});
|
||||
|
||||
test('keeps shared, storage, desktop, and manifest changes focused by workspace', () => {
|
||||
test('records shared, storage, desktop, and manifest workspaces without automatic tests', () => {
|
||||
const cases = [
|
||||
{
|
||||
file: 'shared/src/types/task.types.ts',
|
||||
|
|
@ -131,9 +130,8 @@ test('keeps shared, storage, desktop, and manifest changes focused by workspace'
|
|||
eventName: 'pull_request',
|
||||
changedFiles: [file],
|
||||
});
|
||||
assert.equal(result.scope, 'focused', file);
|
||||
assert.equal(result.scope, 'none', file);
|
||||
assert.deepEqual(result.packages, packages, file);
|
||||
assert.equal(requiresFullSuite(file), false, file);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -168,60 +166,33 @@ test('focused manual runs still classify the selected range by risk', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('a successful reviewed full suite suppresses duplicate post-merge tests', () => {
|
||||
const result = classifyCiTestScope({
|
||||
eventName: 'push',
|
||||
reviewedFullSuite: true,
|
||||
reviewedPullRequest: '1000',
|
||||
changedFiles: ['.github/workflows/ci.yml'],
|
||||
});
|
||||
|
||||
assert.equal(result.scope, 'none');
|
||||
assert.match(result.reason, /PR #1000/);
|
||||
assert.match(result.reason, /ancestor/);
|
||||
});
|
||||
|
||||
test('an exact reviewed tree suppresses duplicate tests after a squash merge', () => {
|
||||
const result = classifyCiTestScope({
|
||||
eventName: 'push',
|
||||
reviewedFullSuite: true,
|
||||
reviewedPullRequest: '1011',
|
||||
reviewedFullSuiteMode: 'identical-tree',
|
||||
changedFiles: ['.github/workflows/ci.yml'],
|
||||
});
|
||||
|
||||
assert.equal(result.scope, 'none');
|
||||
assert.match(result.reason, /PR #1011/);
|
||||
assert.match(result.reason, /exact Git tree/);
|
||||
});
|
||||
|
||||
test('ordinary post-merge pushes remain limited to affected packages', () => {
|
||||
test('ordinary post-merge pushes defer tests while recording affected packages', () => {
|
||||
const result = classifyCiTestScope({
|
||||
eventName: 'push',
|
||||
changedFiles: ['cli/src/commands/doctor.ts'],
|
||||
});
|
||||
|
||||
assert.equal(result.scope, 'focused');
|
||||
assert.equal(result.scope, 'none');
|
||||
assert.deepEqual(result.packages, ['cli']);
|
||||
});
|
||||
|
||||
test('unknown non-documentation paths fail safe to the full suite', () => {
|
||||
test('unknown non-documentation paths wait for an explicit milestone', () => {
|
||||
const result = classifyCiTestScope({
|
||||
eventName: 'pull_request',
|
||||
changedFiles: ['site/src/runtime.ts'],
|
||||
});
|
||||
|
||||
assert.equal(result.scope, 'full');
|
||||
assert.equal(result.scope, 'none');
|
||||
});
|
||||
|
||||
test('deleted known-workspace source stays focused while unknown source fails safe', () => {
|
||||
test('deleted source paths are recorded without automatic test escalation', () => {
|
||||
assert.equal(
|
||||
classifyCiTestScope({
|
||||
eventName: 'pull_request',
|
||||
changedFiles: ['server/src/obsolete.ts'],
|
||||
deletedFiles: ['server/src/obsolete.ts'],
|
||||
}).scope,
|
||||
'focused'
|
||||
'none'
|
||||
);
|
||||
assert.equal(
|
||||
classifyCiTestScope({
|
||||
|
|
@ -229,7 +200,7 @@ test('deleted known-workspace source stays focused while unknown source fails sa
|
|||
changedFiles: ['site/src/obsolete.ts'],
|
||||
deletedFiles: ['site/src/obsolete.ts'],
|
||||
}).scope,
|
||||
'full'
|
||||
'none'
|
||||
);
|
||||
assert.equal(
|
||||
classifyCiTestScope({
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue