vouchington-tooling 0.43.0 → 0.44.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.
Files changed (64) hide show
  1. package/README.md +45 -1
  2. package/dist/cli/commands/fresh-base.d.mts +6 -0
  3. package/dist/cli/commands/fresh-base.mjs +14 -0
  4. package/dist/cli/index.mjs +3 -0
  5. package/dist/cli/parse-fresh-base.d.mts +2 -0
  6. package/dist/cli/parse-fresh-base.mjs +15 -0
  7. package/dist/cli/parse.d.mts +5 -0
  8. package/dist/cli/parse.mjs +3 -0
  9. package/dist/cli/usage.d.mts +1 -1
  10. package/dist/cli/usage.mjs +2 -0
  11. package/dist/fresh-base/classify.d.mts +21 -0
  12. package/dist/fresh-base/classify.mjs +29 -0
  13. package/dist/fresh-base/index.d.mts +4 -0
  14. package/dist/fresh-base/index.mjs +2 -0
  15. package/dist/fresh-base/run.d.mts +25 -0
  16. package/dist/fresh-base/run.mjs +66 -0
  17. package/dist/gh-cli/closing-refs.d.mts +24 -0
  18. package/dist/gh-cli/closing-refs.mjs +37 -0
  19. package/dist/gh-cli/index.d.mts +3 -0
  20. package/dist/gh-cli/index.mjs +2 -0
  21. package/dist/gh-cli/non-closing-refs.d.mts +11 -0
  22. package/dist/gh-cli/non-closing-refs.mjs +24 -0
  23. package/dist/index.d.mts +6 -0
  24. package/dist/index.mjs +3 -0
  25. package/dist/pull-request-diff/diff-size.d.mts +29 -0
  26. package/dist/pull-request-diff/diff-size.mjs +35 -0
  27. package/dist/pull-request-diff/index.d.mts +2 -0
  28. package/dist/pull-request-diff/index.mjs +1 -0
  29. package/dist/retrospective-distill/args.d.mts +13 -0
  30. package/dist/retrospective-distill/args.mjs +27 -0
  31. package/dist/retrospective-distill/checkpoint-entry.d.mts +2 -0
  32. package/dist/retrospective-distill/checkpoint-entry.mjs +11 -0
  33. package/dist/retrospective-distill/fixtures.test-helpers.d.mts +3 -0
  34. package/dist/retrospective-distill/fixtures.test-helpers.mjs +21 -0
  35. package/dist/retrospective-distill/flag-args.d.mts +5 -0
  36. package/dist/retrospective-distill/flag-args.mjs +31 -0
  37. package/dist/retrospective-distill/index.d.mts +10 -0
  38. package/dist/retrospective-distill/index.mjs +6 -0
  39. package/dist/retrospective-distill/partition-records.d.mts +6 -0
  40. package/dist/retrospective-distill/partition-records.mjs +49 -0
  41. package/dist/retrospective-distill/retrospective-entry.d.mts +3 -0
  42. package/dist/retrospective-distill/retrospective-entry.mjs +4 -0
  43. package/dist/retrospective-distill/run.d.mts +5 -0
  44. package/dist/retrospective-distill/run.mjs +28 -0
  45. package/dist/retrospective-distill/session-types.d.mts +17 -0
  46. package/dist/retrospective-distill/session-types.mjs +1 -0
  47. package/dist/retrospective-distill/shape.d.mts +29 -0
  48. package/dist/retrospective-distill/shape.mjs +111 -0
  49. package/docs/mcp-server.md +1 -1
  50. package/package.json +18 -3
  51. package/skills/agent-workflow/SKILL.md +20 -0
  52. package/skills/agent-workflow/references/four-refs.md +74 -0
  53. package/skills/blackboard/SKILL.md +21 -1
  54. package/skills/github-issue/SKILL.md +6 -0
  55. package/skills/manifest.json +11 -5
  56. package/skills/postgres-node-performance-tuning/SKILL.md +2 -0
  57. package/skills/postgres-node-performance-tuning/references/performance-patterns.md +76 -0
  58. package/skills/postgres-node-performance-tuning/references/reading-plans.md +111 -0
  59. package/skills/postgres-schema-design/references/relations.md +18 -0
  60. package/skills/retrospective-distill/SKILL.md +67 -4
  61. package/skills/revisit-followups/SKILL.md +17 -0
  62. package/skills/stacked-prs/SKILL.md +47 -16
  63. package/skills/stacked-prs/references/drain.md +168 -0
  64. package/skills/stacked-prs/references/gh-stack.md +143 -0
package/README.md CHANGED
@@ -138,7 +138,13 @@ one, prints `download failed artifact=<name> exit=<n>`, exits non-zero (a downlo
138
138
  reported as 1 so it cannot be mistaken for absence), and writes no `availability` output.
139
139
 
140
140
  `require-up-to-date` fetches the requested remote branch and fails unless its fetched tip is an
141
- ancestor of `HEAD`. `gitleaks-directory-scan` builds and scans isolated staged-index and current
141
+ ancestor of `HEAD`. `fresh-base` refreshes `<remote>/<branch>` (default `origin/main`, 8 s fetch timeout, `--skip-fetch`
142
+ to compare the cached ref) and prints a JSON report classifying the worktree as `up-to-date`,
143
+ `ahead`, `behind`, `diverged`, `dirty` (matches the base but has uncommitted changes),
144
+ `fetch-failed` (a failed refresh outranks every other status), or `unknown`. It prints nothing
145
+ outside a Git repository. Unlike `require-up-to-date` it never fails on staleness; callers decide
146
+ what to do with the status.
147
+ `gitleaks-directory-scan` builds and scans isolated staged-index and current
142
148
  nonignored-working-tree mirrors with an explicit config; `--directory` selects the repository root.
143
149
  `ast-grep-examples` runs native `ast-grep test`, then validates each scoped rule's `files:` and
144
150
  `ignores:` examples with project `languageGlobs` replay from its root `--config`.
@@ -292,6 +298,7 @@ import {
292
298
  } from 'vouchington-tooling/dependency-license-policy'
293
299
  import { checkGhaWorkspacePolicy } from 'vouchington-tooling/gha-workspace-policy'
294
300
  import { requireUpToDate } from 'vouchington-tooling/require-up-to-date'
301
+ import { checkFreshBase, classifyFreshBase } from 'vouchington-tooling/fresh-base'
295
302
  import { runGitleaksDirectoryScan } from 'vouchington-tooling/gitleaks-directory-scan'
296
303
  import { runAstGrepExamples } from 'vouchington-tooling/ast-grep-examples'
297
304
  import { astGrepPackPaths } from 'vouchington-tooling/ast-grep-pack'
@@ -331,6 +338,30 @@ await processDiffCommand({ executable: 'git', args: ['diff', 'origin/main...HEAD
331
338
  // Use changedFileCount only after processDiffCommand resolves.
332
339
  ```
333
340
 
341
+ `vouchington-tooling/gh-cli` also parses pull request body references.
342
+ `parseClosingIssueReferences` finds GitHub closing keywords (`close`/`closes`/`closed`,
343
+ `fix`/`fixes`/`fixed`, `resolve`/`resolves`/`resolved`) followed by `#N`, `owner/repo#N` or an issue
344
+ URL, deduplicated by key (`#N` or lowercased `owner/repo#N`, see `formatReferenceKey`).
345
+ `parseNonClosingRefs` collects `Ref`, `Refs` and `Part of` keys from text the caller has already
346
+ scoped, such as a "Related issues" section.
347
+
348
+ `vouchington-tooling/pull-request-diff` counts added and deleted lines in unified-diff text
349
+ (`countDiffLineChanges`, excluding `+++`/`---` headers) and compares them with caller-supplied
350
+ `LargeDiffThresholds` through `exceedsLargeDiffThreshold`. `DEFAULT_LARGE_DIFF_THRESHOLDS`
351
+ (5,000 added, 20,000 deleted) is only an example default.
352
+
353
+ ```ts
354
+ import {
355
+ countDiffLineChanges,
356
+ exceedsLargeDiffThreshold,
357
+ } from 'vouchington-tooling/pull-request-diff'
358
+
359
+ exceedsLargeDiffThreshold(countDiffLineChanges(diffText), {
360
+ addedLines: 5000,
361
+ deletedLines: 20_000,
362
+ })
363
+ ```
364
+
334
365
  This breaking minor removes `getDiffAgainstBase`; migrate callers to `processDiffCommand` and pass
335
366
  the command they need to run.
336
367
 
@@ -700,6 +731,19 @@ reexports, and literal dynamic/require loads. Missing or incomplete facts for a
700
731
  throw; external specifiers resolve to `null`. The default file inventory limit is 10,000
701
732
  files and can be reduced with `maxFiles`.
702
733
 
734
+ ## Retrospective distill classifier
735
+
736
+ `vouchington-tooling/retrospective-distill` exports `classifySession(session, entries, cutoffs)`,
737
+ which assigns a session one of `retrospective`, `entry-type-unresolved`, `checkpoint-only`,
738
+ `journal-only`, `zero-entry-child`, or `zero-entry-root` and decides distillation eligibility
739
+ against caller-supplied `retroCutoff` and `sessionCutoff` timestamps. It deduplicates replayed
740
+ source events, quarantines unknown entry types, invalid feedback envelopes, and conflicting source
741
+ events, and never marks an `entry-type-unresolved` session eligible. `readPartitionRecords(path)`
742
+ reads a local agent-blackboard partition JSONL file; `parseDistillClassifyArgs` and
743
+ `runDistillClassify` wrap both behind `<partition.jsonl> --retro-cutoff <iso> --session-cutoff
744
+ <iso>` and print one tab-separated line per session. The code is the source of truth for the
745
+ taxonomy; the retrospective-distill skill is its human-facing description.
746
+
703
747
  ## Static schema-allowlist pairs
704
748
 
705
749
  `extractStaticStringPairs(source)` from `pg-schema-snapshot` collects the first two string literals
@@ -0,0 +1,6 @@
1
+ /** Prints the JSON report (nothing outside a Git repository). Exits 0 whenever a report is made. */
2
+ export declare function runFreshBase(options: {
3
+ remote?: string;
4
+ branch?: string;
5
+ skipFetch: boolean;
6
+ }): number;
@@ -0,0 +1,14 @@
1
+ import { checkFreshBase } from "../../fresh-base/index.mjs";
2
+ /** Prints the JSON report (nothing outside a Git repository). Exits 0 whenever a report is made. */
3
+ export function runFreshBase(options) {
4
+ try {
5
+ const report = checkFreshBase(options);
6
+ if (report !== null)
7
+ process.stdout.write(`${JSON.stringify(report)}\n`);
8
+ return 0;
9
+ }
10
+ catch (error) {
11
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
12
+ return 1;
13
+ }
14
+ }
@@ -21,6 +21,7 @@ import { runRetrospectiveFactsCommand } from "./commands/retrospective-facts.mjs
21
21
  import { runAgentBlackboardCommand } from "./commands/agent-blackboard.mjs";
22
22
  import { runMcpCommand } from "./commands/mcp.mjs";
23
23
  import { runWithHostLock } from "./commands/with-host-lock.mjs";
24
+ import { runFreshBase } from "./commands/fresh-base.mjs";
24
25
  import { runRequireUpToDate } from "./commands/require-up-to-date.mjs";
25
26
  import { runAstGrepExamplesCommand } from "./commands/ast-grep-examples.mjs";
26
27
  import { runAstGrepPackCommand } from "./commands/ast-grep-pack.mjs";
@@ -135,6 +136,8 @@ export function runCli(argv = process.argv) {
135
136
  return runMcpCommand(parsed.args);
136
137
  case 'require-up-to-date':
137
138
  return runRequireUpToDate(parsed);
139
+ case 'fresh-base':
140
+ return runFreshBase(parsed);
138
141
  case 'gitleaks-directory-scan':
139
142
  return runScript('bash', packageScriptPath('scripts/gitleaks-directory-scan.sh'), [
140
143
  '--config',
@@ -0,0 +1,2 @@
1
+ import type { ParsedCli } from './parse.mts';
2
+ export declare function parseFreshBase(args: readonly string[]): ParsedCli;
@@ -0,0 +1,15 @@
1
+ import { parseOptions } from "./parse-direct-options.mjs";
2
+ export function parseFreshBase(args) {
3
+ const skipFetch = args.includes('--skip-fetch');
4
+ const rest = args.filter((arg) => arg !== '--skip-fetch');
5
+ const options = parseOptions(rest, ['remote', 'branch'], 'fresh-base', 'name');
6
+ if ('kind' in options)
7
+ return options;
8
+ const { remote, branch } = options.values;
9
+ return {
10
+ kind: 'fresh-base',
11
+ skipFetch,
12
+ ...(remote === undefined ? {} : { remote }),
13
+ ...(branch === undefined ? {} : { branch }),
14
+ };
15
+ }
@@ -70,6 +70,11 @@ export type ParsedCli = {
70
70
  kind: 'require-up-to-date';
71
71
  remote: string;
72
72
  branch: string;
73
+ } | {
74
+ kind: 'fresh-base';
75
+ remote?: string;
76
+ branch?: string;
77
+ skipFetch: boolean;
73
78
  } | {
74
79
  kind: 'gitleaks-directory-scan';
75
80
  config: string;
@@ -1,6 +1,7 @@
1
1
  import { parseGhaArtifactsCleanup, } from "./parse-gha-artifacts-cleanup.mjs";
2
2
  import { parseGhaRuntimeAudit } from "./parse-gha-runtime-audit.mjs";
3
3
  import { parseForwardedCommand } from "./parse-forwarded-commands.mjs";
4
+ import { parseFreshBase } from "./parse-fresh-base.mjs";
4
5
  import { commandNames } from "./usage.mjs";
5
6
  import { parseAstGrepExamples, parseAstGrepPack, parseGhaWorkspacePolicy, parseGitleaksDirectoryScan, parseHttpOrigin, parseLinkSkill, parseRequireUpToDate, parseRunnerPortPolicy, } from "./parse-options.mjs";
6
7
  const SCRIPT_COMMANDS = new Set([
@@ -50,6 +51,8 @@ export function parseCli(argv) {
50
51
  return parseLinkSkill(rest);
51
52
  if (command === 'require-up-to-date')
52
53
  return parseRequireUpToDate(rest);
54
+ if (command === 'fresh-base')
55
+ return parseFreshBase(rest);
53
56
  if (command === 'gitleaks-directory-scan')
54
57
  return parseGitleaksDirectoryScan(rest);
55
58
  if (command === 'ast-grep-examples')
@@ -1,4 +1,4 @@
1
- export declare const USAGE = "Usage: vouchington <command> [options]\n\nCommands:\n runner-port-policy Print or validate a runner port policy\n with-host-lock Run a command under a host-wide lock\n gha-runtime-audit Audit successful GitHub Actions job runtimes\n require-up-to-date Require HEAD to include a fetched remote branch\n gitleaks-directory-scan Scan a directory with Gitleaks\n ast-grep-examples Run AST-grep rule examples\n ast-grep-pack Print shipped unconditional AST-grep pack paths as JSON\n gha-workspace-policy Check GitHub Actions workspace safety policy\n gha-output Write a collision-safe multiline GITHUB_OUTPUT record\n gha-needs-results Fail if required GitHub Actions job results failed\n download-with-diagnostics Download a URL and report HTTP status on failure\n download-optional-run-artifacts Download optional artifacts from the current run\n host-pressure-diagnostics Print a bounded host memory/OOM/PSI snapshot\n allocate-browser-safe-ports Allocate Fetch-safe localhost ports\n diagnose-port-collision Capture bounded localhost port diagnostics\n prepare-trivy-db Download the Trivy vulnerability database\n gha-artifacts-cleanup Delete classified GitHub Actions artifacts\n http-origin Validate an optional HTTP(S) origin\n vitest-blob-manifest Stamp a vitest-blob-manifest:v1 identity file\n vitest-report-attempt Write or read a Vitest report-attempt marker\n prepare-vitest-reports Validate and select Vitest report JSON files\n pnpm-install Install a pnpm workspace with retry and release-age fail-fast\n check-cache-size Measure a path and decide whether to save a GHA cache\n make-shard-matrix Emit a [1..N] GitHub Actions shard matrix\n load-runner-env Overlay a runner env file onto GITHUB_ENV with injection guards\n clean-workspace Reset a persistent-runner workspace with a fork-PR trust gate\n install-github-release Download a checksum-verified GitHub Release binary\n run-with-timeout Run a command with GNU timeout or a Perl fallback\n lint-links Two-pass lychee: internal links fail, external warn\n materialize-pr-context Dump PR title/body/files/diff/comments and #N crawl\n wait-for-apt-locks Wait until apt/dpkg lock files are free\n install-playwright-chromium-arm64 Install Playwright Chromium from browsers.json\n ghcr-package-retention Delete old GHCR package versions past KEEP_MIN\n harness-admission-lane Compute a GITHUB_RUN_ID admission lane for fleet fan-out\n harness-assert-gates Fail if any named HARNESS_*_ENABLED gate is enabled\n gha-collaborator-trust Classify authors as write-access collaborators or not\n nuget-central-version Validate a Directory.Packages.props PackageVersion delta\n swift-semantic-equal Compare Swift sources ignoring comments and whitespace\n post-review Post one COMMENT review from a staged payload file\n stage-review-payload Validate a review payload file into a staging directory\n retrospective-transcript Format facts from Claude-compatible, Codex, or Grok transcripts\n link-skill Link one packaged skill into an explicit consumer directory\n retrospective-facts Gather immutable facts for a retrospective\n agent-blackboard Probe and journal an Agent Blackboard deployment\n mcp Serve the agent-blackboard journal tools over stdio (MCP)\n\nOptions:\n -h, --help Show this help\n -v, --version Print the package version\n\nrunner-port-policy\n (no args) Print the shipped policy as JSON\n --file <path> Validate and print a policy file\n --reserved <port> Print true if the port is reserved\n\nwith-host-lock\n --name <family>\n [--slots <n>]\n --timeout-seconds <n>\n [--command-timeout-seconds <n>]\n [--failure-diagnostics <absolute-script>]\n [--on-acquire-timeout fail|run-unlocked]\n -- <command> [args...]\n\ngha-runtime-audit\n [--repository owner/name] Default GITHUB_REPOSITORY\n [--branch main]\n [--median-threshold-floor <seconds>] Off unless set. Must be below the ceiling\n [--median-threshold-ceiling <seconds>] Default 360. Flags a five-sample median above this\n --pr-workflow <name|/regex/> Repeatable\n --push-workflow <name|/regex/> Repeatable\n\nrequire-up-to-date --remote <name> --branch <name>\ngitleaks-directory-scan --config <path> [--directory <path>]\nast-grep-examples --rules <directory> --config <path>\nast-grep-pack\ngha-workspace-policy [--root <directory>] [--workflow-directory <directory>] [--action-directory <directory>]\n\ngha-output <name>\ngha-needs-results [label]\ndownload-with-diagnostics <url> <destination> [-- curl-args...]\ndownload-optional-run-artifacts (--name <name>... | --pattern <pattern>) --dir <directory>\nhost-pressure-diagnostics\nallocate-browser-safe-ports <count> [--policy path] [--forbidden-ports path]\ndiagnose-port-collision [--ports \"2200 2216\"] [--output-dir PATH]\nprepare-trivy-db\ngha-artifacts-cleanup run --run-id <id> [--keep-pattern glob] [--delete-pattern glob] [--patterns-file json]\ngha-artifacts-cleanup sweep --older-than-hours <n> [--keep-pattern glob] [--delete-pattern glob] [--patterns-file json]\nhttp-origin [--field NAME] [value]\nvitest-blob-manifest <suite> [reports-directory]\nvitest-report-attempt <write DIRECTORY SUITE|read ROOT>\nprepare-vitest-reports [primary-directory] [fallback-directory] [output-directory]\npnpm-install --runner-lifecycle persistent|ephemeral|ephemeral-full --install-scripts true|false\ncheck-cache-size <path> <max-bytes> <label>\nmake-shard-matrix <total>\nload-runner-env\nclean-workspace\ninstall-github-release --repo owner/name --version X --asset 'name-{platform}.tar.gz' --bin name [--tag-prefix PREFIX] [--expected-sha256 SHA256] [--no-checksum] [--checksums-asset NAME] [--version-flag FLAG] [--bin-dir DIR]\nrun-with-timeout <timeout-seconds> <kill-after-seconds> <command...>\nlint-links [--offline] [--config PATH] [--glob PATTERN] [files...]\nmaterialize-pr-context\nwait-for-apt-locks\ninstall-playwright-chromium-arm64 [name:archive...]\nghcr-package-retention <url-encoded-package>...\nharness-admission-lane <lanes>\nharness-assert-gates <gate>...\ngha-collaborator-trust <owner/repo> [--allow-bot <login>]... < authors.json\nnuget-central-version <trusted-props> <candidate-props> <metadata-json> <output-props>\nswift-semantic-equal <base> <head> <file.swift>\npost-review\nstage-review-payload optional|required <source> <destination>\nretrospective-transcript [--session-id ID] [--jsonl PATH] [--projects-dir PATH] [--codex-sessions-dir PATH] [--grok-sessions-dir PATH]\nlink-skill <name> --source-root <skills-dir> --target-root <consumer-skills-dir> Link a packaged or repository-local skill\nretrospective-facts (--pr NUMBER | --branch NAME | --no-pr) [--repo OWNER/NAME] [--raw]\nagent-blackboard probe\nagent-blackboard journal append --mode interactive|autonomous --source-event-id ID --work-outcome OUTCOME --coverage-status STATUS --session-id UUID --agent NAME --version VERSION --file PATH --repository OWNER/NAME [--repository OWNER/NAME ...] [--parent-session-id UUID] [--timestamp ISO8601] [--coverage-source SOURCE,...] [--dropped-count N] [--outbox-directory PATH]\nagent-blackboard journal entries --session-id UUID\nagent-blackboard journal status --session-id UUID --outbox-directory PATH\nagent-blackboard journal flush --outbox-directory PATH [--session-id UUID]\nInteractive journal append requires --outbox-directory PATH.\nagent-blackboard journal is deprecated and being phased out: agents journal through the mcp server.\nagent-blackboard snapshot partition --snapshot PATH --checksum SHA256 --counts '{\"sessions\":N,\"entries\":N,\"records\":N,\"bytes\":N}'\nagent-blackboard snapshot cleanup [--snapshot PATH] [--partition-directory PATH --receipt JSON]\nmcp\n (no args) Serve journal_append, journal_entries, outbox_status, outbox_flush,\n session_ensure, retrospective_check, retrospective_save,\n pr_validate, pr_create, pr_update,\n snapshot_export, and session_archive over stdio.\n Needs the optional @modelcontextprotocol/sdk peer (and zod).\n Launch it from inside the repository's git worktree.\n";
1
+ export declare const USAGE = "Usage: vouchington <command> [options]\n\nCommands:\n runner-port-policy Print or validate a runner port policy\n with-host-lock Run a command under a host-wide lock\n gha-runtime-audit Audit successful GitHub Actions job runtimes\n require-up-to-date Require HEAD to include a fetched remote branch\n fresh-base Classify the worktree against a remote base branch\n gitleaks-directory-scan Scan a directory with Gitleaks\n ast-grep-examples Run AST-grep rule examples\n ast-grep-pack Print shipped unconditional AST-grep pack paths as JSON\n gha-workspace-policy Check GitHub Actions workspace safety policy\n gha-output Write a collision-safe multiline GITHUB_OUTPUT record\n gha-needs-results Fail if required GitHub Actions job results failed\n download-with-diagnostics Download a URL and report HTTP status on failure\n download-optional-run-artifacts Download optional artifacts from the current run\n host-pressure-diagnostics Print a bounded host memory/OOM/PSI snapshot\n allocate-browser-safe-ports Allocate Fetch-safe localhost ports\n diagnose-port-collision Capture bounded localhost port diagnostics\n prepare-trivy-db Download the Trivy vulnerability database\n gha-artifacts-cleanup Delete classified GitHub Actions artifacts\n http-origin Validate an optional HTTP(S) origin\n vitest-blob-manifest Stamp a vitest-blob-manifest:v1 identity file\n vitest-report-attempt Write or read a Vitest report-attempt marker\n prepare-vitest-reports Validate and select Vitest report JSON files\n pnpm-install Install a pnpm workspace with retry and release-age fail-fast\n check-cache-size Measure a path and decide whether to save a GHA cache\n make-shard-matrix Emit a [1..N] GitHub Actions shard matrix\n load-runner-env Overlay a runner env file onto GITHUB_ENV with injection guards\n clean-workspace Reset a persistent-runner workspace with a fork-PR trust gate\n install-github-release Download a checksum-verified GitHub Release binary\n run-with-timeout Run a command with GNU timeout or a Perl fallback\n lint-links Two-pass lychee: internal links fail, external warn\n materialize-pr-context Dump PR title/body/files/diff/comments and #N crawl\n wait-for-apt-locks Wait until apt/dpkg lock files are free\n install-playwright-chromium-arm64 Install Playwright Chromium from browsers.json\n ghcr-package-retention Delete old GHCR package versions past KEEP_MIN\n harness-admission-lane Compute a GITHUB_RUN_ID admission lane for fleet fan-out\n harness-assert-gates Fail if any named HARNESS_*_ENABLED gate is enabled\n gha-collaborator-trust Classify authors as write-access collaborators or not\n nuget-central-version Validate a Directory.Packages.props PackageVersion delta\n swift-semantic-equal Compare Swift sources ignoring comments and whitespace\n post-review Post one COMMENT review from a staged payload file\n stage-review-payload Validate a review payload file into a staging directory\n retrospective-transcript Format facts from Claude-compatible, Codex, or Grok transcripts\n link-skill Link one packaged skill into an explicit consumer directory\n retrospective-facts Gather immutable facts for a retrospective\n agent-blackboard Probe and journal an Agent Blackboard deployment\n mcp Serve the agent-blackboard journal tools over stdio (MCP)\n\nOptions:\n -h, --help Show this help\n -v, --version Print the package version\n\nrunner-port-policy\n (no args) Print the shipped policy as JSON\n --file <path> Validate and print a policy file\n --reserved <port> Print true if the port is reserved\n\nwith-host-lock\n --name <family>\n [--slots <n>]\n --timeout-seconds <n>\n [--command-timeout-seconds <n>]\n [--failure-diagnostics <absolute-script>]\n [--on-acquire-timeout fail|run-unlocked]\n -- <command> [args...]\n\ngha-runtime-audit\n [--repository owner/name] Default GITHUB_REPOSITORY\n [--branch main]\n [--median-threshold-floor <seconds>] Off unless set. Must be below the ceiling\n [--median-threshold-ceiling <seconds>] Default 360. Flags a five-sample median above this\n --pr-workflow <name|/regex/> Repeatable\n --push-workflow <name|/regex/> Repeatable\n\nrequire-up-to-date --remote <name> --branch <name>\nfresh-base [--remote <name>] [--branch <name>] [--skip-fetch]\ngitleaks-directory-scan --config <path> [--directory <path>]\nast-grep-examples --rules <directory> --config <path>\nast-grep-pack\ngha-workspace-policy [--root <directory>] [--workflow-directory <directory>] [--action-directory <directory>]\n\ngha-output <name>\ngha-needs-results [label]\ndownload-with-diagnostics <url> <destination> [-- curl-args...]\ndownload-optional-run-artifacts (--name <name>... | --pattern <pattern>) --dir <directory>\nhost-pressure-diagnostics\nallocate-browser-safe-ports <count> [--policy path] [--forbidden-ports path]\ndiagnose-port-collision [--ports \"2200 2216\"] [--output-dir PATH]\nprepare-trivy-db\ngha-artifacts-cleanup run --run-id <id> [--keep-pattern glob] [--delete-pattern glob] [--patterns-file json]\ngha-artifacts-cleanup sweep --older-than-hours <n> [--keep-pattern glob] [--delete-pattern glob] [--patterns-file json]\nhttp-origin [--field NAME] [value]\nvitest-blob-manifest <suite> [reports-directory]\nvitest-report-attempt <write DIRECTORY SUITE|read ROOT>\nprepare-vitest-reports [primary-directory] [fallback-directory] [output-directory]\npnpm-install --runner-lifecycle persistent|ephemeral|ephemeral-full --install-scripts true|false\ncheck-cache-size <path> <max-bytes> <label>\nmake-shard-matrix <total>\nload-runner-env\nclean-workspace\ninstall-github-release --repo owner/name --version X --asset 'name-{platform}.tar.gz' --bin name [--tag-prefix PREFIX] [--expected-sha256 SHA256] [--no-checksum] [--checksums-asset NAME] [--version-flag FLAG] [--bin-dir DIR]\nrun-with-timeout <timeout-seconds> <kill-after-seconds> <command...>\nlint-links [--offline] [--config PATH] [--glob PATTERN] [files...]\nmaterialize-pr-context\nwait-for-apt-locks\ninstall-playwright-chromium-arm64 [name:archive...]\nghcr-package-retention <url-encoded-package>...\nharness-admission-lane <lanes>\nharness-assert-gates <gate>...\ngha-collaborator-trust <owner/repo> [--allow-bot <login>]... < authors.json\nnuget-central-version <trusted-props> <candidate-props> <metadata-json> <output-props>\nswift-semantic-equal <base> <head> <file.swift>\npost-review\nstage-review-payload optional|required <source> <destination>\nretrospective-transcript [--session-id ID] [--jsonl PATH] [--projects-dir PATH] [--codex-sessions-dir PATH] [--grok-sessions-dir PATH]\nlink-skill <name> --source-root <skills-dir> --target-root <consumer-skills-dir> Link a packaged or repository-local skill\nretrospective-facts (--pr NUMBER | --branch NAME | --no-pr) [--repo OWNER/NAME] [--raw]\nagent-blackboard probe\nagent-blackboard journal append --mode interactive|autonomous --source-event-id ID --work-outcome OUTCOME --coverage-status STATUS --session-id UUID --agent NAME --version VERSION --file PATH --repository OWNER/NAME [--repository OWNER/NAME ...] [--parent-session-id UUID] [--timestamp ISO8601] [--coverage-source SOURCE,...] [--dropped-count N] [--outbox-directory PATH]\nagent-blackboard journal entries --session-id UUID\nagent-blackboard journal status --session-id UUID --outbox-directory PATH\nagent-blackboard journal flush --outbox-directory PATH [--session-id UUID]\nInteractive journal append requires --outbox-directory PATH.\nagent-blackboard journal is deprecated and being phased out: agents journal through the mcp server.\nagent-blackboard snapshot partition --snapshot PATH --checksum SHA256 --counts '{\"sessions\":N,\"entries\":N,\"records\":N,\"bytes\":N}'\nagent-blackboard snapshot cleanup [--snapshot PATH] [--partition-directory PATH --receipt JSON]\nmcp\n (no args) Serve journal_append, journal_entries, outbox_status, outbox_flush,\n session_ensure, retrospective_check, retrospective_save,\n pr_validate, pr_create, pr_update,\n snapshot_export, and session_archive over stdio.\n Needs the optional @modelcontextprotocol/sdk peer (and zod).\n Launch it from inside the repository's git worktree.\n";
2
2
  export declare function printUsage(stream?: NodeJS.WritableStream): void;
3
3
  export declare function commandNames(usage?: string): string[];
4
4
  export declare function commandUsage(command: string, usage?: string): string;
@@ -5,6 +5,7 @@ Commands:
5
5
  with-host-lock Run a command under a host-wide lock
6
6
  gha-runtime-audit Audit successful GitHub Actions job runtimes
7
7
  require-up-to-date Require HEAD to include a fetched remote branch
8
+ fresh-base Classify the worktree against a remote base branch
8
9
  gitleaks-directory-scan Scan a directory with Gitleaks
9
10
  ast-grep-examples Run AST-grep rule examples
10
11
  ast-grep-pack Print shipped unconditional AST-grep pack paths as JSON
@@ -74,6 +75,7 @@ gha-runtime-audit
74
75
  --push-workflow <name|/regex/> Repeatable
75
76
 
76
77
  require-up-to-date --remote <name> --branch <name>
78
+ fresh-base [--remote <name>] [--branch <name>] [--skip-fetch]
77
79
  gitleaks-directory-scan --config <path> [--directory <path>]
78
80
  ast-grep-examples --rules <directory> --config <path>
79
81
  ast-grep-pack
@@ -0,0 +1,21 @@
1
+ export type FreshBaseFetch = 'fresh' | 'skipped' | 'failed';
2
+ export type FreshBaseTree = 'clean' | 'dirty' | 'unknown';
3
+ export type FreshBaseRelation = 'matches' | 'behind' | 'ahead' | 'diverged' | 'unknown';
4
+ export type FreshBaseStatus = 'up-to-date' | 'ahead' | 'behind' | 'diverged' | 'dirty' | 'fetch-failed' | 'unknown';
5
+ export interface FreshBaseInput {
6
+ /** Commits on HEAD that the base lacks; null when the comparison was unavailable. */
7
+ readonly ahead: number | null;
8
+ /** Commits on the base that HEAD lacks; null when the comparison was unavailable. */
9
+ readonly behind: number | null;
10
+ readonly tree: FreshBaseTree;
11
+ readonly fetch: FreshBaseFetch;
12
+ }
13
+ export interface FreshBaseClassification extends FreshBaseInput {
14
+ readonly status: FreshBaseStatus;
15
+ readonly relation: FreshBaseRelation;
16
+ /** True for every status except `up-to-date`. */
17
+ readonly needsAttention: boolean;
18
+ }
19
+ export declare function relationOf(ahead: number | null, behind: number | null): FreshBaseRelation;
20
+ /** Pure classification of a worktree against its base from already-gathered facts. */
21
+ export declare function classifyFreshBase(input: FreshBaseInput): FreshBaseClassification;
@@ -0,0 +1,29 @@
1
+ export function relationOf(ahead, behind) {
2
+ if (ahead === null || behind === null)
3
+ return 'unknown';
4
+ if (ahead === 0 && behind === 0)
5
+ return 'matches';
6
+ if (ahead === 0)
7
+ return 'behind';
8
+ if (behind === 0)
9
+ return 'ahead';
10
+ return 'diverged';
11
+ }
12
+ function statusOf(relation, input) {
13
+ // A failed refresh means a cached "matches" is not evidence of freshness.
14
+ if (input.fetch === 'failed')
15
+ return 'fetch-failed';
16
+ if (relation === 'unknown')
17
+ return 'unknown';
18
+ if (relation !== 'matches')
19
+ return relation;
20
+ if (input.tree === 'dirty')
21
+ return 'dirty';
22
+ return input.tree === 'clean' ? 'up-to-date' : 'unknown';
23
+ }
24
+ /** Pure classification of a worktree against its base from already-gathered facts. */
25
+ export function classifyFreshBase(input) {
26
+ const relation = relationOf(input.ahead, input.behind);
27
+ const status = statusOf(relation, input);
28
+ return { ...input, relation, status, needsAttention: status !== 'up-to-date' };
29
+ }
@@ -0,0 +1,4 @@
1
+ export { classifyFreshBase, relationOf } from './classify.mts';
2
+ export type { FreshBaseClassification, FreshBaseFetch, FreshBaseInput, FreshBaseRelation, FreshBaseStatus, FreshBaseTree, } from './classify.mts';
3
+ export { checkFreshBase } from './run.mts';
4
+ export type { FreshBaseOptions, FreshBaseReport, GitResult, GitRunner } from './run.mts';
@@ -0,0 +1,2 @@
1
+ export { classifyFreshBase, relationOf } from "./classify.mjs";
2
+ export { checkFreshBase } from "./run.mjs";
@@ -0,0 +1,25 @@
1
+ import type { FreshBaseClassification } from './classify.mts';
2
+ export interface GitResult {
3
+ readonly status: number;
4
+ readonly stdout: string;
5
+ }
6
+ export type GitRunner = (args: readonly string[], cwd?: string, timeoutMs?: number) => GitResult;
7
+ export interface FreshBaseOptions {
8
+ readonly remote?: string;
9
+ readonly branch?: string;
10
+ readonly cwd?: string;
11
+ /** Skip the refresh and compare against the cached remote-tracking ref. */
12
+ readonly skipFetch?: boolean;
13
+ readonly fetchTimeoutMs?: number;
14
+ readonly git?: GitRunner;
15
+ }
16
+ export interface FreshBaseReport extends FreshBaseClassification {
17
+ readonly base: string;
18
+ /** Current branch name, or `detached HEAD`. */
19
+ readonly branch: string;
20
+ }
21
+ /**
22
+ * Refreshes the base, then classifies the worktree against it. Returns null outside a Git
23
+ * repository. Git failures never throw; they surface as `fetch-failed` or `unknown`.
24
+ */
25
+ export declare function checkFreshBase(options?: FreshBaseOptions): FreshBaseReport | null;
@@ -0,0 +1,66 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { classifyFreshBase } from "./classify.mjs";
3
+ const DEFAULT_FETCH_TIMEOUT_MS = 8000;
4
+ const defaultGit = (args, cwd, timeoutMs) => {
5
+ const result = spawnSync('git', [...args], {
6
+ cwd,
7
+ encoding: 'utf8',
8
+ stdio: ['ignore', 'pipe', 'ignore'],
9
+ ...(timeoutMs === undefined ? {} : { timeout: timeoutMs }),
10
+ });
11
+ return { status: result.status ?? 1, stdout: result.stdout ?? '' };
12
+ };
13
+ function validateName(value, option) {
14
+ if (value === '' || value.startsWith('-')) {
15
+ throw new Error(`${option} must be a non-option Git name`);
16
+ }
17
+ }
18
+ function countsOf(git, ref, cwd) {
19
+ // A shallow history can hide the ancestry that connects HEAD and the base.
20
+ const shallow = git(['rev-parse', '--is-shallow-repository'], cwd);
21
+ if (shallow.status !== 0 || shallow.stdout.trim() !== 'false') {
22
+ return { ahead: null, behind: null };
23
+ }
24
+ const counts = git(['rev-list', '--left-right', '--count', `${ref}...HEAD`], cwd);
25
+ const [behind, ahead] = counts.stdout.trim().split(/\s+/u).map(Number);
26
+ if (counts.status !== 0 || !Number.isInteger(behind) || !Number.isInteger(ahead)) {
27
+ return { ahead: null, behind: null };
28
+ }
29
+ return { ahead: ahead, behind: behind };
30
+ }
31
+ /**
32
+ * Refreshes the base, then classifies the worktree against it. Returns null outside a Git
33
+ * repository. Git failures never throw; they surface as `fetch-failed` or `unknown`.
34
+ */
35
+ export function checkFreshBase(options = {}) {
36
+ const remote = options.remote ?? 'origin';
37
+ const branch = options.branch ?? 'main';
38
+ validateName(remote, 'remote');
39
+ validateName(branch, 'branch');
40
+ const git = options.git ?? defaultGit;
41
+ const { cwd } = options;
42
+ if (git(['rev-parse', '--git-dir'], cwd).status !== 0)
43
+ return null;
44
+ if (git(['check-ref-format', '--branch', branch], cwd).status !== 0) {
45
+ throw new Error('branch must be a valid Git branch name');
46
+ }
47
+ const ref = `refs/remotes/${remote}/${branch}`;
48
+ let fetch = 'skipped';
49
+ if (options.skipFetch !== true) {
50
+ const timeout = options.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
51
+ const fetched = git(['fetch', remote, `+refs/heads/${branch}:${ref}`, '--quiet'], cwd, timeout);
52
+ fetch = fetched.status === 0 ? 'fresh' : 'failed';
53
+ }
54
+ const current = git(['branch', '--show-current'], cwd).stdout.trim();
55
+ const status = git(['status', '--porcelain', '--untracked-files=normal'], cwd);
56
+ let tree = 'unknown';
57
+ if (status.status === 0)
58
+ tree = status.stdout.trim() === '' ? 'clean' : 'dirty';
59
+ const available = git(['rev-parse', '--verify', '--quiet', ref], cwd).status === 0;
60
+ const counts = available ? countsOf(git, ref, cwd) : { ahead: null, behind: null };
61
+ return {
62
+ ...classifyFreshBase({ ...counts, tree, fetch }),
63
+ base: `${remote}/${branch}`,
64
+ branch: current === '' ? 'detached HEAD' : current,
65
+ };
66
+ }
@@ -0,0 +1,24 @@
1
+ export type ClosingIssueReference = {
2
+ key: string;
3
+ number: number;
4
+ owner: string | undefined;
5
+ repo: string | undefined;
6
+ };
7
+ /**
8
+ * GitHub closing keywords (close/closes/closed, fix/fixes/fixed, resolve/resolves/resolved,
9
+ * optionally followed by a colon) followed by `#N`, `owner/repo#N` or a
10
+ * `https://github.com/owner/repo/issues/N` URL. Owner and repo are lowercased and references are
11
+ * deduplicated by key, in first-seen order.
12
+ *
13
+ * The body is scanned as raw text: closing keywords inside HTML comments or code spans/fences (for
14
+ * example a PR template example) are reported too. Callers that must ignore them blank those spans
15
+ * out before calling, as `parseNonClosingRefs` callers do with their scoped text.
16
+ */
17
+ export declare function parseClosingIssueReferences(body: string): ClosingIssueReference[];
18
+ export declare function hasClosingIssueReference(body: string): boolean;
19
+ /** `#N` for a same-repository reference, otherwise lowercased `owner/repo#N`. */
20
+ export declare function formatReferenceKey(ref: {
21
+ number: number;
22
+ owner?: string | undefined;
23
+ repo?: string | undefined;
24
+ }): string;
@@ -0,0 +1,37 @@
1
+ const CLOSING_ISSUE_RE = /\b(?:close(?:s|d)?|fix(?:es|ed)?|resolve(?:s|d)?):?[ \t]+(?:(?<repo>[\w.-]+\/[\w.-]+)?#(?<number>\d+)|https?:\/\/github\.com\/(?<urlRepo>[\w.-]+\/[\w.-]+)\/issues\/(?<urlNumber>\d+))\b/gi;
2
+ /**
3
+ * GitHub closing keywords (close/closes/closed, fix/fixes/fixed, resolve/resolves/resolved,
4
+ * optionally followed by a colon) followed by `#N`, `owner/repo#N` or a
5
+ * `https://github.com/owner/repo/issues/N` URL. Owner and repo are lowercased and references are
6
+ * deduplicated by key, in first-seen order.
7
+ *
8
+ * The body is scanned as raw text: closing keywords inside HTML comments or code spans/fences (for
9
+ * example a PR template example) are reported too. Callers that must ignore them blank those spans
10
+ * out before calling, as `parseNonClosingRefs` callers do with their scoped text.
11
+ */
12
+ export function parseClosingIssueReferences(body) {
13
+ const refs = [];
14
+ const seen = new Set();
15
+ for (const match of body.matchAll(CLOSING_ISSUE_RE)) {
16
+ const number = Number(match.groups?.number ?? match.groups?.urlNumber);
17
+ if (!Number.isSafeInteger(number) || number < 1)
18
+ continue;
19
+ const repoRef = (match.groups?.repo ?? match.groups?.urlRepo)?.toLowerCase();
20
+ const [owner, repo] = repoRef?.split('/') ?? [undefined, undefined];
21
+ const key = formatReferenceKey({ number, owner, repo });
22
+ if (seen.has(key))
23
+ continue;
24
+ seen.add(key);
25
+ refs.push({ key, number, owner, repo });
26
+ }
27
+ return refs;
28
+ }
29
+ export function hasClosingIssueReference(body) {
30
+ return parseClosingIssueReferences(body).length > 0;
31
+ }
32
+ /** `#N` for a same-repository reference, otherwise lowercased `owner/repo#N`. */
33
+ export function formatReferenceKey(ref) {
34
+ return ref.owner !== undefined && ref.repo !== undefined
35
+ ? `${ref.owner.toLowerCase()}/${ref.repo.toLowerCase()}#${ref.number}`
36
+ : `#${ref.number}`;
37
+ }
@@ -6,3 +6,6 @@ export { GITHUB_BODY_MAX_CHARACTERS, validateGitHubBodyLength } from './body-len
6
6
  export type { GitHubBodyLengthValidation } from './body-length.mts';
7
7
  export { assertHeadPushed, buildGhPrCreateArgs, createPullRequest, DetachedHeadError, HeadNotPushedError, HeadOutOfDateError, resolveHeadBranch, } from './pr-create.mts';
8
8
  export type { AssertHeadPushedOptions, BuildGhPrCreateArgsOptions, CreatePullRequestExecutors, CreatePullRequestOptions, } from './pr-create.mts';
9
+ export { formatReferenceKey, hasClosingIssueReference, parseClosingIssueReferences, } from './closing-refs.mts';
10
+ export type { ClosingIssueReference } from './closing-refs.mts';
11
+ export { parseNonClosingRefs } from './non-closing-refs.mts';
@@ -2,3 +2,5 @@ export { createCommandRunner, runGh, runGit } from "./exec.mjs";
2
2
  export { DiffCommandError, processDiffCommand } from "./diff.mjs";
3
3
  export { GITHUB_BODY_MAX_CHARACTERS, validateGitHubBodyLength } from "./body-length.mjs";
4
4
  export { assertHeadPushed, buildGhPrCreateArgs, createPullRequest, DetachedHeadError, HeadNotPushedError, HeadOutOfDateError, resolveHeadBranch, } from "./pr-create.mjs";
5
+ export { formatReferenceKey, hasClosingIssueReference, parseClosingIssueReferences, } from "./closing-refs.mjs";
6
+ export { parseNonClosingRefs } from "./non-closing-refs.mjs";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Collects non-closing references (`Ref`, `Refs`, `Part of`, optionally followed by a colon) from
3
+ * `referenceText`. Returns reference keys (`#N` or lowercased `owner/repo#N`, the format
4
+ * `formatReferenceKey` produces) rather than bare numbers, so a `Refs owner/other-repo#42` cannot
5
+ * disposition a same-numbered local `#42`; callers match by key.
6
+ *
7
+ * The caller decides which text counts: pass only the section that carries issue dispositions (for
8
+ * example a "Related issues" section with code, comments and HTML blanked out), so an incidental
9
+ * `Refs #N` elsewhere does not satisfy an audit.
10
+ */
11
+ export declare function parseNonClosingRefs(referenceText: string): Set<string>;
@@ -0,0 +1,24 @@
1
+ import { formatReferenceKey } from "./closing-refs.mjs";
2
+ const NON_CLOSING_REF_RE = /\b(?:refs?|part\s+of):?[ \t]+(?:(?<ownerRepo>[\w.-]+\/[\w.-]+))?#(?<number>\d+)\b/gi;
3
+ /**
4
+ * Collects non-closing references (`Ref`, `Refs`, `Part of`, optionally followed by a colon) from
5
+ * `referenceText`. Returns reference keys (`#N` or lowercased `owner/repo#N`, the format
6
+ * `formatReferenceKey` produces) rather than bare numbers, so a `Refs owner/other-repo#42` cannot
7
+ * disposition a same-numbered local `#42`; callers match by key.
8
+ *
9
+ * The caller decides which text counts: pass only the section that carries issue dispositions (for
10
+ * example a "Related issues" section with code, comments and HTML blanked out), so an incidental
11
+ * `Refs #N` elsewhere does not satisfy an audit.
12
+ */
13
+ export function parseNonClosingRefs(referenceText) {
14
+ const keys = new Set();
15
+ for (const match of referenceText.matchAll(NON_CLOSING_REF_RE)) {
16
+ const number = Number(match.groups?.number);
17
+ if (!Number.isSafeInteger(number) || number < 1)
18
+ continue;
19
+ const ownerRepo = match.groups?.ownerRepo?.toLowerCase();
20
+ const [owner, repo] = ownerRepo?.split('/') ?? [undefined, undefined];
21
+ keys.add(formatReferenceKey({ number, owner, repo }));
22
+ }
23
+ return keys;
24
+ }
package/dist/index.d.mts CHANGED
@@ -19,6 +19,8 @@ export { auditCiJobRuntime, parseWorkflowNameMatch } from './gha-runtime-audit/i
19
19
  export type { GhApiExecutor, RuntimeAuditOptions, RuntimeAuditResult, RuntimeAuditWorkflowFilter, RuntimeJobResult, RuntimeSample, } from './gha-runtime-audit/index.mts';
20
20
  export { checkGhaWorkspacePolicy } from './gha-workspace-policy/index.mts';
21
21
  export type { GhaWorkspacePolicyOptions } from './gha-workspace-policy/index.mts';
22
+ export { checkFreshBase, classifyFreshBase, relationOf } from './fresh-base/index.mts';
23
+ export type { FreshBaseClassification, FreshBaseFetch, FreshBaseInput, FreshBaseOptions, FreshBaseRelation, FreshBaseReport, FreshBaseStatus, FreshBaseTree, GitResult, GitRunner, } from './fresh-base/index.mts';
22
24
  export { requireUpToDate } from './require-up-to-date/index.mts';
23
25
  export type { GitExecutor, RequireUpToDateOptions } from './require-up-to-date/index.mts';
24
26
  export { gitleaksDirectoryScanArguments, runGitleaksDirectoryScan, } from './gitleaks-directory-scan/index.mts';
@@ -101,3 +103,7 @@ export { GITHUB_BODY_MAX_CHARACTERS, validateGitHubBodyLength } from './gh-cli/i
101
103
  export type { GitHubBodyLengthValidation } from './gh-cli/index.mts';
102
104
  export { assertHeadPushed, buildGhPrCreateArgs, createPullRequest, DetachedHeadError, HeadNotPushedError, HeadOutOfDateError, resolveHeadBranch, } from './gh-cli/index.mts';
103
105
  export type { AssertHeadPushedOptions, BuildGhPrCreateArgsOptions, CreatePullRequestExecutors, CreatePullRequestOptions, } from './gh-cli/index.mts';
106
+ export { formatReferenceKey, hasClosingIssueReference, parseClosingIssueReferences, parseNonClosingRefs, } from './gh-cli/index.mts';
107
+ export type { ClosingIssueReference } from './gh-cli/index.mts';
108
+ export { countDiffLineChanges, DEFAULT_LARGE_DIFF_THRESHOLDS, exceedsLargeDiffThreshold, } from './pull-request-diff/index.mts';
109
+ export type { DiffLineChanges, LargeDiffThresholds } from './pull-request-diff/index.mts';
package/dist/index.mjs CHANGED
@@ -10,6 +10,7 @@ export { extractCreateTableMetadata, extractDefaultFunction, extractFuncCallArgC
10
10
  export { dollarQuoteEnd, lineOf, maskSqlQuotedText, readDollarQuoteDelimiter, readStringLiteral, splitSqlStatements, sqlFragments, stripSqlComments, } from "./sql-scanner/index.mjs";
11
11
  export { auditCiJobRuntime, parseWorkflowNameMatch } from "./gha-runtime-audit/index.mjs";
12
12
  export { checkGhaWorkspacePolicy } from "./gha-workspace-policy/index.mjs";
13
+ export { checkFreshBase, classifyFreshBase, relationOf } from "./fresh-base/index.mjs";
13
14
  export { requireUpToDate } from "./require-up-to-date/index.mjs";
14
15
  export { gitleaksDirectoryScanArguments, runGitleaksDirectoryScan, } from "./gitleaks-directory-scan/index.mjs";
15
16
  export { astGrepExamplesArguments, compareAstGrepCompanions, runAstGrepExamples, } from "./ast-grep-examples/index.mjs";
@@ -55,3 +56,5 @@ export { createCommandRunner, runGh, runGit } from "./gh-cli/index.mjs";
55
56
  export { DiffCommandError, processDiffCommand } from "./gh-cli/index.mjs";
56
57
  export { GITHUB_BODY_MAX_CHARACTERS, validateGitHubBodyLength } from "./gh-cli/index.mjs";
57
58
  export { assertHeadPushed, buildGhPrCreateArgs, createPullRequest, DetachedHeadError, HeadNotPushedError, HeadOutOfDateError, resolveHeadBranch, } from "./gh-cli/index.mjs";
59
+ export { formatReferenceKey, hasClosingIssueReference, parseClosingIssueReferences, parseNonClosingRefs, } from "./gh-cli/index.mjs";
60
+ export { countDiffLineChanges, DEFAULT_LARGE_DIFF_THRESHOLDS, exceedsLargeDiffThreshold, } from "./pull-request-diff/index.mjs";
@@ -0,0 +1,29 @@
1
+ export type DiffLineChanges = {
2
+ added: number;
3
+ deleted: number;
4
+ };
5
+ export type LargeDiffThresholds = {
6
+ /** A diff adding strictly more lines than this is large. */
7
+ addedLines: number;
8
+ /** A diff deleting strictly more lines than this is large. */
9
+ deletedLines: number;
10
+ };
11
+ /**
12
+ * Example thresholds for a "decide whether to split this PR" gate: additions are the trigger for a
13
+ * split, while deletion-heavy retirement work stays coherent at a larger limit. Callers pass their
14
+ * own `LargeDiffThresholds`; these are only a default.
15
+ */
16
+ export declare const DEFAULT_LARGE_DIFF_THRESHOLDS: Readonly<LargeDiffThresholds>;
17
+ /**
18
+ * Added + removed lines in a unified diff (`git diff <base>...HEAD` output), excluding the
19
+ * `+++`/`---` file-header lines so renamed/moved files don't double-count their path lines.
20
+ *
21
+ * Known limitation: this matches the literal `+++`/`---` prefix, not "is this actually a file
22
+ * header." An added line whose content starts with `++`, or a removed line whose content starts
23
+ * with `--` (a SQL comment, a CLI flag), renders as `+++`/`---` once the diff marker is prepended
24
+ * and is silently excluded from the count. For a coarse "probably needs a split" heuristic with an
25
+ * explicit override, that undercount is acceptable; this is not a full hunk parser.
26
+ */
27
+ export declare function countDiffLineChanges(diffText: string): DiffLineChanges;
28
+ /** True when either count is strictly greater than its threshold. */
29
+ export declare function exceedsLargeDiffThreshold(changes: DiffLineChanges, thresholds?: LargeDiffThresholds): boolean;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Example thresholds for a "decide whether to split this PR" gate: additions are the trigger for a
3
+ * split, while deletion-heavy retirement work stays coherent at a larger limit. Callers pass their
4
+ * own `LargeDiffThresholds`; these are only a default.
5
+ */
6
+ export const DEFAULT_LARGE_DIFF_THRESHOLDS = Object.freeze({
7
+ addedLines: 5000,
8
+ deletedLines: 20_000,
9
+ });
10
+ /**
11
+ * Added + removed lines in a unified diff (`git diff <base>...HEAD` output), excluding the
12
+ * `+++`/`---` file-header lines so renamed/moved files don't double-count their path lines.
13
+ *
14
+ * Known limitation: this matches the literal `+++`/`---` prefix, not "is this actually a file
15
+ * header." An added line whose content starts with `++`, or a removed line whose content starts
16
+ * with `--` (a SQL comment, a CLI flag), renders as `+++`/`---` once the diff marker is prepended
17
+ * and is silently excluded from the count. For a coarse "probably needs a split" heuristic with an
18
+ * explicit override, that undercount is acceptable; this is not a full hunk parser.
19
+ */
20
+ export function countDiffLineChanges(diffText) {
21
+ const changes = { added: 0, deleted: 0 };
22
+ for (const line of diffText.split('\n')) {
23
+ if (line.startsWith('+++') || line.startsWith('---'))
24
+ continue;
25
+ if (line.startsWith('+'))
26
+ changes.added++;
27
+ else if (line.startsWith('-'))
28
+ changes.deleted++;
29
+ }
30
+ return changes;
31
+ }
32
+ /** True when either count is strictly greater than its threshold. */
33
+ export function exceedsLargeDiffThreshold(changes, thresholds = DEFAULT_LARGE_DIFF_THRESHOLDS) {
34
+ return changes.added > thresholds.addedLines || changes.deleted > thresholds.deletedLines;
35
+ }
@@ -0,0 +1,2 @@
1
+ export { countDiffLineChanges, DEFAULT_LARGE_DIFF_THRESHOLDS, exceedsLargeDiffThreshold, } from './diff-size.mts';
2
+ export type { DiffLineChanges, LargeDiffThresholds } from './diff-size.mts';
@@ -0,0 +1 @@
1
+ export { countDiffLineChanges, DEFAULT_LARGE_DIFF_THRESHOLDS, exceedsLargeDiffThreshold, } from "./diff-size.mjs";
@@ -0,0 +1,13 @@
1
+ export type DistillClassifyArgs = {
2
+ partitionPath: string;
3
+ retroCutoff: string;
4
+ sessionCutoff: string;
5
+ };
6
+ /**
7
+ * Parses `<partition.jsonl> --retro-cutoff <iso> --session-cutoff <iso>`.
8
+ *
9
+ * The classifier never computes its own cutoffs: the eligibility rule is evaluated against
10
+ * whatever `retroCutoff`/`sessionCutoff` the caller passes in, so a classifier run always matches
11
+ * the caller's own idea of "eligible" for that same run.
12
+ */
13
+ export declare function parseDistillClassifyArgs(argv: string[]): DistillClassifyArgs;