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
7 changes: 4 additions & 3 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ export default [
ignores: ['**/dist/**/*', '**/coverage/**/*', '**/out/**/*', '**/.turbo/**/*'],
},
{
// Comment hygiene across all package sources. `warn` until the codebase sweep lands, then `error`.
files: ['packages/**/*.ts'],
// Comment hygiene in production sources only. Test comments legitimately reference mock issue/PR
// data (fixture numbers, external-repo PRs), so bare `#NNN` there isn't a citation to flag.
files: ['packages/**/src/**/*.ts'],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand All @@ -43,7 +44,7 @@ export default [
local: { rules: { 'no-bare-issue-refs': noBareIssueRefs } },
},
rules: {
'local/no-bare-issue-refs': 'warn',
'local/no-bare-issue-refs': 'error',
},
},
{
Expand Down
2 changes: 1 addition & 1 deletion packages/config/test/unit/generate-schema.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe('generate-schema', () => {
const schema = buildSchema() as {
properties?: Record<string, { properties?: Record<string, { description?: string }> }>;
};
// ci.scopeLabels is the field #277 is about — it must keep its description
// ci.scopeLabels must keep its description
// (an empty description here is the exact regression this generator prevents).
const scopeLabels = schema.properties?.ci?.properties?.scopeLabels;
expect(scopeLabels?.description).toBeDefined();
Expand Down
2 changes: 1 addition & 1 deletion packages/config/test/unit/parse.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ describe('parseJsonc', () => {
expect(result).toEqual({ url: 'https://example.com/path?q=1&a=2' });
});

// Regression for #247: a real comment forces the JSONC path, and the `//`
// Regression: a real comment forces the JSONC path, and the `//`
// inside the $schema URL must not be treated as a line comment.
it('should parse a config with a comment and an https URL ($schema)', () => {
const jsonc = `{
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/changelogRefs.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable local/no-bare-issue-refs -- the #NNN below are examples of the reference format this module renders, not issue citations */
/**
* Neutralising bare GitHub-autolinked tokens in changelog output. GitHub auto-links a bare `#NNN`
* issue/PR ref (with a hovercard) and treats a bare `@scope/pkg` / `@user` as a mention (a stray
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/selectionRegion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function rkPreMarker(packageName: string): string {
}

/**
* Identity marker for a prerelease row's channel toggle — "graduate this package to stable" (#521),
* Identity marker for a prerelease row's channel toggle — "graduate this package to stable",
* the inverse of {@link rkPreMarker}. Machine-read, never the row's prose.
*/
export function rkGradMarker(packageName: string): string {
Expand Down
15 changes: 7 additions & 8 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export interface VersionPackageChangelog {
}

/**
* The resolved action a package's version landed on — purely additive observability (#420), it
* The resolved action a package's version landed on — purely additive observability, it
* never changes which version resolves. `'graduated'` means a prerelease was promoted to stable and
* any requested bump was ignored; `'bumped'` is the ordinary commit/label-driven bump;
* `'first-release'` is a package with no prior tag. Defined as a string-literal union (core cannot
Expand All @@ -64,8 +64,7 @@ export type VersionAction = 'first-release' | 'graduated' | 'bumped';
* The release channel a version sits on. Derived per-package and purely from the resolved
* version: `'prerelease'` when the version carries a semver prerelease segment (`…-<preid>.N`), else
* `'stable'`. A standing PR with permanently-mixed maturity carries both at once — some `'stable'`
* packages, some `'prerelease'` — each advancing along its own line. #486 (per-package graduation)
* and #487 (channel-grouped rendering) build on this.
* packages, some `'prerelease'` — each advancing along its own line.
*/
export type ReleaseChannel = 'stable' | 'prerelease';

Expand Down Expand Up @@ -126,8 +125,8 @@ export interface VersionPackageUpdate {
/**
* The resolved baseline version this update bumped from — the package's prior release, in the same
* consumer-tag display form the changelog carries ({@link VersionPackageChangelog.previousVersion}).
* Absent for a first release and whenever the baseline was unreachable / fell back to all-history
* (#339), so consumers derive the bump magnitude only when it is present. Optional for backwards
* Absent for a first release and whenever the baseline was unreachable / fell back to all-history,
* so consumers derive the bump magnitude only when it is present. Optional for backwards
* compatibility: standing-PR manifests written before this field existed omit it (old PRs still
* open), so every consumer must tolerate its absence (render a dash / skip the delta).
*/
Expand Down Expand Up @@ -163,7 +162,7 @@ export interface VersionPackageUpdate {
*/
prerequisiteOf?: string[];
/**
* The resolved version action for this update (#420): `'graduated'` (prerelease → stable, bump
* The resolved version action for this update: `'graduated'` (prerelease → stable, bump
* ignored), `'bumped'` (commit/label-driven), or `'first-release'`. Purely additive observability —
* it never affects which version resolved. Optional for backwards compatibility: standing-PR
* manifests produced before this field existed won't carry it, so every consumer must tolerate
Expand All @@ -173,12 +172,12 @@ export interface VersionPackageUpdate {
/** Short human-readable reason for {@link action} (e.g. `Graduated 1.0.0-next.1 → 1.0.0 (bump ignored).`). Optional, same back-compat rule as {@link action}. */
actionReason?: string;
/**
* The release channel this package's new version sits on (#485): `'prerelease'` when `newVersion`
* The release channel this package's new version sits on: `'prerelease'` when `newVersion`
* carries a semver prerelease segment, else `'stable'`. Derived from the resolved version via
* {@link deriveReleaseChannel}; a mixed standing PR carries both, each package advancing on its own
* line. Purely additive observability — it never affects which version resolves. Optional for
* backwards compatibility: standing-PR manifests written before this field existed omit it, so every
* consumer must tolerate its absence (re-derive from `newVersion`). #486/#487 build on it.
* consumer must tolerate its absence (re-derive from `newVersion`).
*/
channel?: ReleaseChannel;
}
2 changes: 1 addition & 1 deletion packages/core/test/unit/changelogRefs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ describe('neutralizeDescriptionRefs', () => {
const repo = 'https://github.com/octocat/hello';

it('should remove a parenthesised ref that is duplicated in the appended label', () => {
// #507: `(#467)` in the subject-derived description also appears as `closes #467` in the label.
// `(#467)` in the subject-derived description also appears as `closes #467` in the label.
expect(neutralizeDescriptionRefs('failed queued batch (#467)', ['#475', '#467'], 'link', repo)).toBe(
'failed queued batch',
);
Expand Down
8 changes: 4 additions & 4 deletions packages/forge/src/fake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ interface FakeComment {
*/
prNumber?: number;
/** The comment's author. Left undefined, a seeded comment reads as bot-authored (the common case).
* Seed `{ type: 'User' }` to simulate a human-authored forgery for author-binding tests (#556). */
* Seed `{ type: 'User' }` to simulate a human-authored forgery for author-binding tests. */
user?: CommentAuthor;
}

Expand Down Expand Up @@ -204,7 +204,7 @@ export class FakeForge implements Forge {

async findOpenIssueByLabel(label: string): Promise<IssueRef | null> {
// GitHub returns newest-first; approximate "newest" with the highest number so multiple seeded
// matches resolve the way production would (#462 review).
// matches resolve the way production would.
const matches = this.openIssues.filter((i) => i.labels.includes(label)).sort((a, b) => b.number - a.number);
return matches[0] ? { number: matches[0].number, url: matches[0].url } : null;
}
Expand All @@ -214,7 +214,7 @@ export class FakeForge implements Forge {
(c) => c.body.startsWith(marker) && (c.prNumber === undefined || c.prNumber === prNumber),
);
// Mirror the real adapter: prefer a bot-authored match, falling back to a human-authored one so
// the caller can still see (and reject) it when it's the only marker comment present (#556).
// the caller can still see (and reject) it when it's the only marker comment present.
return matches.find((c) => isBotComment(c)) ?? matches[0] ?? null;
}

Expand All @@ -232,7 +232,7 @@ export class FakeForge implements Forge {
async upsertMarkerComment(prNumber: number, marker: string, body: string): Promise<void> {
this.upsertedComments.push({ prNumber, marker, body });
const existing = await this.findComment(prNumber, marker);
// Only adopt a bot-authored comment; a human-authored marker is left alone (#556).
// Only adopt a bot-authored comment; a human-authored marker is left alone.
if (existing && isBotComment(existing)) {
await this.updateComment(existing.id, body);
} else {
Expand Down
8 changes: 4 additions & 4 deletions packages/forge/src/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@ export class GitHubForge implements Forge {
async findOpenIssueByLabel(label: string): Promise<IssueRef | null> {
// listForRepo returns issues AND PRs; a PR carries a `pull_request` field, so skip those — we
// only want a real issue. Paginate rather than read one page, so a page full of label-carrying
// PRs (or an older reusable issue) can't hide the issue and leave the caller stacking a duplicate
// (#462 review). Default sort is newest-first, so the first real issue we reach is the most recent.
// PRs (or an older reusable issue) can't hide the issue and leave the caller stacking a duplicate.
// Default sort is newest-first, so the first real issue we reach is the most recent.
const iterator = this.octokit.paginate.iterator(this.octokit.rest.issues.listForRepo, {
...this.base,
state: 'open',
Expand Down Expand Up @@ -211,7 +211,7 @@ export class GitHubForge implements Forge {
per_page: 100,
});
// Prefer a bot-authored match over a human-authored one carrying the same marker: a pre-seeded
// marker comment (write access, or the upsert race) must never shadow the bot's real one (#556).
// marker comment (write access, or the upsert race) must never shadow the bot's real one.
// A human-authored match is still returned when it's the only one, so the caller can reject it.
let humanMatch: ForgeComment | null = null;
for await (const response of iterator) {
Expand Down Expand Up @@ -241,7 +241,7 @@ export class GitHubForge implements Forge {
const existing = await this.findComment(prNumber, marker);
// Only adopt (update in place) a comment the bot itself authored. A human-authored comment
// carrying the marker — pre-seeded to win the adoption race — is left alone; the bot posts its
// own so its content is authoritative and never laundered through an attacker-owned comment (#556).
// own so its content is authoritative and never laundered through an attacker-owned comment.
if (existing && isBotComment(existing)) {
await this.updateComment(existing.id, body);
} else {
Expand Down
4 changes: 2 additions & 2 deletions packages/forge/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export interface PullRequestDetails {

/** The author of a comment, as reported by the forge. `type` is `'Bot'` for a GitHub App / bot
* actor (the tool itself), `'User'` for a human. Used to bind a machine-state marker comment (e.g.
* the release manifest) to the bot identity so a human-authored forgery is never trusted (#556). */
* the release manifest) to the bot identity so a human-authored forgery is never trusted. */
export interface CommentAuthor {
login?: string;
type?: string;
Expand All @@ -92,7 +92,7 @@ export interface ForgeComment {
* Whether a marker comment was authored by the bot/app identity (the tool) rather than a human.
* A machine-state marker (the release manifest, previews, failure reports) is only trustworthy when
* the bot wrote it; a human-authored comment carrying the same marker is a forgery/pre-seed and must
* not be adopted or trusted (#556).
* not be adopted or trusted.
*
* Fails open on an unknown author (`user` absent) — the real GitHub adapter always reports one, so
* `undefined` only occurs on the fake/legacy paths, where treating it as ours preserves behavior.
Expand Down
1 change: 1 addition & 0 deletions packages/notes/src/input/conventional-changelog.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable local/no-bare-issue-refs -- the #NNN here are regex format examples, not issue citations */
import * as fs from 'node:fs';
import type { ChangelogEntry, ChangelogInput, PackageChangelog } from '../core/types.js';
import { InputParseError } from '../errors/index.js';
Expand Down
2 changes: 1 addition & 1 deletion packages/notes/src/output/versioned.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type { TemplateContext } from '../core/types.js';
* flat path would silently overwrite another package that shares a version number — the history loss
* this mode exists to prevent. Each release writes a *new* file keyed by version; re-running the same
* release rewrites it idempotently. Standalone so both the live pipeline and the notes-backfill
* command (#293) share one writer.
* command share one writer.
*/
export function writeVersionedNotes(
contexts: TemplateContext[],
Expand Down
2 changes: 1 addition & 1 deletion packages/publish/src/stages/prepare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export async function runPrepareStage(ctx: PipelineContext): Promise<void> {

// Update Cargo.toml versions for cargo packages
if (config.cargo.enabled) {
// Cargo.lock files to stage alongside the manifests (#496), deduped — crates in one workspace
// Cargo.lock files to stage alongside the manifests, deduped — crates in one workspace
// share a single workspace-root lock. We re-sync the lock here (rather than trust the version
// step) so the direct-commit flow — git-commit stages an explicit file list, not `git add -A` —
// is self-sufficient and correct even run in isolation. The standing-PR flow stages the lock via
Expand Down
2 changes: 1 addition & 1 deletion packages/publish/src/utils/cargo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export { parseCargoToml };

/**
* Refresh a crate's `Cargo.lock` self-version entry after its `Cargo.toml` was bumped, so the
* committed lock doesn't drift (#496). `cargo update --workspace --offline` moves only workspace
* committed lock doesn't drift. `cargo update --workspace --offline` moves only workspace
* members' own lock entries to match their manifests — registry dependencies are never re-resolved.
*
* Returns the lock path (so the caller can stage it) or `undefined` when there's no committed lock,
Expand Down
8 changes: 4 additions & 4 deletions packages/publish/test/integration/publish-e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/**
* End-to-end coverage of the NON-dry-run publish pipeline (#580).
* End-to-end coverage of the NON-dry-run publish pipeline.
*
* Every other test layer bypasses the real publish path — Examples Smoke runs `--dry-run` (registry
* auth is dry-run-exempt), the Action harness sets `SKIP_PUBLISH`, and unit specs mock the registries.
* That gap is exactly why #578 (cargo auth demanded on a crateless repo) shipped despite a green suite.
* That gap is exactly why cargo auth demanded on a crateless repo shipped despite a green suite.
* This spec drives the real `runPipeline` with `dryRun: false` so registry auth, discovery, stage
* ordering, tagging and the GitHub-Release call all run for real.
*
Expand All @@ -16,7 +16,7 @@
* ordering, not the upload itself.
*
* The lightweight guards (enabled-but-empty no-op, missing-credential detection) run by default — they
* need no network and are the direct #578-class regression net. The heavier real-`npm publish` cases
* need no network and are the direct regression net. The heavier real-`npm publish` cases
* are opt-in via `RELEASEKIT_PUBLISH_E2E=1` so the default gate stays fast (see the PR body).
*/
import { execFileSync } from 'node:child_process';
Expand Down Expand Up @@ -265,7 +265,7 @@ afterEach(async () => {
// ===========================================================================
describe('Publish pipeline e2e (non-dry-run guards)', () => {
it('should no-op enabled-but-empty cargo and pub stages without demanding credentials', async () => {
// The #578 regression class: cargo/pub enabled on a repo with no crates/pub packages must not
// The regression class: cargo/pub enabled on a repo with no crates/pub packages must not
// fail on a missing registry token. A dry run can't catch this (auth is dry-run-exempt); this is
// a REAL non-dry-run run with no CARGO_REGISTRY_TOKEN / PUB_TOKEN present.
delete process.env.CARGO_REGISTRY_TOKEN;
Expand Down
2 changes: 1 addition & 1 deletion packages/publish/test/unit/stages/github-release.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ describe('github-release stage', () => {
});

it('should match a sanitized "name@vX.Y.Z" tag to release notes keyed by scoped package name', async () => {
// Regression for #288: tagTemplate "${packageName}@v${version}" sanitizes a scoped name into
// Regression: tagTemplate "${packageName}@v${version}" sanitizes a scoped name into
// the tag (e.g. wdio-tauri-plugin@v1.1.0). Previously neither the raw-"@" nor the sanitized-"-"
// branch matched it, so notes silently fell back to GitHub auto-generated notes.
const { execCommand } = await import('../../../src/utils/exec.js');
Expand Down
2 changes: 1 addition & 1 deletion packages/release/src/backfill/reconstruct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export interface ReconstructedVersion {
* tag with its predecessor to scope the commit range (`prevTag..tag`; the first tag uses all commits
* reachable from it). Tags are re-sorted ascending by semver so the pairing is chronological by
* version regardless of creation order. This is the offline counterpart of the live version stage's
* per-package changelog build, for the notes-backfill command (#293).
* per-package changelog build, for the notes-backfill command.
*/
export async function reconstructChangelogs(opts: ReconstructOptions): Promise<ReconstructedVersion[]> {
// Package-specific repos tag each package (`pkg@v1.2.0`); sync/single repos share one global tag
Expand Down
2 changes: 1 addition & 1 deletion packages/release/src/commands/backfill-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function readPackageJson(pkgPath: string): { name?: string; repoUrl?: string } {
* version's notes from git history. Renders each version through the notes pipeline and writes the
* result to per-version files (`notes.releaseNotes.file.dir`), to the matching GitHub release bodies
* (`--update-releases`), or both. Single package by default; `--all` discovers every workspace
* package. Dry-run by default. (#293 — LLM caching and the Action surface are follow-ups.)
* package. Dry-run by default. (LLM caching and the Action surface are follow-ups.)
*/
export function createBackfillCommand(): Command {
return new Command('backfill')
Expand Down
Loading