Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .agents/skills/branching-and-release-model/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,8 @@ rather than guessing from the repo's contents.
build height. That height is both wrong (the real height differs) and a maintenance trap.
"Correcting" `1.0` to `1.0.0` is a defect.
- **A no-op publish (unchanged NBGV `SemVer2`) on a schedule or push trigger re-pushes nothing to
any target keyed on the version string.** Docker is the exception: it always re-pushes (a dispatch
refreshes the release instead of skipping) to pick up upstream base-image
refreshes. Full guarantee and the `version.json` `pathFilters` boundary:
any target keyed on the version string.** Docker is the exception: it always re-pushes to pick up
upstream base-image refreshes. Full guarantee and the `version.json` `pathFilters` boundary:
`references/release-publish-mechanics.md`.
- **A package push can fail after the release is already cut**, since it runs after the release
task and no gate covers it. A full re-run is always available inside its bounded
Expand Down
5 changes: 2 additions & 3 deletions .agents/skills/comment-and-doc-style/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,8 @@ incrementally, and this section names each check as it ships.

- **Short sentences: at most 25 words in one sentence**, ASD-STE100's descriptive cap, checked by
the `sentence-length` rule in `prose_lint.py`. The rule reads Markdown prose, code comments, and
docstrings. The check is opt-in like `sentence-split`, because the existing corpus predates the
cap and a default gate would fail whole files nobody is editing. Write new prose under the cap,
and scope a run to a change with `--check sentence-length --diff <base>`.
docstrings. It runs by default, so a bare run checks it. A run with `--diff <base>` checks each
sentence a changed line belongs to, in full. Write new prose under the cap.
- **One instruction per sentence.** A procedure step states one action, and a second action is a
second step. No pattern reaches this, so it is authoring discipline with no check.
- **Active voice, imperative mood for procedure steps.** Write "run the gate", never "the gate
Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/local-strict-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Task: adversarial review of this branch's diff against its merge-base with its t
read full surrounding files where the diff hunks alone do not give enough context.
Run `python3 <hub-checkout>/scripts/prose_lint.py --diff "$(git merge-base refs/remotes/origin/<target> HEAD)"`.
Add `--allow-comments` where the change carries, or will open carrying, the `comments` label.
Then run the same command again with `--check sentence-length` added, and report each output as findings.
Report its output as findings.
Worktree: <worktree>, the checkout holding the branch.
Start every command with `cd <worktree> &&`, since a harness can reset the working directory between calls.
Read each listed file at its path under `<worktree>`.
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ff60e0fba2b7e0c4
6d8df70427b01dab
Original file line number Diff line number Diff line change
@@ -1 +1 @@
b8e088d656d9ff94
846e20f174fbe3a7
Original file line number Diff line number Diff line change
@@ -1 +1 @@
c5ee59056c13d054
8303ad535ff03847
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,8 @@ rather than guessing from the repo's contents.
build height. That height is both wrong (the real height differs) and a maintenance trap.
"Correcting" `1.0` to `1.0.0` is a defect.
- **A no-op publish (unchanged NBGV `SemVer2`) on a schedule or push trigger re-pushes nothing to
any target keyed on the version string.** Docker is the exception: it always re-pushes (a dispatch
refreshes the release instead of skipping) to pick up upstream base-image
refreshes. Full guarantee and the `version.json` `pathFilters` boundary:
any target keyed on the version string.** Docker is the exception: it always re-pushes to pick up
upstream base-image refreshes. Full guarantee and the `version.json` `pathFilters` boundary:
`references/release-publish-mechanics.md`.
- **A package push can fail after the release is already cut**, since it runs after the release
task and no gate covers it. A full re-run is always available inside its bounded
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,8 @@ incrementally, and this section names each check as it ships.

- **Short sentences: at most 25 words in one sentence**, ASD-STE100's descriptive cap, checked by
the `sentence-length` rule in `prose_lint.py`. The rule reads Markdown prose, code comments, and
docstrings. The check is opt-in like `sentence-split`, because the existing corpus predates the
cap and a default gate would fail whole files nobody is editing. Write new prose under the cap,
and scope a run to a change with `--check sentence-length --diff <base>`.
docstrings. It runs by default, so a bare run checks it. A run with `--diff <base>` checks each
sentence a changed line belongs to, in full. Write new prose under the cap.
- **One instruction per sentence.** A procedure step states one action, and a second action is a
second step. No pattern reaches this, so it is authoring discipline with no check.
- **Active voice, imperative mood for procedure steps.** Write "run the gate", never "the gate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Task: adversarial review of this branch's diff against its merge-base with its t
read full surrounding files where the diff hunks alone do not give enough context.
Run `python3 <hub-checkout>/scripts/prose_lint.py --diff "$(git merge-base refs/remotes/origin/<target> HEAD)"`.
Add `--allow-comments` where the change carries, or will open carrying, the `comments` label.
Then run the same command again with `--check sentence-length` added, and report each output as findings.
Report its output as findings.
Worktree: <worktree>, the checkout holding the branch.
Start every command with `cd <worktree> &&`, since a harness can reset the working directory between calls.
Read each listed file at its path under `<worktree>`.
Expand Down
15 changes: 10 additions & 5 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
# The two trees below are what scripts/build_dist.py generates from .agents/skills/ and CI holds current, so a finding in either belongs at its source and a review of every copy is one finding three times.
# The two trees below are what scripts/build_dist.py generates from .agents/skills/ and CI holds current.
# A finding in either therefore belongs at its source, and a review of every copy is one finding three times.
reviews:
# Auto review covers only the default branch unless a base is listed here, each entry a regex, and the default branch stays included whatever is listed.
# Auto review covers only the default branch unless a base is listed here, each entry a regex.
# The default branch stays included whatever is listed.
auto_review:
enabled: true
base_branches:
- "^develop$"
# A fleet pull request routinely passes five pushes before it merges, and the default pauses after five reviewed commits, which reads as a reviewer that stopped.
# A fleet pull request routinely passes five pushes before it merges.
# The default pauses after five reviewed commits, which reads as a reviewer that stopped.
auto_pause_after_reviewed_commits: 0
path_filters:
- "!.github/skills/**"
- "!.claude-plugin/fleet-skills/**"
# Canonical prose is linted for style in CI and read whole by the local review pass, so a review comment on it earns its place only by naming something false or unfollowable.
# Canonical prose is linted for style in CI and read whole by the local review pass.
# A review comment on it therefore earns its place only by naming something false or unfollowable.
path_instructions:
- path: "**/*.md"
instructions: |
Expand All @@ -21,7 +25,8 @@ reviews:
suggested_labels: false
suggested_reviewers: false
in_progress_fortune: false
# CI runs these four linters and fails the pull request on them, so a review comment from the same tool is a second copy of a red check.
# CI runs these four linters and fails the pull request on them.
# A review comment from the same tool is therefore a second copy of a red check.
tools:
markdownlint:
enabled: false
Expand Down
3 changes: 2 additions & 1 deletion .github/actions/nuget-build-default/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ runs:
path: |
${{ runner.temp }}/publish/*.nupkg
${{ runner.temp }}/publish/*.snupkg
# A project that packs nothing would otherwise only warn, create no artifact at all, cut a release, and fail one job later on a download that cannot say why.
# A project that packs nothing would otherwise only warn and create no artifact at all.
# It would still cut a release, and fail one job later on a download that cannot say why.
if-no-files-found: error
# Consumed within the run by the caller stub's publish-nuget job, so minimize artifact storage.
retention-days: 1
Expand Down
25 changes: 16 additions & 9 deletions .github/actions/prose-gate/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,17 @@ runs:
id: gate
shell: bash
env:
# A caller reaches this gate at whatever hub commit its workflow pins, and only the action knows which one that is, since a checked-out action carries no history to read it from.
# A caller reaches this gate at whatever hub commit its workflow pins.
# Only the action knows which commit that is, since a checked-out action carries no history to read it from.
# Unnamed, a finding from a stale pin reproduces against no local run and reads as a gate defect rather than as a version gap.
# Both the context and the default environment variable are read, because the context's value for a repository-relative reference is undocumented and the two are populated by different paths.
# Either being empty leaves the script to fall back to its own checkout, which is the correct answer whenever the action was not fetched from a pin.
# This repository calls the reusable workflow by a workspace-relative path, so its own runs may well take that fallback, and a downstream repository calling it at a pin is the case these exist for.
# What each form populates is read off a real run rather than predicted here, since predicting it is what a verdict naming its own copy exists to stop anyone having to do.
# Both the context and the default environment variable are read.
# The reasons are that the context's value for a repository-relative reference is undocumented, and that the two are populated by different paths.
# Either being empty leaves the script to fall back to its own checkout.
# That is the correct answer whenever the action was not fetched from a pin.
# This repository calls the reusable workflow by a workspace-relative path, so its own runs may well take that fallback.
# A downstream repository calling it at a pin is the case these exist for.
# What each form populates is read off a real run rather than predicted here.
# This is because predicting it is what a verdict naming its own copy exists to stop anyone having to do.
ACTION_REPOSITORY: ${{ github.action_repository }}
ACTION_REF: ${{ github.action_ref }}
run: |
Expand Down Expand Up @@ -62,7 +67,8 @@ runs:
# Check the base resolves before scanning, so an empty or absent ref fails naming itself.
# Unresolvable, the run would report the repository's whole backlog against this change.
# A shallow checkout is the usual cause, so the caller fetches full history.
# A base that is a freshly created merge commit can still lag GitHub's own replication, so retry with backoff before failing rather than treat that lag as a gate failure.
# A base that is a freshly created merge commit can still lag GitHub's own replication.
# So retry with backoff before failing, rather than treat that lag as a gate failure.
resolved=false
for delay in 0 2 4 8 15 30; do
if [ "$delay" -gt 0 ]; then
Expand Down Expand Up @@ -109,7 +115,8 @@ runs:
inert_excludes="${inert_excludes//$'\r'/'\x0d'}"
echo "Excluding ${#excluded_paths[@]} path(s) from $exclude_file: ${inert_excludes//'##['/'##\x5b'}"
fi
# Naming a repository with no ref, or a ref with no repository, would be worse than naming nothing, since the value exists to identify one commit.
# Naming a repository with no ref, or a ref with no repository, would be worse than naming nothing.
# That is because the value exists to identify one commit.
# Defaulted rather than read bare, since `set -u` makes an unset variable fatal and a harness that runs this block outside Actions sets neither.
action_repository="${ACTION_REPOSITORY:-}"
action_ref="${ACTION_REF:-}"
Expand All @@ -129,6 +136,6 @@ runs:
provenance_args=(--provenance "${action_repository}@${action_ref}")
fi
# Options first, then `--`, so every remaining token is read as a literal path.
# A path-shaped token beginning with a dash would otherwise parse as an option, and
# `--list-files` in that position turns the gate into a file listing that exits 0.
# A path-shaped token beginning with a dash would otherwise parse as an option.
# In that position, `--list-files` turns the gate into a file listing that exits 0.
uv run --no-project --script "$GATE_PATH/prose_lint.py" --diff "$BASE" "${exclude_args[@]}" "${allow_args[@]}" "${provenance_args[@]}" -- "${scan[@]}"
2 changes: 1 addition & 1 deletion .github/actions/prose-gate/prose-gate.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"comment-added": { "default": true, "summary": "a comment line this change adds or edits, opening its own line" },
"dupword": { "default": true, "summary": "a duplicated consecutive word" },
"sentence-split": { "default": false, "summary": "a sentence wrapping across lines" },
"sentence-length": { "default": false, "summary": "a sentence over the word cap" },
"sentence-length": { "default": true, "summary": "a sentence over the word cap" },
"spelling": { "default": true, "summary": "a British spelling where the repo convention is US English" },
"home-path": { "default": true, "summary": "an absolute home path naming a real account" },
"dead-path": { "default": true, "summary": "a mention of a path git once tracked and the tree no longer holds" },
Expand Down
30 changes: 20 additions & 10 deletions .github/actions/prose-gate/prose_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,25 @@
# ///
"""Enforce the GOVERNANCE.md "Documentation Style Conventions" rules no other linter checks.

Three layers: extract text units through Pygments and markdown-it-py, normalize them per language
from the `prose-gate.json` beside this script, and evaluate each rule in `RULE_TABLE` on a unit. The
JSON holds the language data, each rule's message and default, and the vocabulary and exemptions.
Three layers:

- Extract text units through Pygments and markdown-it-py.
- Normalize them per language from the `prose-gate.json` beside this script.
- Evaluate each rule in `RULE_TABLE` on a unit.

The JSON holds the language data, each rule's message and default, and the vocabulary and
exemptions.

A repository may carry `.prose-gate.json` at its root with additions to the carried exemptions and
nothing else, each a list merged over the carried one: `hub_hosted` and `home_path_exempt` take
paths, `generated_trees` directory names, `service_accounts` account names, `dupword_allow` and
`spelling_allow` phrases and words, and `tool_directives` directive names matched literally. Any
other key is refused, since an override that silently does nothing reads as one that works.
nothing else. Each is a list merged over the carried one:

- `hub_hosted` and `home_path_exempt` take paths.
- `generated_trees` takes directory names.
- `service_accounts` takes account names.
- `dupword_allow` and `spelling_allow` take phrases and words.
- `tool_directives` takes directive names matched literally.

Any other key is refused, since an override that silently does nothing reads as one that works.

Exit 1 on a finding, 2 when the run cannot be scoped. Read-only. Every git read names its encoding,
with surrogateescape, so a byte the locale cannot map neither ends the read nor changes a name.
Expand Down Expand Up @@ -610,8 +620,8 @@ def comment_lines(
) -> list[tuple[Line, bool]]:
"""Every comment line in `text`, and whether it is a documentation comment.

A comment opening on no marker the language declares belongs to a language embedded in it,
such as a script in HTML, and is left out. `shift` is the line `text` starts after.
A comment opening on no marker the language declares belongs to a language embedded in it, such
as a script in HTML. Such a comment is left out. `shift` is the line `text` starts after.
"""
starts = Starts(text)
out: list[tuple[Line, bool]] = []
Expand Down Expand Up @@ -707,7 +717,7 @@ def split_heredocs(text: str) -> list[tuple[int, str]]:

A body's prose can hold an apostrophe, which the shell lexer reads as a string running on to the
next one anywhere after it. The bodies come from the lexer that knows shell state, run on the
script without its YAML indent, and each is lexed alone so that string ends with the body.
script without its YAML indent. Each is lexed alone so that string ends with the body.
"""
rows = text.splitlines(keepends=True)
indent = min((len(r) - len(r.lstrip()) for r in rows if r.strip()), default=0)
Expand Down
3 changes: 2 additions & 1 deletion .github/actions/pypi-build-default/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ runs:
with:
name: pypi-build-${{ inputs.branch }}
path: ${{ inputs.project-dir }}/dist/*
# A build that emits nothing would otherwise only warn, create no artifact at all, and fail one job later on a download that cannot say why.
# A build that emits nothing would otherwise only warn and create no artifact at all.
# It would fail one job later on a download that cannot say why.
if-no-files-found: error
# Consumed within the run by the caller stub's publish-pypi job, so minimize artifact storage.
retention-days: 1
26 changes: 15 additions & 11 deletions .github/actions/python-directories/python_directories.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@
keeps the root, where a root pyproject.toml is tracked, which is the only directory the
validator gated before the input existed.

The declaration is what decides what is gated, never discovery, since a stray or vendored
`.py` file would otherwise change the gate silently. Tracked `.py` files that no resolved
directory covers are therefore reported as a warning rather than gated, and the fleet audit,
which compares the declaration against the registry, is where they become a finding.
The declaration is what decides what is gated, never discovery, since a stray or vendored `.py` file
would otherwise change the gate silently. Tracked `.py` files that no resolved directory covers are
therefore reported as a warning rather than gated. The fleet audit, which compares the declaration
against the registry, is where they become a finding.

Each directory is classed by the shape that decides how its tools run: `uv` for a tracked
uv.lock of its own, `pip` for a requirements*.txt beside it, `lint-only` for a pyproject.toml
with no [project] or [build-system] table, and `unsupported` for a project with none of those,
whose dependencies the validator cannot install. A uv workspace member is that last shape,
since its lock belongs to the workspace root, which is the directory to declare instead.
Each directory is classed by the shape that decides how its tools run:

- `uv` for a tracked uv.lock of its own.
- `pip` for a requirements*.txt beside it.
- `lint-only` for a pyproject.toml with no [project] or [build-system] table.
- `unsupported` for a project with none of those, whose dependencies the validator cannot install.

A uv workspace member is that last shape, since its lock belongs to the workspace root, which is the
directory to declare instead.

Writes `projects` (one `<shape><TAB><directory>` line each), `any`, and `declared` to
GITHUB_OUTPUT. Exits 1 on a declaration naming a path that is not a tracked Python project.
Expand Down Expand Up @@ -102,8 +106,8 @@ def in_directory(directory: str, name: str) -> str:
def shape(directory: str, tracked_set: set[str], read: Callable[[str], str]) -> str:
"""How the directory's tools run, from its tracked files and its pyproject.toml.

The pyproject.toml is parsed first whatever the shape, so a malformed one fails here rather than
reading as a project with no [project] table in a later step.
The pyproject.toml is parsed first whatever the shape. A malformed one therefore fails here
rather than reading as a project with no [project] table in a later step.
"""
config = tomllib.loads(read(in_directory(directory, "pyproject.toml")))
if in_directory(directory, "uv.lock") in tracked_set:
Expand Down
Loading
Loading