delivery-friction-analyzer 0.9.0 → 0.10.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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Friction Report Contract
|
|
2
2
|
|
|
3
|
-
Milestone 3 introduced `friction-report.v1`, a deterministic report generated from a `friction-metrics.v1` repository metrics summary.
|
|
3
|
+
Milestone 3 introduced `friction-report.v1`, a deterministic report generated from a `friction-metrics.v1` repository metrics summary. Milestones 4 and 5 add Markdown and methodology profile suggestions without adding report JSON fields. The report layer does not fetch GitHub data, mutate repositories, rank individuals, or depend on services beyond the data collection path that produced the metrics summary.
|
|
4
4
|
|
|
5
5
|
## Outputs
|
|
6
6
|
|
|
@@ -57,10 +57,11 @@ The Markdown renderer presents the same report data for human review:
|
|
|
57
57
|
- a compact recommendation-category snapshot before detailed bottlenecks, with the full category reference retained later in the report;
|
|
58
58
|
- a short "How To Read This Report" guide that distinguishes observed evidence, interpretation, recommendations, and caveats;
|
|
59
59
|
- a configured workflow context section only when repository profile workflow fields are present, labeled as user-configured profile context rather than observed GitHub evidence;
|
|
60
|
+
- workflow data caveats when configured workflow context clarifies unavailable PR-open diff or workflow-run evidence;
|
|
60
61
|
- evidence-quality and coverage tables before detailed recommendations;
|
|
61
62
|
- key findings that highlight top bottlenecks, strongest displayed signal, outlier caveats, PR class caveats, and coverage caveats;
|
|
62
63
|
- a PR class context table that shows analyzed PR counts, changed lines, sample share, and classification sources by class;
|
|
63
|
-
- profile suggestions when fallback `unknown` PR classes
|
|
64
|
+
- profile suggestions when fallback `unknown` PR classes, unknown file role/surface evidence, or omitted workflow context with relevant unavailable coverage cross deterministic thresholds;
|
|
64
65
|
- a top-level shared-signal interpretation callout when multiple displayed bottlenecks share a ranking key or representative PR evidence;
|
|
65
66
|
- outlier and sensitivity analysis when displayed examples are dominated by one PR;
|
|
66
67
|
- a prioritization explanation that describes strongest-signal ordering and how PR size is used as context, using reader-facing change-scope language while mapping back to the internal changed-file-spread signal when needed;
|
|
@@ -78,6 +79,8 @@ Markdown output should not include individual contributor or reviewer rankings.
|
|
|
78
79
|
Status labels are Markdown presentation helpers, not `friction-report.v1` fields. They should preserve the underlying source labels and counts rather than replacing auditable evidence.
|
|
79
80
|
Profile suggestions are also presentation helpers, not `friction-report.v1` fields. They are derived from existing PR class and file-surface evidence, appear at most once per suggestion category, and do not change scores, rankings, CSV exports, filtering, or PR class matching. Because the report JSON does not carry repository-profile rule inventory, all analyzed PRs using fallback `unknown` PR class evidence is the renderer's small-sample proxy for no configured PR class rule producing usable classification evidence.
|
|
80
81
|
|
|
82
|
+
Workflow-context suggestions are presentation helpers, not `friction-report.v1` fields. They render when workflow context is omitted and the report has unavailable PR-open diff coverage or workflow-run coverage that maintainer-confirmed workflow context could help explain. They are omitted when workflow context is configured or when those coverage caveats are absent.
|
|
83
|
+
|
|
81
84
|
## Recommendation Boundaries
|
|
82
85
|
|
|
83
86
|
Recommendations are inferred from transparent component metrics and representative PR examples. They suggest workflow interventions such as readiness gates, preflight scripts, smaller milestones, planning artifacts, or scope control. They do not automate repository changes.
|
|
@@ -94,7 +97,7 @@ The M3 report contract supports these recommendation categories:
|
|
|
94
97
|
|
|
95
98
|
## Coverage And Confidence
|
|
96
99
|
|
|
97
|
-
Reports must label unavailable or partial GitHub data instead of inferring unavailable values from merge-time data. PR-open diff growth remains unavailable unless
|
|
100
|
+
Reports must label unavailable or partial GitHub data instead of inferring unavailable values from merge-time data. Final/current PR metadata can come from GitHub PR data, but PR-open diff growth remains unavailable unless an open-time snapshot or equivalent captured state exists. Workflow coverage and review-thread sources are summarized separately.
|
|
98
101
|
|
|
99
102
|
Representative examples should carry enough source evidence to trace a report claim back to generated artifacts. Validation examples should name the workflow-run source and conclusions. Review churn examples should name the review-thread source, review decision evidence, and comment sources. PR class evidence should be visible in representative bottleneck examples so readers can distinguish workflow populations such as release, dependency, development, or repository-specific classes. When `reviewThreads` is zero, review decision evidence should make clean human approval distinguishable from unavailable review evidence and from observed absence of human review. When displayed examples are dominated by one PR or one PR class, the report should say so instead of implying a repository-wide pattern from an outlier or workflow population.
|
|
100
103
|
|
|
@@ -111,7 +114,7 @@ Full live analysis writes `methodology.md` as a hybrid artifact: stable explanat
|
|
|
111
114
|
- target repository and report/metric versions;
|
|
112
115
|
- profile path when available;
|
|
113
116
|
- configured workflow context when supplied by the repository profile, labeled as user-configured context rather than observed GitHub evidence;
|
|
114
|
-
- profile suggestions when PR class
|
|
117
|
+
- profile suggestions when PR class, file/path, or workflow-context profile evidence crosses deterministic fallback thresholds, or an explicit no-threshold note when none were triggered;
|
|
115
118
|
- requested and collected PR counts;
|
|
116
119
|
- collection coverage status and API-family diagnostics;
|
|
117
120
|
- scoring, ranking, dominance, sensitivity, and limitation explanations;
|
package/package.json
CHANGED
package/release-log.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
### 2026-06-20 — Workflow Data Caveats
|
|
6
|
+
|
|
7
|
+
- What changed: Markdown friction reports and methodology now explain PR-open diff and workflow-run coverage limits with configured workflow context when it is available, and suggest adding workflow context when omitted context would clarify unavailable evidence.
|
|
8
|
+
- Why it matters: Maintainers can distinguish final GitHub PR metadata from unreconstructable open-time PR size without mistaking merge strategy for observed evidence or a scoring input.
|
|
9
|
+
- Who is affected: Maintainers and contributors reviewing generated reports or authoring repository profiles with workflow context.
|
|
10
|
+
- Action needed: Optional; add repository-profile workflow context when unavailable coverage would be easier to interpret with maintainer-confirmed merge or branch assumptions.
|
|
11
|
+
- PR: https://github.com/hannasdev/delivery-friction-analyzer/pull/46
|
|
12
|
+
|
|
5
13
|
### 2026-06-20 — Profile Improvement Suggestions
|
|
6
14
|
|
|
7
15
|
- What changed: Markdown friction reports and methodology now suggest PR class or file/path profile improvements when fallback `unknown` evidence dominates the analyzed sample.
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
configuredWorkflowEntries,
|
|
4
4
|
hasConfiguredWorkflowContext,
|
|
5
5
|
profileSuggestions,
|
|
6
|
+
workflowDataCaveats,
|
|
6
7
|
} from "./friction-report.js";
|
|
7
8
|
|
|
8
9
|
const BOT_OR_SCANNER_SOURCES = new Set([
|
|
@@ -346,6 +347,7 @@ function formatSensitivitySummaries(report) {
|
|
|
346
347
|
function formatConfiguredWorkflowContext(report) {
|
|
347
348
|
const configuredWorkflow = report.configuredWorkflow;
|
|
348
349
|
if (!hasConfiguredWorkflowContext(configuredWorkflow)) return [];
|
|
350
|
+
const caveats = workflowDataCaveats(report);
|
|
349
351
|
|
|
350
352
|
return [
|
|
351
353
|
"## Configured Workflow Context",
|
|
@@ -354,6 +356,14 @@ function formatConfiguredWorkflowContext(report) {
|
|
|
354
356
|
"",
|
|
355
357
|
...configuredWorkflowEntries(configuredWorkflow)
|
|
356
358
|
.map(entry => `- ${entry.label}: ${entry.valueLabel}`),
|
|
359
|
+
...(caveats.length
|
|
360
|
+
? [
|
|
361
|
+
"",
|
|
362
|
+
"Workflow data caveats:",
|
|
363
|
+
"",
|
|
364
|
+
...caveats.map(caveat => `- ${caveat}`),
|
|
365
|
+
]
|
|
366
|
+
: []),
|
|
357
367
|
"",
|
|
358
368
|
];
|
|
359
369
|
}
|
|
@@ -364,7 +374,7 @@ function formatProfileSuggestions(report) {
|
|
|
364
374
|
return [
|
|
365
375
|
"## Profile Suggestions",
|
|
366
376
|
"",
|
|
367
|
-
"- No profile suggestion thresholds were triggered by this report's PR class, role,
|
|
377
|
+
"- No profile suggestion thresholds were triggered by this report's PR class, role, functional-surface, or workflow-coverage evidence.",
|
|
368
378
|
"",
|
|
369
379
|
];
|
|
370
380
|
}
|
|
@@ -83,6 +83,8 @@ const WORKFLOW_CONTEXT_VALUE_LABELS = new Map([
|
|
|
83
83
|
|
|
84
84
|
export const CONFIGURED_WORKFLOW_NOTE = "Configured workflow context comes from the repository profile. It is user-configured context, not observed GitHub evidence, and it does not change scores, rankings, CSV exports, or PR class matching.";
|
|
85
85
|
|
|
86
|
+
const PR_OPEN_DIFF_LIMITATION_NOTE = "PR-open diff growth is unavailable for PRs without an open-time snapshot or equivalent captured state; final/current PR metadata can still come from GitHub PR data, but open-time size is not reconstructed from merge-time data.";
|
|
87
|
+
|
|
86
88
|
const PROFILE_SUGGESTION_THRESHOLDS = {
|
|
87
89
|
minimumPrClassSample: 3,
|
|
88
90
|
unknownPrClassShare: 0.8,
|
|
@@ -421,9 +423,7 @@ function summarizeCoverage(metricsSummary) {
|
|
|
421
423
|
|
|
422
424
|
const notes = [];
|
|
423
425
|
if (prOpenDiff.unavailable) {
|
|
424
|
-
notes.push(
|
|
425
|
-
"PR-open diff growth is unavailable for PRs without captured or reconstructed open-time snapshots; it is not inferred from merge-time data.",
|
|
426
|
-
);
|
|
426
|
+
notes.push(PR_OPEN_DIFF_LIMITATION_NOTE);
|
|
427
427
|
}
|
|
428
428
|
if (workflowRuns.unavailable) {
|
|
429
429
|
notes.push("Workflow-run coverage is unavailable for some PRs, often because branch-based history is missing.");
|
|
@@ -643,6 +643,26 @@ export function profileSuggestions(report = {}) {
|
|
|
643
643
|
});
|
|
644
644
|
}
|
|
645
645
|
|
|
646
|
+
const hasWorkflowContext = hasConfiguredWorkflowContext(report.configuredWorkflow);
|
|
647
|
+
const unavailablePrOpenDiff = Number(report.coverage?.prOpenDiff?.unavailable ?? 0);
|
|
648
|
+
const unavailableWorkflowRuns = Number(report.coverage?.workflowRuns?.unavailable ?? 0);
|
|
649
|
+
if (!hasWorkflowContext && (unavailablePrOpenDiff > 0 || unavailableWorkflowRuns > 0)) {
|
|
650
|
+
const evidence = [
|
|
651
|
+
unavailablePrOpenDiff > 0
|
|
652
|
+
? `PR-open diff coverage unavailable for ${formatCount(unavailablePrOpenDiff, "PR")}`
|
|
653
|
+
: null,
|
|
654
|
+
unavailableWorkflowRuns > 0
|
|
655
|
+
? `workflow-run coverage unavailable for ${formatCount(unavailableWorkflowRuns, "PR")}`
|
|
656
|
+
: null,
|
|
657
|
+
].filter(Boolean).join("; ");
|
|
658
|
+
suggestions.push({
|
|
659
|
+
id: "workflow-context",
|
|
660
|
+
area: "Workflow context",
|
|
661
|
+
evidence: `${evidence}.`,
|
|
662
|
+
suggestion: "Configure repository-profile workflow context, such as primary merge method or branch strategy, so unavailable diff-growth or workflow-run evidence is interpreted with maintainer-confirmed context instead of guesses.",
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
|
|
646
666
|
return suggestions;
|
|
647
667
|
}
|
|
648
668
|
|
|
@@ -682,6 +702,39 @@ export function hasConfiguredWorkflowContext(configuredWorkflow) {
|
|
|
682
702
|
return configuredWorkflowEntries(configuredWorkflow).length > 0;
|
|
683
703
|
}
|
|
684
704
|
|
|
705
|
+
function configuredWorkflowEntry(configuredWorkflow, field) {
|
|
706
|
+
const entry = configuredWorkflowEntries(configuredWorkflow).find(candidate => candidate.field === field);
|
|
707
|
+
return entry ?? null;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
export function workflowDataCaveats(report = {}) {
|
|
711
|
+
if (!hasConfiguredWorkflowContext(report.configuredWorkflow)) return [];
|
|
712
|
+
|
|
713
|
+
const unavailablePrOpenDiff = Number(report.coverage?.prOpenDiff?.unavailable ?? 0);
|
|
714
|
+
const unavailableWorkflowRuns = Number(report.coverage?.workflowRuns?.unavailable ?? 0);
|
|
715
|
+
if (unavailablePrOpenDiff <= 0 && unavailableWorkflowRuns <= 0) return [];
|
|
716
|
+
|
|
717
|
+
const mergeMethod = configuredWorkflowEntry(report.configuredWorkflow, "primaryMergeMethod");
|
|
718
|
+
const caveats = [];
|
|
719
|
+
if (unavailablePrOpenDiff > 0) {
|
|
720
|
+
const prefix = mergeMethod
|
|
721
|
+
? `Profile context says primary merge method is ${mergeMethod.valueLabel}; this is configured profile context, not observed evidence.`
|
|
722
|
+
: "Configured workflow fields are profile context, not observed evidence.";
|
|
723
|
+
const methodLimit = {
|
|
724
|
+
squash_merge: "Squash merge keeps final PR metadata available through GitHub PR data, but it does not preserve the original branch commit topology on the base branch.",
|
|
725
|
+
rebase_merge: "Rebase merge keeps final PR metadata available through GitHub PR data, but rebased commits do not provide a reliable open-time diff snapshot from base-branch history.",
|
|
726
|
+
merge_commit: "Merge commits can preserve a merge boundary, but this analyzer still uses GitHub PR data for final/current PR metadata and does not reconstruct PR-open size from merge commits or branch history.",
|
|
727
|
+
}[mergeMethod?.value] ?? "Final/current PR metadata can come from GitHub PR data, but PR-open diff growth still needs captured open-time evidence.";
|
|
728
|
+
caveats.push(`${prefix} ${methodLimit} PR-open diff growth requires an open-time snapshot or equivalent captured state.`);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
if (unavailableWorkflowRuns > 0) {
|
|
732
|
+
caveats.push("Unavailable workflow-run coverage remains a GitHub collection coverage limit; configured workflow context can explain the repository's expected workflow shape, but it is not observed run evidence.");
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
return caveats;
|
|
736
|
+
}
|
|
737
|
+
|
|
685
738
|
function evidenceSignature(bottleneck) {
|
|
686
739
|
return (bottleneck.observedData ?? [])
|
|
687
740
|
.map(evidence => evidence.number)
|
|
@@ -1393,6 +1446,18 @@ function renderConfiguredWorkflowContext(configuredWorkflow) {
|
|
|
1393
1446
|
].join("\n");
|
|
1394
1447
|
}
|
|
1395
1448
|
|
|
1449
|
+
function renderWorkflowDataCaveats(report) {
|
|
1450
|
+
const caveats = workflowDataCaveats(report);
|
|
1451
|
+
if (!caveats.length) return "";
|
|
1452
|
+
|
|
1453
|
+
return [
|
|
1454
|
+
"## Workflow Data Caveats",
|
|
1455
|
+
"",
|
|
1456
|
+
renderList(caveats),
|
|
1457
|
+
"",
|
|
1458
|
+
].join("\n");
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1396
1461
|
function classDominanceCaveat(bottleneck) {
|
|
1397
1462
|
return bottleneck.classDominance?.status === "single_class_dominates"
|
|
1398
1463
|
? bottleneck.classDominance.note
|
|
@@ -1547,6 +1612,9 @@ export function renderRepositoryFrictionMarkdown(report) {
|
|
|
1547
1612
|
...(hasConfiguredWorkflowContext(report.configuredWorkflow)
|
|
1548
1613
|
? [renderConfiguredWorkflowContext(report.configuredWorkflow)]
|
|
1549
1614
|
: []),
|
|
1615
|
+
...(workflowDataCaveats(report).length
|
|
1616
|
+
? [renderWorkflowDataCaveats(report)]
|
|
1617
|
+
: []),
|
|
1550
1618
|
"## Evidence Quality And Coverage",
|
|
1551
1619
|
"",
|
|
1552
1620
|
renderCoverageSummary(report.coverage),
|
|
@@ -1617,13 +1685,16 @@ export function renderRepositoryFrictionMarkdown(report) {
|
|
|
1617
1685
|
"- File roles and functional surfaces come from repository-profile classification, not from language names alone.",
|
|
1618
1686
|
profileSuggestions(report).length
|
|
1619
1687
|
? "- Profile suggestions are optional interpretation improvements derived from existing report evidence; they do not change scores, rankings, CSV exports, or JSON report fields."
|
|
1620
|
-
: "- No profile suggestion thresholds were triggered by this report's PR class, role,
|
|
1688
|
+
: "- No profile suggestion thresholds were triggered by this report's PR class, role, functional-surface, or workflow-coverage evidence.",
|
|
1621
1689
|
"- Bottlenecks are ranked by their strongest representative observed signal, with stable category order only used to break ties.",
|
|
1622
1690
|
"- Recommendations are inferred from transparent component evidence and representative PR examples; they are not automated changes.",
|
|
1623
1691
|
"- Missing or partial GitHub data remains visible in coverage tables rather than being inferred from unrelated fields.",
|
|
1624
1692
|
...(hasConfiguredWorkflowContext(report.configuredWorkflow)
|
|
1625
1693
|
? ["- Configured workflow context is user-configured repository-profile context; it does not change scoring, ranking, CSV exports, or PR class matching."]
|
|
1626
1694
|
: []),
|
|
1695
|
+
...(workflowDataCaveats(report).length
|
|
1696
|
+
? ["- Workflow data caveats explain unavailable evidence using configured profile context without treating merge method as observed evidence or reconstructing open-time PR size."]
|
|
1697
|
+
: []),
|
|
1627
1698
|
"- Sensitivity analysis, when present, excludes one dominant representative PR at a time to show robustness context without changing the baseline ranking.",
|
|
1628
1699
|
report.analysisFilter?.excludedPrClasses?.length
|
|
1629
1700
|
? "- PR class filtering was explicitly applied before metrics and ranking; PR class context still supports interpretation of the filtered sample."
|