@outfitter/tooling 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -98,6 +98,75 @@ bunx @outfitter/tooling check-boundary-invocations
98
98
  When this fails, replace direct source execution with canonical command surfaces
99
99
  (`outfitter repo ...` in monorepo scripts, or package bins for standalone use).
100
100
 
101
+ ### `tooling check-bunup-registry`
102
+
103
+ Validate that packages using `bunup --filter` are registered in `bunup.config.ts`.
104
+
105
+ ```bash
106
+ bunx @outfitter/tooling check-bunup-registry
107
+ ```
108
+
109
+ ### `tooling check-changeset`
110
+
111
+ Validate that PRs touching package source include a changeset.
112
+
113
+ ```bash
114
+ bunx @outfitter/tooling check-changeset
115
+
116
+ # Skip the check (e.g. for non-package changes)
117
+ bunx @outfitter/tooling check-changeset --skip
118
+ ```
119
+
120
+ ### `tooling check-exports`
121
+
122
+ Validate that `package.json` exports match source entry points.
123
+
124
+ ```bash
125
+ bunx @outfitter/tooling check-exports
126
+
127
+ # Machine-readable output
128
+ bunx @outfitter/tooling check-exports --json
129
+ ```
130
+
131
+ ### `tooling check-tsdoc`
132
+
133
+ Check TSDoc coverage on exported declarations.
134
+
135
+ ```bash
136
+ bunx @outfitter/tooling check-tsdoc
137
+
138
+ # Check specific packages
139
+ bunx @outfitter/tooling check-tsdoc packages/cli packages/contracts
140
+
141
+ # Strict mode with coverage threshold
142
+ bunx @outfitter/tooling check-tsdoc --strict --min-coverage 80
143
+
144
+ # JSON output
145
+ bunx @outfitter/tooling check-tsdoc --json
146
+ ```
147
+
148
+ ### `tooling check-clean-tree`
149
+
150
+ Assert the working tree is clean (no modified or untracked files).
151
+
152
+ ```bash
153
+ bunx @outfitter/tooling check-clean-tree
154
+
155
+ # Check specific paths only
156
+ bunx @outfitter/tooling check-clean-tree --paths packages/cli packages/contracts
157
+ ```
158
+
159
+ ### `tooling check-readme-imports`
160
+
161
+ Validate that README import examples match package exports.
162
+
163
+ ```bash
164
+ bunx @outfitter/tooling check-readme-imports
165
+
166
+ # Machine-readable output
167
+ bunx @outfitter/tooling check-readme-imports --json
168
+ ```
169
+
101
170
  ## Configuration Presets
102
171
 
103
172
  ### Biome
@@ -188,7 +257,6 @@ bun run apps/outfitter/src/cli.ts repo check boundary-invocations --cwd .
188
257
 
189
258
  - [@outfitter/contracts](../contracts/README.md) — Result types and error patterns
190
259
  - [@outfitter/cli](../cli/README.md) — CLI framework
191
- - [@outfitter/kit](../kit/README.md) — Version coordination
192
260
 
193
261
  ## License
194
262
 
package/biome.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "https://biomejs.dev/schemas/2.3.12/schema.json",
2
+ "$schema": "https://biomejs.dev/schemas/2.4.4/schema.json",
3
3
  "root": false,
4
4
  "javascript": {
5
5
  "globals": ["Bun"]
@@ -1,17 +1,12 @@
1
- /**
2
- * Check-changeset command — validates PRs touching package source include a changeset.
3
- *
4
- * Pure core functions for detecting changed packages and verifying changeset
5
- * presence. The CLI runner in {@link runCheckChangeset} handles git discovery
6
- * and output.
7
- *
8
- * @packageDocumentation
9
- */
10
1
  /** Result of checking whether changesets are required */
11
2
  interface ChangesetCheckResult {
12
3
  readonly ok: boolean;
13
4
  readonly missingFor: string[];
14
5
  }
6
+ interface ChangesetIgnoredReference {
7
+ readonly file: string;
8
+ readonly packages: string[];
9
+ }
15
10
  /**
16
11
  * Extract unique package names from changed file paths.
17
12
  *
@@ -44,6 +39,13 @@ declare function getChangedChangesetFiles(files: string[]): string[];
44
39
  * @param changesetFiles - Changeset filenames found in `.changeset/`
45
40
  */
46
41
  declare function checkChangesetRequired(changedPackages: string[], changesetFiles: string[]): ChangesetCheckResult;
42
+ declare function parseIgnoredPackagesFromChangesetConfig(jsonContent: string): string[];
43
+ declare function parseChangesetFrontmatterPackageNames(markdownContent: string): string[];
44
+ declare function findIgnoredPackageReferences(input: {
45
+ readonly changesetFiles: readonly string[];
46
+ readonly ignoredPackages: readonly string[];
47
+ readonly readChangesetFile: (filename: string) => string;
48
+ }): ChangesetIgnoredReference[];
47
49
  interface CheckChangesetOptions {
48
50
  readonly skip?: boolean;
49
51
  }
@@ -61,4 +63,4 @@ interface CheckChangesetOptions {
61
63
  * - Git diff fails (local dev without origin)
62
64
  */
63
65
  declare function runCheckChangeset(options?: CheckChangesetOptions): Promise<void>;
64
- export { runCheckChangeset, getChangedPackagePaths, getChangedChangesetFiles, checkChangesetRequired, CheckChangesetOptions, ChangesetCheckResult };
66
+ export { runCheckChangeset, parseIgnoredPackagesFromChangesetConfig, parseChangesetFrontmatterPackageNames, getChangedPackagePaths, getChangedChangesetFiles, findIgnoredPackageReferences, checkChangesetRequired, CheckChangesetOptions, ChangesetIgnoredReference, ChangesetCheckResult };
@@ -1,14 +1,20 @@
1
1
  // @bun
2
2
  import {
3
3
  checkChangesetRequired,
4
+ findIgnoredPackageReferences,
4
5
  getChangedChangesetFiles,
5
6
  getChangedPackagePaths,
7
+ parseChangesetFrontmatterPackageNames,
8
+ parseIgnoredPackagesFromChangesetConfig,
6
9
  runCheckChangeset
7
- } from "../shared/@outfitter/tooling-3w8vr2w3.js";
10
+ } from "../shared/@outfitter/tooling-cj5vsa9k.js";
8
11
  import"../shared/@outfitter/tooling-dvwh9qve.js";
9
12
  export {
10
13
  runCheckChangeset,
14
+ parseIgnoredPackagesFromChangesetConfig,
15
+ parseChangesetFrontmatterPackageNames,
11
16
  getChangedPackagePaths,
12
17
  getChangedChangesetFiles,
18
+ findIgnoredPackageReferences,
13
19
  checkChangesetRequired
14
20
  };
@@ -1,2 +1,2 @@
1
- import { CheckExportsOptions, CheckResult, CompareInput, ExportDrift, ExportMap, PackageResult, compareExports, entryToSubpath, resolveJsonMode, runCheckExports } from "../shared/@outfitter/tooling-wesswf21";
1
+ import { CheckExportsOptions, CheckResult, CompareInput, ExportDrift, ExportMap, PackageResult, compareExports, entryToSubpath, resolveJsonMode, runCheckExports } from "../shared/@outfitter/tooling-wesswf21.js";
2
2
  export { runCheckExports, resolveJsonMode, entryToSubpath, compareExports, PackageResult, ExportMap, ExportDrift, CompareInput, CheckResult, CheckExportsOptions };
@@ -1,4 +1,4 @@
1
- import { ExportMap } from "../shared/@outfitter/tooling-wesswf21";
1
+ import { ExportMap } from "../shared/@outfitter/tooling-wesswf21.js";
2
2
  /** An import example extracted from a markdown code block */
3
3
  interface ImportExample {
4
4
  /** Package name, e.g. "@outfitter/cli" */
@@ -0,0 +1,2 @@
1
+ import { CheckTsDocOptions, CoverageLevel, CoverageSummary, DeclarationCoverage, PackageCoverage, TsDocCheckResult, analyzeCheckTsdoc, analyzeSourceFile, calculateCoverage, classifyDeclaration, coverageLevelSchema, coverageSummarySchema, declarationCoverageSchema, getDeclarationKind, getDeclarationName, isExportedDeclaration, packageCoverageSchema, printCheckTsdocHuman, resolveJsonMode, runCheckTsdoc, tsDocCheckResultSchema } from "../shared/@outfitter/tooling-njw4z34x.js";
2
+ export { tsDocCheckResultSchema, runCheckTsdoc, resolveJsonMode, printCheckTsdocHuman, packageCoverageSchema, isExportedDeclaration, getDeclarationName, getDeclarationKind, declarationCoverageSchema, coverageSummarySchema, coverageLevelSchema, classifyDeclaration, calculateCoverage, analyzeSourceFile, analyzeCheckTsdoc, TsDocCheckResult, PackageCoverage, DeclarationCoverage, CoverageSummary, CoverageLevel, CheckTsDocOptions };
@@ -0,0 +1,36 @@
1
+ // @bun
2
+ import {
3
+ analyzeCheckTsdoc,
4
+ analyzeSourceFile,
5
+ calculateCoverage,
6
+ classifyDeclaration,
7
+ coverageLevelSchema,
8
+ coverageSummarySchema,
9
+ declarationCoverageSchema,
10
+ getDeclarationKind,
11
+ getDeclarationName,
12
+ isExportedDeclaration,
13
+ packageCoverageSchema,
14
+ printCheckTsdocHuman,
15
+ resolveJsonMode,
16
+ runCheckTsdoc,
17
+ tsDocCheckResultSchema
18
+ } from "../shared/@outfitter/tooling-qk5xgmxr.js";
19
+ import"../shared/@outfitter/tooling-dvwh9qve.js";
20
+ export {
21
+ tsDocCheckResultSchema,
22
+ runCheckTsdoc,
23
+ resolveJsonMode,
24
+ printCheckTsdocHuman,
25
+ packageCoverageSchema,
26
+ isExportedDeclaration,
27
+ getDeclarationName,
28
+ getDeclarationKind,
29
+ declarationCoverageSchema,
30
+ coverageSummarySchema,
31
+ coverageLevelSchema,
32
+ classifyDeclaration,
33
+ calculateCoverage,
34
+ analyzeSourceFile,
35
+ analyzeCheckTsdoc
36
+ };
package/dist/cli/index.js CHANGED
@@ -1,7 +1,10 @@
1
1
  #!/usr/bin/env bun
2
2
  import {
3
- VERSION
4
- } from "../shared/chunk-8aenrm6f.js";
3
+ VERSION,
4
+ analyzeSourceFile,
5
+ calculateCoverage,
6
+ runCheckTsdoc
7
+ } from "../shared/chunk-cmde0fwx.js";
5
8
  import {
6
9
  __require
7
10
  } from "../shared/chunk-3s189drz.js";
@@ -205,6 +208,8 @@ Add the missing entries to ${COLORS.blue}bunup.config.ts${COLORS.reset} defineWo
205
208
  }
206
209
 
207
210
  // src/cli/check-changeset.ts
211
+ import { existsSync, readFileSync } from "node:fs";
212
+ import { join } from "node:path";
208
213
  function getChangedPackagePaths(files) {
209
214
  const packageNames = new Set;
210
215
  const pattern = /^packages\/([^/]+)\/src\//;
@@ -236,6 +241,73 @@ function checkChangesetRequired(changedPackages, changesetFiles) {
236
241
  }
237
242
  return { ok: false, missingFor: changedPackages };
238
243
  }
244
+ function parseIgnoredPackagesFromChangesetConfig(jsonContent) {
245
+ try {
246
+ const parsed = JSON.parse(jsonContent);
247
+ if (!Array.isArray(parsed.ignore)) {
248
+ return [];
249
+ }
250
+ return parsed.ignore.filter((entry) => typeof entry === "string");
251
+ } catch {
252
+ return [];
253
+ }
254
+ }
255
+ function parseChangesetFrontmatterPackageNames(markdownContent) {
256
+ const frontmatterMatch = /^---\r?\n([\s\S]*?)\r?\n---/.exec(markdownContent);
257
+ if (!frontmatterMatch?.[1]) {
258
+ return [];
259
+ }
260
+ const packages = new Set;
261
+ for (const line of frontmatterMatch[1].split(/\r?\n/)) {
262
+ const trimmed = line.trim();
263
+ const match = /^(["']?)(@[^"':\s]+\/[^"':\s]+)\1\s*:/.exec(trimmed);
264
+ if (match?.[2]) {
265
+ packages.add(match[2]);
266
+ }
267
+ }
268
+ return [...packages].sort();
269
+ }
270
+ function findIgnoredPackageReferences(input) {
271
+ if (input.ignoredPackages.length === 0 || input.changesetFiles.length === 0) {
272
+ return [];
273
+ }
274
+ const ignored = new Set(input.ignoredPackages);
275
+ const results = [];
276
+ for (const file of input.changesetFiles) {
277
+ const content = input.readChangesetFile(file);
278
+ const referencedPackages = parseChangesetFrontmatterPackageNames(content);
279
+ const invalidReferences = referencedPackages.filter((pkg) => ignored.has(pkg));
280
+ if (invalidReferences.length > 0) {
281
+ results.push({ file, packages: invalidReferences.sort() });
282
+ }
283
+ }
284
+ return results.sort((a, b) => a.file.localeCompare(b.file));
285
+ }
286
+ function loadIgnoredPackages(cwd) {
287
+ const configPath = join(cwd, ".changeset", "config.json");
288
+ if (!existsSync(configPath)) {
289
+ return [];
290
+ }
291
+ try {
292
+ return parseIgnoredPackagesFromChangesetConfig(readFileSync(configPath, "utf-8"));
293
+ } catch {
294
+ return [];
295
+ }
296
+ }
297
+ function getIgnoredReferencesForChangedChangesets(cwd, changesetFiles) {
298
+ const ignoredPackages = loadIgnoredPackages(cwd);
299
+ return findIgnoredPackageReferences({
300
+ changesetFiles,
301
+ ignoredPackages,
302
+ readChangesetFile: (filename) => {
303
+ try {
304
+ return readFileSync(join(cwd, ".changeset", filename), "utf-8");
305
+ } catch {
306
+ return "";
307
+ }
308
+ }
309
+ });
310
+ }
239
311
  var COLORS2 = {
240
312
  reset: "\x1B[0m",
241
313
  red: "\x1B[31m",
@@ -275,25 +347,46 @@ async function runCheckChangeset(options = {}) {
275
347
  }
276
348
  const changesetFiles = getChangedChangesetFiles(changedFiles);
277
349
  const check = checkChangesetRequired(changedPackages, changesetFiles);
278
- if (check.ok) {
279
- process.stdout.write(`${COLORS2.green}Changeset found for ${changedPackages.length} changed package(s).${COLORS2.reset}
350
+ if (!check.ok) {
351
+ process.stderr.write(`${COLORS2.red}Missing changeset!${COLORS2.reset}
352
+
280
353
  `);
281
- process.exit(0);
354
+ process.stderr.write(`The following packages have source changes but no changeset:
355
+
356
+ `);
357
+ for (const pkg of check.missingFor) {
358
+ process.stderr.write(` ${COLORS2.yellow}@outfitter/${pkg}${COLORS2.reset}
359
+ `);
360
+ }
361
+ process.stderr.write(`
362
+ Run ${COLORS2.blue}bun run changeset${COLORS2.reset} to add a changeset, ` + `or add the ${COLORS2.blue}no-changeset${COLORS2.reset} label to skip.
363
+ `);
364
+ process.exit(1);
282
365
  }
283
- process.stderr.write(`${COLORS2.red}Missing changeset!${COLORS2.reset}
366
+ const ignoredReferences = getIgnoredReferencesForChangedChangesets(cwd, changesetFiles);
367
+ if (ignoredReferences.length > 0) {
368
+ process.stderr.write(`${COLORS2.red}Invalid changeset package reference(s).${COLORS2.reset}
284
369
 
285
370
  `);
286
- process.stderr.write(`The following packages have source changes but no changeset:
371
+ process.stderr.write(`Changesets must not reference packages listed in .changeset/config.json ignore:
287
372
 
288
373
  `);
289
- for (const pkg of check.missingFor) {
290
- process.stderr.write(` ${COLORS2.yellow}@outfitter/${pkg}${COLORS2.reset}
374
+ for (const reference of ignoredReferences) {
375
+ process.stderr.write(` ${COLORS2.yellow}${reference.file}${COLORS2.reset}
376
+ `);
377
+ for (const pkg of reference.packages) {
378
+ process.stderr.write(` - ${pkg}
291
379
  `);
380
+ }
381
+ }
382
+ process.stderr.write(`
383
+ Update the affected changeset files to remove ignored packages before merging.
384
+ `);
385
+ process.exit(1);
292
386
  }
293
- process.stderr.write(`
294
- Run ${COLORS2.blue}bun run changeset${COLORS2.reset} to add a changeset, ` + `or add the ${COLORS2.blue}no-changeset${COLORS2.reset} label to skip.
387
+ process.stdout.write(`${COLORS2.green}Changeset found for ${changedPackages.length} changed package(s).${COLORS2.reset}
295
388
  `);
296
- process.exit(1);
389
+ process.exit(0);
297
390
  }
298
391
 
299
392
  // src/cli/check-clean-tree.ts
@@ -674,8 +767,9 @@ async function runInit(cwd = process.cwd()) {
674
767
  }
675
768
 
676
769
  // src/cli/pre-push.ts
677
- import { existsSync, readFileSync } from "node:fs";
678
- import { join } from "node:path";
770
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
771
+ import { join as join2, resolve as resolve4 } from "node:path";
772
+ import ts from "typescript";
679
773
  var COLORS5 = {
680
774
  reset: "\x1B[0m",
681
775
  red: "\x1B[31m",
@@ -712,6 +806,9 @@ function isRedPhaseBranch(branch) {
712
806
  function isScaffoldBranch(branch) {
713
807
  return branch.endsWith("-scaffold") || branch.endsWith("/scaffold") || branch.endsWith("_scaffold");
714
808
  }
809
+ function isReleaseBranch(branch) {
810
+ return branch.startsWith("changeset-release/");
811
+ }
715
812
  var TEST_PATH_PATTERNS = [
716
813
  /(^|\/)__tests__\//,
717
814
  /(^|\/)__snapshots__\//,
@@ -731,6 +828,32 @@ function areFilesTestOnly(paths) {
731
828
  function canBypassRedPhaseByChangedFiles(changedFiles) {
732
829
  return changedFiles.deterministic && areFilesTestOnly(changedFiles.files);
733
830
  }
831
+ function hasPackageSourceChanges(changedFiles) {
832
+ const packageSrcPattern = /^packages\/[^/]+\/src\//;
833
+ return changedFiles.files.some((f) => packageSrcPattern.test(f));
834
+ }
835
+ async function printTsdocSummary() {
836
+ const glob = new Bun.Glob("packages/*/src/index.ts");
837
+ const cwd = process.cwd();
838
+ const allDeclarations = [];
839
+ for (const entry of glob.scanSync({ cwd })) {
840
+ const filePath = resolve4(cwd, entry);
841
+ const content = await Bun.file(filePath).text();
842
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
843
+ allDeclarations.push(...analyzeSourceFile(sourceFile));
844
+ }
845
+ if (allDeclarations.length === 0)
846
+ return;
847
+ const coverage = calculateCoverage(allDeclarations);
848
+ const parts = [];
849
+ if (coverage.documented > 0)
850
+ parts.push(`${coverage.documented} documented`);
851
+ if (coverage.partial > 0)
852
+ parts.push(`${coverage.partial} partial`);
853
+ if (coverage.undocumented > 0)
854
+ parts.push(`${coverage.undocumented} undocumented`);
855
+ log(`${COLORS5.blue}TSDoc${COLORS5.reset}: ${coverage.percentage}% coverage (${parts.join(", ")} of ${coverage.total} total)`);
856
+ }
734
857
  function resolveBaseRef() {
735
858
  const candidates = [
736
859
  "origin/main",
@@ -868,12 +991,12 @@ function createVerificationPlan(scripts) {
868
991
  };
869
992
  }
870
993
  function readPackageScripts(cwd = process.cwd()) {
871
- const packageJsonPath = join(cwd, "package.json");
872
- if (!existsSync(packageJsonPath)) {
994
+ const packageJsonPath = join2(cwd, "package.json");
995
+ if (!existsSync2(packageJsonPath)) {
873
996
  return {};
874
997
  }
875
998
  try {
876
- const parsed = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
999
+ const parsed = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
877
1000
  const scripts = parsed.scripts ?? {};
878
1001
  const normalized = {};
879
1002
  for (const [name, value] of Object.entries(scripts)) {
@@ -898,6 +1021,11 @@ async function runPrePush(options = {}) {
898
1021
  log(`${COLORS5.blue}Pre-push verify${COLORS5.reset} (TDD-aware)`);
899
1022
  log("");
900
1023
  const branch = getCurrentBranch();
1024
+ if (isReleaseBranch(branch)) {
1025
+ log(`${COLORS5.yellow}Release branch detected${COLORS5.reset}: ${COLORS5.blue}${branch}${COLORS5.reset}`);
1026
+ log(`${COLORS5.yellow}Skipping strict verification${COLORS5.reset} for automated changeset release push`);
1027
+ process.exit(0);
1028
+ }
901
1029
  if (isRedPhaseBranch(branch)) {
902
1030
  if (maybeSkipForRedPhase("branch", branch)) {
903
1031
  process.exit(0);
@@ -943,14 +1071,20 @@ async function runPrePush(options = {}) {
943
1071
  log(" - feature_tests");
944
1072
  process.exit(1);
945
1073
  }
1074
+ const changedFiles = getChangedFilesForPush();
1075
+ if (hasPackageSourceChanges(changedFiles)) {
1076
+ try {
1077
+ await printTsdocSummary();
1078
+ } catch {}
1079
+ }
946
1080
  log("");
947
1081
  log(`${COLORS5.green}Strict verification passed${COLORS5.reset}`);
948
1082
  process.exit(0);
949
1083
  }
950
1084
 
951
1085
  // src/cli/upgrade-bun.ts
952
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
953
- import { join as join2 } from "node:path";
1086
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync } from "node:fs";
1087
+ import { join as join3 } from "node:path";
954
1088
  var COLORS6 = {
955
1089
  reset: "\x1B[0m",
956
1090
  red: "\x1B[31m",
@@ -988,13 +1122,13 @@ function findPackageJsonFiles(dir) {
988
1122
  const glob = new Bun.Glob("**/package.json");
989
1123
  for (const path of glob.scanSync({ cwd: dir })) {
990
1124
  if (!path.includes("node_modules")) {
991
- results.push(join2(dir, path));
1125
+ results.push(join3(dir, path));
992
1126
  }
993
1127
  }
994
1128
  return results;
995
1129
  }
996
1130
  function updateEnginesBun(filePath, version) {
997
- const content = readFileSync2(filePath, "utf-8");
1131
+ const content = readFileSync3(filePath, "utf-8");
998
1132
  const pattern = /"bun":\s*">=[\d.]+"/;
999
1133
  if (!pattern.test(content)) {
1000
1134
  return false;
@@ -1007,7 +1141,7 @@ function updateEnginesBun(filePath, version) {
1007
1141
  return false;
1008
1142
  }
1009
1143
  function updateTypesBun(filePath, version) {
1010
- const content = readFileSync2(filePath, "utf-8");
1144
+ const content = readFileSync3(filePath, "utf-8");
1011
1145
  const pattern = /"@types\/bun":\s*"\^[\d.]+"/;
1012
1146
  if (!pattern.test(content)) {
1013
1147
  return false;
@@ -1021,14 +1155,14 @@ function updateTypesBun(filePath, version) {
1021
1155
  }
1022
1156
  async function runUpgradeBun(targetVersion, options = {}) {
1023
1157
  const cwd = process.cwd();
1024
- const bunVersionFile = join2(cwd, ".bun-version");
1158
+ const bunVersionFile = join3(cwd, ".bun-version");
1025
1159
  let version = targetVersion;
1026
1160
  if (!version) {
1027
1161
  info("Fetching latest Bun version...");
1028
1162
  version = await fetchLatestVersion();
1029
1163
  log2(`Latest version: ${version}`);
1030
1164
  }
1031
- const currentVersion = existsSync2(bunVersionFile) ? readFileSync2(bunVersionFile, "utf-8").trim() : "unknown";
1165
+ const currentVersion = existsSync3(bunVersionFile) ? readFileSync3(bunVersionFile, "utf-8").trim() : "unknown";
1032
1166
  log2(`Current version: ${currentVersion}`);
1033
1167
  if (currentVersion === version) {
1034
1168
  success(`Already on version ${version}`);
@@ -1129,6 +1263,14 @@ register(new Command("check-changeset").description("Validate PRs touching packa
1129
1263
  register(new Command("check-exports").description("Validate package.json exports match source entry points").option("--json", "Output results as JSON").action(async (options) => {
1130
1264
  await runCheckExports(options);
1131
1265
  }));
1266
+ register(new Command("check-tsdoc").description("Check TSDoc coverage on exported declarations").argument("[paths...]", "Specific package paths to check").option("--strict", "Exit non-zero when coverage is below threshold").option("--json", "Output results as JSON").option("--min-coverage <percentage>", "Minimum coverage percentage", "0").action(async (paths, options) => {
1267
+ await runCheckTsdoc({
1268
+ paths: paths.length > 0 ? paths : undefined,
1269
+ strict: options.strict,
1270
+ json: options.json,
1271
+ minCoverage: options.minCoverage ? Number.parseInt(options.minCoverage, 10) : undefined
1272
+ });
1273
+ }));
1132
1274
  register(new Command("check-clean-tree").description("Assert working tree is clean (no modified or untracked files)").option("--paths <paths...>", "Limit check to specific paths").action(async (options) => {
1133
1275
  await runCheckCleanTree(options);
1134
1276
  }));
@@ -6,6 +6,7 @@ declare function isRedPhaseBranch(branch: string): boolean;
6
6
  * Check if branch is a scaffold branch
7
7
  */
8
8
  declare function isScaffoldBranch(branch: string): boolean;
9
+ declare function isReleaseBranch(branch: string): boolean;
9
10
  declare function isTestOnlyPath(path: string): boolean;
10
11
  declare function areFilesTestOnly(paths: readonly string[]): boolean;
11
12
  interface PushChangedFiles {
@@ -14,6 +15,12 @@ interface PushChangedFiles {
14
15
  readonly source: "upstream" | "baseRef" | "undetermined";
15
16
  }
16
17
  declare function canBypassRedPhaseByChangedFiles(changedFiles: PushChangedFiles): boolean;
18
+ /**
19
+ * Check whether any changed files are package source files.
20
+ *
21
+ * Matches files under "packages/PKGNAME/src/" (any depth).
22
+ */
23
+ declare function hasPackageSourceChanges(changedFiles: PushChangedFiles): boolean;
17
24
  type ScriptMap = Readonly<Record<string, string | undefined>>;
18
25
  type VerificationPlan = {
19
26
  readonly ok: true;
@@ -38,4 +45,4 @@ interface PrePushOptions {
38
45
  * Main pre-push command
39
46
  */
40
47
  declare function runPrePush(options?: PrePushOptions): Promise<void>;
41
- export { runPrePush, isTestOnlyPath, isScaffoldBranch, isRedPhaseBranch, createVerificationPlan, canBypassRedPhaseByChangedFiles, areFilesTestOnly, VerificationPlan, PushChangedFiles, PrePushOptions };
48
+ export { runPrePush, isTestOnlyPath, isScaffoldBranch, isReleaseBranch, isRedPhaseBranch, hasPackageSourceChanges, createVerificationPlan, canBypassRedPhaseByChangedFiles, areFilesTestOnly, VerificationPlan, PushChangedFiles, PrePushOptions };
@@ -3,17 +3,22 @@ import {
3
3
  areFilesTestOnly,
4
4
  canBypassRedPhaseByChangedFiles,
5
5
  createVerificationPlan,
6
+ hasPackageSourceChanges,
6
7
  isRedPhaseBranch,
8
+ isReleaseBranch,
7
9
  isScaffoldBranch,
8
10
  isTestOnlyPath,
9
11
  runPrePush
10
- } from "../shared/@outfitter/tooling-8sd32ts6.js";
12
+ } from "../shared/@outfitter/tooling-2n2dpsaa.js";
13
+ import"../shared/@outfitter/tooling-qk5xgmxr.js";
11
14
  import"../shared/@outfitter/tooling-dvwh9qve.js";
12
15
  export {
13
16
  runPrePush,
14
17
  isTestOnlyPath,
15
18
  isScaffoldBranch,
19
+ isReleaseBranch,
16
20
  isRedPhaseBranch,
21
+ hasPackageSourceChanges,
17
22
  createVerificationPlan,
18
23
  canBypassRedPhaseByChangedFiles,
19
24
  areFilesTestOnly