function getChangedFilePathsInGitSince
getChangedFilePathsInGitSince(
projectRoot: string,
ref: string,
timeoutMs?
): Task<ChangeScan, GitScanFailure>

Resolves the working-tree paths that differ from ref, plus all uncommitted modifications/additions, as absolute paths — or scope: 'everything' when a blast-radius file (package.json, tsconfig*.json, …) changed.

timeoutMs is injectable for tests; production always uses the default.

Returns a lazy, retryable Task: the two git calls run only when the Task is awaited, and a caller can .retry() a transient failure (e.g. index.lock contention) for free — Task.all rebuilds over restarted members, so a retry re-runs this whole chain, git calls included. The git boundary is the only throwing step, so .mapErr remaps any rejection there to a declared GitScanFailed; a parsing bug in .map below is downstream of it and stays a bug, never masked as a Failure. Callers await the value, or .result() for the bare ChangeScan | GitScanFailure union.

import * as Failure from '../result/failure.ts';

// Defined, not invoked: awaiting the Task spawns the git subprocesses.
async function scan(projectRoot: string): Promise<ChangeScan | 'run-all'> {
  const result = await getChangedFilePathsInGitSince(projectRoot, 'main').result();
  return Failure.is(result) ? 'run-all' : result; // GitScanFailed degrades to a full run
}

Parameters

projectRoot: string
ref: string
optional
timeoutMs = GIT_TIMEOUT_MS

Return Type

Usage

import { getChangedFilePathsInGitSince } from "lib/utils/get-changed-file-paths-in-git-since.ts";