delivery-friction-analyzer 0.27.0 → 0.28.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/package.json +1 -1
- package/release-log.md +8 -0
- package/src/cli/analyze-github.js +81 -2
package/package.json
CHANGED
package/release-log.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
### 2026-07-05 — Dry-Run Short-Sample Explanations
|
|
6
|
+
|
|
7
|
+
- What changed: Live dry-run completion now explains why fewer pull requests were sampled than requested when the analyzer has enough evidence, or says the reason is unknown when it cannot prove the cause.
|
|
8
|
+
- Why it matters: First-time users and maintainers can interpret short coverage probes without assuming the repository has too few eligible PRs, filters were applied, or GitHub data was complete.
|
|
9
|
+
- Who is affected: Users running live GitHub `--dry-run` or `--metadata-only` checks, especially with requested limits larger than the collected sample.
|
|
10
|
+
- Action needed: None.
|
|
11
|
+
- PR: https://github.com/hannasdev/delivery-friction-analyzer/pull/75
|
|
12
|
+
|
|
5
13
|
### 2026-07-04 — Guided Setup Guard Clarity
|
|
6
14
|
|
|
7
15
|
- What changed: Interactive setup now stops as soon as users enter this tool's product repository, explains that no GitHub data was collected and no files were written, and keeps Conventional Commit PR class setup as a short prompt with detailed `?` help.
|
|
@@ -1868,7 +1868,7 @@ export async function runAnalyzeGithub(options, {
|
|
|
1868
1868
|
paths: generatedPaths,
|
|
1869
1869
|
sourceBundle,
|
|
1870
1870
|
requestedLimit: options.limit,
|
|
1871
|
-
sampledLimit: collectionLimit,
|
|
1871
|
+
sampledLimit: sourceBundle.selection?.collectedCount ?? collectionLimit,
|
|
1872
1872
|
csv: false,
|
|
1873
1873
|
analysisFilter: requestedAnalysisFilter(options.excludedPrClasses ?? []),
|
|
1874
1874
|
savedProfilePath: options.savedProfilePath,
|
|
@@ -1996,6 +1996,81 @@ function coverageCaveats(coverage) {
|
|
|
1996
1996
|
});
|
|
1997
1997
|
}
|
|
1998
1998
|
|
|
1999
|
+
function dryRunCollectedCount(result) {
|
|
2000
|
+
const count = result.selection?.collectedCount;
|
|
2001
|
+
return Number.isInteger(count) ? count : result.sampledLimit;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
function relevantShortSampleCoverageStatus(coverage) {
|
|
2005
|
+
const relevantFamilies = new Set([
|
|
2006
|
+
"pull_request_inventory",
|
|
2007
|
+
"pull_request_details",
|
|
2008
|
+
"review_threads",
|
|
2009
|
+
"workflow_runs",
|
|
2010
|
+
]);
|
|
2011
|
+
const statuses = (coverage?.sourceFamilies ?? []).filter(family => (
|
|
2012
|
+
relevantFamilies.has(family.family)
|
|
2013
|
+
&& family.status
|
|
2014
|
+
&& family.status !== "available"
|
|
2015
|
+
)).map(family => family.status);
|
|
2016
|
+
|
|
2017
|
+
if (!statuses.length) return null;
|
|
2018
|
+
if (statuses.includes("partial") && statuses.includes("unavailable")) return "incomplete";
|
|
2019
|
+
if (statuses.includes("partial")) return "partial";
|
|
2020
|
+
if (statuses.includes("unavailable")) return "unavailable";
|
|
2021
|
+
return "incomplete";
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
function pullRequestCountLabel(count) {
|
|
2025
|
+
return count === 1 ? "one pull request" : `${count} pull requests`;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
function dryRunShortSampleExplanation(result) {
|
|
2029
|
+
if (!result.dryRun) return null;
|
|
2030
|
+
const requestedCount = result.requestedLimit;
|
|
2031
|
+
const collectedCount = dryRunCollectedCount(result);
|
|
2032
|
+
if (!Number.isInteger(requestedCount)
|
|
2033
|
+
|| !Number.isInteger(collectedCount)
|
|
2034
|
+
|| collectedCount >= requestedCount) {
|
|
2035
|
+
return null;
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
if (Number.isInteger(result.analysisFilter?.originalPullRequests)
|
|
2039
|
+
&& Number.isInteger(result.analysisFilter?.filteredPullRequests)
|
|
2040
|
+
&& result.analysisFilter.originalPullRequests >= requestedCount
|
|
2041
|
+
&& result.analysisFilter.filteredPullRequests < requestedCount) {
|
|
2042
|
+
return `Sampled ${collectedCount} of ${requestedCount} requested after applying the requested PR class filter.`;
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
if (Number.isInteger(result.selection?.requestedLimit)
|
|
2046
|
+
&& Number.isInteger(result.selection?.collectedCount)
|
|
2047
|
+
&& result.selection.collectedCount < result.selection.requestedLimit) {
|
|
2048
|
+
return `Sampled ${collectedCount} of ${requestedCount} requested because GitHub returned ${result.selection.collectedCount} merged pull request(s) for this query.`;
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
if (Number.isInteger(result.selection?.requestedLimit)
|
|
2052
|
+
&& result.selection.requestedLimit < requestedCount
|
|
2053
|
+
&& collectedCount >= result.selection.requestedLimit) {
|
|
2054
|
+
return `Sampled ${collectedCount} of ${requestedCount} requested because dry-run mode intentionally checks ${pullRequestCountLabel(result.selection.requestedLimit)} to verify GitHub access and coverage without writing artifacts.`;
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
const relevantCoverageStatus = relevantShortSampleCoverageStatus(result.collectionCoverage);
|
|
2058
|
+
if (relevantCoverageStatus === "partial") {
|
|
2059
|
+
return `Sampled ${collectedCount} of ${requestedCount} requested. Collection coverage was partial; check the coverage details before treating the sample as complete.`;
|
|
2060
|
+
}
|
|
2061
|
+
if (relevantCoverageStatus === "unavailable") {
|
|
2062
|
+
return `Sampled ${collectedCount} of ${requestedCount} requested. Collection coverage was unavailable for one or more relevant sources; check the coverage details before treating the sample as complete.`;
|
|
2063
|
+
}
|
|
2064
|
+
if (relevantCoverageStatus === "incomplete") {
|
|
2065
|
+
return `Sampled ${collectedCount} of ${requestedCount} requested. Collection coverage was incomplete; check the coverage details before treating the sample as complete.`;
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
const eligiblePullRequests = collectedCount === 1
|
|
2069
|
+
? "one eligible pull request"
|
|
2070
|
+
: `${collectedCount} eligible pull requests`;
|
|
2071
|
+
return `Sampled ${collectedCount} of ${requestedCount} requested. The analyzer could not determine the reason from available dry-run evidence; check coverage details before assuming the repository has only ${eligiblePullRequests}.`;
|
|
2072
|
+
}
|
|
2073
|
+
|
|
1999
2074
|
export function formatAnalyzeGithubCompletion(result) {
|
|
2000
2075
|
const target = result.targetRepository?.owner && result.targetRepository?.name
|
|
2001
2076
|
? `${result.targetRepository.owner}/${result.targetRepository.name}`
|
|
@@ -2005,9 +2080,13 @@ export function formatAnalyzeGithubCompletion(result) {
|
|
|
2005
2080
|
if (result.dryRun) {
|
|
2006
2081
|
lines.push(
|
|
2007
2082
|
`Dry run complete for ${target}.`,
|
|
2008
|
-
`Sampled pull requests: ${result
|
|
2083
|
+
`Sampled pull requests: ${dryRunCollectedCount(result)} of ${result.requestedLimit} requested.`,
|
|
2009
2084
|
"Artifacts: not written.",
|
|
2010
2085
|
);
|
|
2086
|
+
const shortSampleExplanation = dryRunShortSampleExplanation(result);
|
|
2087
|
+
if (shortSampleExplanation) {
|
|
2088
|
+
lines.push(shortSampleExplanation);
|
|
2089
|
+
}
|
|
2011
2090
|
} else {
|
|
2012
2091
|
const paths = result.artifactPaths ?? {};
|
|
2013
2092
|
lines.push(
|