Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
cfd01bf
Add Rush reporter overhaul RFC
TheLarkInn Jul 12, 2026
92cc427
Add @rushstack/reporter public beta package scaffold
TheLarkInn Jul 14, 2026
4f155b8
Add rush change file for @rushstack/reporter
TheLarkInn Jul 14, 2026
267a350
Add canonical reporter event envelope and core event type union
TheLarkInn Jul 14, 2026
c518c06
Add rush change file for reporter event envelope
TheLarkInn Jul 14, 2026
61f23a2
Add scoped producer API for reporter events
TheLarkInn Jul 14, 2026
88077ba
Add rush change file for scoped producer API
TheLarkInn Jul 14, 2026
7fdb0ea
Add structured diagnostics with code registry and field privacy
TheLarkInn Jul 14, 2026
780e97a
Add rush change file for structured diagnostics
TheLarkInn Jul 14, 2026
e4b4384
Add NDJSON wire protocol with handshake and size limits
TheLarkInn Jul 14, 2026
61bfa6c
Add rush change file for NDJSON wire protocol
TheLarkInn Jul 14, 2026
f1947bb
Add ReporterManager lifecycle, ordering, and fan-out
TheLarkInn Jul 14, 2026
00a9988
Add rush change file for ReporterManager
TheLarkInn Jul 14, 2026
82d4a1e
Add protocol, compatibility, and legacy output goldens
TheLarkInn Jul 14, 2026
4eac7b2
Add rush change file for reporter goldens
TheLarkInn Jul 14, 2026
67bcef3
Add @rushstack/reporter to the repository README package list
TheLarkInn Jul 15, 2026
874dc3d
Address reporter contract review feedback
TheLarkInn Jul 16, 2026
18b5f38
Realign @rushstack/rush-reporter contracts with design review decisions
TheLarkInn Aug 19, 2026
1f80473
Add two-stage bootstrap event buffer and handoff
TheLarkInn Jul 14, 2026
e4a78a6
Add rush change file for bootstrap handoff
TheLarkInn Jul 15, 2026
91c98c2
Add frontend reporter host and bootstrap handoff replay
TheLarkInn Jul 15, 2026
af56a1c
Add rush change file for frontend reporter host
TheLarkInn Jul 15, 2026
18bc4ff
Add cross-version reporter compatibility adapters
TheLarkInn Jul 15, 2026
630ee83
Add rush change file for compatibility adapters
TheLarkInn Jul 15, 2026
7aa64c9
Harden bootstrap handoff with nonce authentication and owner-only per…
TheLarkInn Aug 19, 2026
51180be
Fix the README package badge for the @rushstack/rush-reporter rename
TheLarkInn Aug 19, 2026
719230e
Address reporter contract review follow-up
TheLarkInn Aug 21, 2026
3657add
Preserve NDJSON records across decode errors
TheLarkInn Aug 21, 2026
a3aacd8
Avoid Error cause target conflicts
TheLarkInn Aug 21, 2026
1ce5da2
Merge reporter contracts base
TheLarkInn Aug 21, 2026
b7baa7c
Fetch full history for change verification
TheLarkInn Aug 21, 2026
1bcf05a
Mark reporter integration features partial
TheLarkInn Aug 22, 2026
658c3e3
Merge main after PR retarget
TheLarkInn Aug 24, 2026
0f0756a
Harden reporter bootstrap handoff
TheLarkInn Aug 24, 2026
4d1ddc2
Address reporter bootstrap review feedback
TheLarkInn Aug 24, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:

- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 2
fetch-depth: 0
path: repo-a

- name: Git config user
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@rushstack/rush-reporter",
"comment": "Add the two-stage bootstrap machinery: a frozen self-contained event buffer/encoder with 1 MiB overflow handling and a bufferTruncated event, temporary NDJSON handoff file helpers, and an early reporter controls parser",
"type": "minor"
}
],
"packageName": "@rushstack/rush-reporter",
"email": "TheLarkInn@users.noreply.github.com"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@rushstack/rush-reporter",
"comment": "Add ReporterHost: the frontend-owned manager that replays and deletes the bootstrap handoff, exposes a typed sink to rush-lib without selection ownership, skips direct invocations, and cleans abandoned handoff files",
"type": "minor"
}
],
"packageName": "@rushstack/rush-reporter",
"email": "TheLarkInn@users.noreply.github.com"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@rushstack/rush-reporter",
"comment": "Add cross-version compatibility adapters: resolveReporterCompatibility, createEngineSink with a LegacyFallbackSink for old-frontend/new-engine fallback, and OldEngineOutputAdapter that bridges an old engine's raw output into structured events",
"type": "minor"
}
],
"packageName": "@rushstack/rush-reporter",
"email": "TheLarkInn@users.noreply.github.com"
}
200 changes: 200 additions & 0 deletions common/reviews/api/rush-reporter.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,118 @@

```ts

// @beta
export const BOOTSTRAP_BUFFER_MAX_BYTES: number;

// @beta
export const BOOTSTRAP_BUFFER_TRUNCATED_EXTENSION_NAME: 'rush.reporter.buffer-truncated';

// @beta
export const BOOTSTRAP_EXTERNAL_CHUNK_MAX_BYTES: number;

// @beta
export const BOOTSTRAP_HANDOFF_FILE_PREFIX: 'rush-reporter-bootstrap-';

// @beta
export const BOOTSTRAP_HANDOFF_FILE_SUFFIX: '.ndjson';

// @beta
export const BOOTSTRAP_PROTOCOL_MAJOR: number;

// @beta
export class BootstrapEventBuffer {
constructor(options: IBootstrapEventBufferOptions);
addExternalOutput(stream: 'stdout' | 'stderr', text: string): void;
emit(input: IBootstrapEventInput): string;
get failed(): boolean;
serialize(): string;
get truncation(): IBootstrapTruncation;
}

// @beta
export type BootstrapPrivacyClassification = 'public' | 'local-sensitive' | 'secret';

// @beta
export function computeEnvelopePrivacyFloor(classifications: Iterable<ReporterPrivacyClassification>): ReporterPrivacyClassification;

// @beta
export function createEngineSink(providedSink?: IReporterEventSink): IEngineSinkResolution;

// @beta
export function createRushDiagnostic(code: RushDiagnosticCodes, options?: ICreateRushDiagnosticOptions): IRushDiagnostic;

// @beta
export const DEFAULT_FLUSH_TIMEOUT_MS: number;

// @beta
export const DEFAULT_HANDOFF_RETENTION_MS: number;

// @beta
export const DEFAULT_SIGNAL_FLUSH_TIMEOUT_MS: number;

// @beta
export function deleteBootstrapHandoffFileAsync(filePath: string): Promise<void>;

// @beta
export function encodeNdjsonRecord(value: unknown, options?: INdjsonOptions): string;

// @beta
export function getPrivacyClassificationRank(classification: ReporterPrivacyClassification): number;

// @beta
export interface IBootstrapEventBufferOptions {
readonly maxBytes?: number;
readonly now?: () => string;
readonly sessionId: string;
readonly source: IBootstrapEventSource;
}

// @beta
export interface IBootstrapEventInput {
readonly payload?: unknown;
readonly privacy?: BootstrapPrivacyClassification;
readonly type: ReporterEventType;
}

// @beta
export interface IBootstrapEventSource {
// (undocumented)
readonly packageName: string;
// (undocumented)
readonly packageVersion: string;
}

// @beta
export interface IBootstrapHandoffHeader {
readonly kind: 'bootstrapHandoff';
readonly nonce: string;
}

// @beta
export interface IBootstrapHandoffWriteResult {
readonly handoffPath: string;
readonly nonce: string;
}

// @beta
export interface IBootstrapReplayResult {
readonly direct: boolean;
readonly eventCount: number;
readonly handoffPath?: string;
readonly replayed: boolean;
readonly skippedEventCount?: number;
readonly skipReason?: 'unreadable' | 'invalid-path' | 'nonce-mismatch' | 'invalid-event' | 'incompatible-protocol';
}

// @beta
export interface IBootstrapTruncation {
readonly droppedOther: number;
readonly droppedReplaceable: number;
readonly droppedRequired: number;
readonly failed: boolean;
readonly truncated: boolean;
}

// @beta
export interface IClassifiedDiagnosticValue {
readonly privacy: ReporterPrivacyClassification;
Expand All @@ -42,6 +136,18 @@ export interface ICreateRushDiagnosticOptions {
readonly source?: IRushDiagnosticSource;
}

// @beta
export interface IEarlyReporterControls {
readonly logLevel?: string;
readonly reporter?: string;
}

// @beta
export interface IEngineSinkResolution {
readonly mode: 'structured' | 'legacy-fallback';
readonly sink: IReporterEventSink;
}

// @beta
export interface IMessageEmittedPayload {
readonly privacy?: ReporterPrivacyClassification;
Expand All @@ -59,6 +165,15 @@ export class InvalidReporterHelloError extends Error {
constructor(reason: string);
}

// @beta
export interface IOldEngineOutputAdapterOptions {
readonly maxChunkBytes?: number;
readonly protocolVersion?: IReporterProtocolVersion;
readonly sessionId: string;
readonly sink: IReporterEventSink;
readonly source: IReporterEventSource;
}

// @beta
export interface IReporter {
closeAsync(): Promise<void>;
Expand All @@ -68,6 +183,15 @@ export interface IReporter {
report(event: IReporterEventEnvelope<unknown>): void;
}

// @beta
export interface IReporterCompatibilityDecision {
readonly engineRendersLegacy: boolean;
readonly legacyRenderingVisible: boolean;
readonly mode: ReporterCompatibilityMode;
readonly provideSinkToEngine: boolean;
readonly reason: string;
}

// @beta
export interface IReporterContext {
readonly destination?: string;
Expand All @@ -77,6 +201,12 @@ export interface IReporterContext {
// @beta
export type IReporterEmitEventInput<TPayload> = Omit<IReporterEventEnvelope<TPayload>, 'eventId' | 'sequence' | 'sourceSequence' | 'timestamp' | 'required'>;

// @beta
export interface IReporterEngineDescriptor {
readonly protocolMajor?: number;
readonly supportsStructuredSink: boolean;
}

// @beta
export interface IReporterEventEnvelope<TPayload = unknown> {
readonly eventId: string;
Expand Down Expand Up @@ -115,6 +245,12 @@ export interface IReporterEventSource {
readonly packageVersion: string;
}

// @beta
export interface IReporterFrontendDescriptor {
readonly hasManager: boolean;
readonly protocolMajor: number;
}

// @beta
export interface IReporterHandshakeOptions {
readonly supportedCapabilities?: readonly ReporterCapability[];
Expand Down Expand Up @@ -145,6 +281,15 @@ export interface IReporterHelloAck {
readonly rejectedRequiredFeatures: readonly string[];
}

// @beta
export interface IReporterHostOptions {
readonly env?: Record<string, string | undefined>;
readonly handoffDirectory?: string;
readonly manager?: ReporterManager;
readonly nowMs?: () => number;
readonly retentionMs?: number;
}

// @beta
export interface IReporterManagerOptions {
readonly coalesceThreshold?: number;
Expand Down Expand Up @@ -225,6 +370,9 @@ export interface IRushToolDiagnosticSource {
readonly toolName: string;
}

// @beta
export function isBootstrapHandoffFileName(fileName: string): boolean;

// @beta
export interface IScopedMessageOptions {
readonly privacy?: ReporterPrivacyClassification;
Expand All @@ -251,9 +399,21 @@ export function isReporterProtocolCompatible(consumer: IReporterProtocolVersion,
// @beta
export function isValidRushDiagnosticCode(code: string): boolean;

// @beta
export interface IWriteBootstrapHandoffOptions {
readonly directory?: string;
readonly pid?: number;
}

// @beta
export type KnownRushDiagnosticCategory = 'configuration' | 'input' | 'dependency-tool' | 'environment' | 'network-auth' | 'operation' | 'internal';

// @beta
export class LegacyFallbackSink implements IReporterEventSink {
// (undocumented)
emit(): string;
}

// @beta
export class NdjsonDecoder {
constructor(options?: INdjsonOptions);
Expand All @@ -278,15 +438,31 @@ export class NdjsonRecordTooLargeError extends Error {
// @beta
export function negotiateReporterHello(helloValue: unknown, options: IReporterHandshakeOptions): IReporterHandshakeResult;

// @beta
export class OldEngineOutputAdapter {
constructor(options: IOldEngineOutputAdapterOptions);
capture(stream: 'stdout' | 'stderr', text: string): string[];
}

// @beta
export type OneOrMoreRushDiagnosticCodeSegments<TSegments extends string = string> = string extends TSegments ? `_${Uppercase<string>}` : TSegments extends `_${infer Segments}` ? Segments extends '' ? never : TSegments extends Uppercase<TSegments> ? TSegments : never : never;

// @beta
export function parseEarlyReporterControls(argv: readonly string[], env: Record<string, string | undefined>): IEarlyReporterControls;

// @beta
export function parseReporterExtensionEventName(name: string): ReporterExtensionEventName;

// @beta
export function parseReporterHello(value: unknown): IReporterHello;

// @beta
export function readBootstrapHandoffFileAsync(filePath: string): Promise<{
header: IBootstrapHandoffHeader | undefined;
events: unknown[];
discardedRecordCount: number;
}>;

// @beta
export const REPORTER_EVENT_TYPES: readonly ["sessionStarted", "sessionCompleted", "commandStarted", "commandCompleted", "operationRegistered", "operationStatusChanged", "activityChanged", "watchCycleCompleted", "diagnosticEmitted", "messageEmitted", "externalProcessStarted", "externalOutput", "externalProcessCompleted", "artifactAvailable", "commandResult", "extension"];

Expand All @@ -305,6 +481,9 @@ export const REPORTER_PROTOCOL_VERSION: IReporterProtocolVersion;
// @beta
export type ReporterCapability = (typeof REPORTER_KNOWN_CAPABILITIES)[number] | (string & {});

// @beta
export type ReporterCompatibilityMode = 'structured' | 'new-frontend-old-engine' | 'old-frontend-new-engine' | 'legacy';

// @beta
export type ReporterEventType = (typeof REPORTER_EVENT_TYPES)[number];

Expand All @@ -313,6 +492,15 @@ export type ReporterExtensionEventName = `${string}.${string}` & {
readonly __reporterExtensionEventNameBrand: 'ReporterExtensionEventName';
};

// @beta
export class ReporterHost {
constructor(options?: IReporterHostOptions);
cleanAbandonedHandoffFilesAsync(): Promise<string[]>;
getSink(): IReporterEventSink;
get manager(): ReporterManager;
replayBootstrapHandoffAsync(): Promise<IBootstrapReplayResult>;
}

// @beta
export type ReporterJsonNull = null;

Expand Down Expand Up @@ -353,6 +541,9 @@ export class ReporterMultiplexer implements IReporter {
// @beta
export type ReporterPrivacyClassification = 'public' | 'local-sensitive' | 'secret';

// @beta
export function resolveReporterCompatibility(frontend: IReporterFrontendDescriptor, engine: IReporterEngineDescriptor): IReporterCompatibilityDecision;

// @beta
export const RUSH_DIAGNOSTIC_CODE_DEFINITIONS: readonly [{
readonly code: "RUSH_CONFIG_INVALID_JSON";
Expand Down Expand Up @@ -413,6 +604,12 @@ export const RUSH_DIAGNOSTIC_TEMPLATES: Readonly<Record<RushDiagnosticTemplateKe
// @beta
export const RUSH_INTERNAL_ERROR_CODE: 'RUSH_INTERNAL_UNEXPECTED';

// @beta
export const RUSH_REPORTER_BOOTSTRAP_HANDOFF_ENV_VAR: '_RUSH_REPORTER_BOOTSTRAP_HANDOFF';

// @beta
export const RUSH_REPORTER_BOOTSTRAP_NONCE_ENV_VAR: '_RUSH_REPORTER_BOOTSTRAP_NONCE';

// @beta
export type RushDiagnosticCategory = KnownRushDiagnosticCategory | (string & {});

Expand Down Expand Up @@ -447,4 +644,7 @@ export class RushError extends Error {
// @beta
export type RushRemediationSafety = 'safe' | 'requires-confirmation' | 'unsafe';

// @beta
export function writeBootstrapHandoffFileAsync(buffer: BootstrapEventBuffer, options?: IWriteBootstrapHandoffOptions): Promise<IBootstrapHandoffWriteResult>;

```
Loading