delivery-friction-analyzer 0.5.0 → 0.6.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.
|
@@ -27,6 +27,7 @@ The command reads local `friction-metrics.v1` JSON and writes deterministic `fri
|
|
|
27
27
|
- `metricVersion`: source metrics contract version.
|
|
28
28
|
- `targetRepository`: analyzed repository identity; live analysis sample size is encoded as `targetRepository.analysisPullRequestLimit` from collection metadata.
|
|
29
29
|
- `analysisFilter`: optional metadata for explicit filters applied before metrics computation, including excluded PR classes and before/after PR counts.
|
|
30
|
+
- `configuredWorkflow`: optional user-configured workflow context from the repository profile. It is not observed GitHub evidence and does not change scoring, ranking, CSV exports, or PR class matching.
|
|
30
31
|
- `summary`: repository totals and top bottleneck identifiers.
|
|
31
32
|
- `coverage`: PR-open diff, workflow-run, and review-thread coverage counts plus caveats.
|
|
32
33
|
- `commentSources`: total and source-grouped review comments for Copilot, human, bot, scanner, author replies, and unknown sources.
|
|
@@ -55,6 +56,7 @@ The Markdown renderer presents the same report data for human review:
|
|
|
55
56
|
- a top-of-report focus snapshot that names focus areas, action categories, evidence reviewed, and confidence caveats before detailed bottlenecks;
|
|
56
57
|
- a compact recommendation-category snapshot before detailed bottlenecks, with the full category reference retained later in the report;
|
|
57
58
|
- a short "How To Read This Report" guide that distinguishes observed evidence, interpretation, recommendations, and caveats;
|
|
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;
|
|
58
60
|
- evidence-quality and coverage tables before detailed recommendations;
|
|
59
61
|
- key findings that highlight top bottlenecks, strongest displayed signal, outlier caveats, PR class caveats, and coverage caveats;
|
|
60
62
|
- a PR class context table that shows analyzed PR counts, changed lines, sample share, and classification sources by class;
|
|
@@ -104,6 +106,7 @@ Full live analysis writes `methodology.md` as a hybrid artifact: stable explanat
|
|
|
104
106
|
|
|
105
107
|
- target repository and report/metric versions;
|
|
106
108
|
- profile path when available;
|
|
109
|
+
- configured workflow context when supplied by the repository profile, labeled as user-configured context rather than observed GitHub evidence;
|
|
107
110
|
- requested and collected PR counts;
|
|
108
111
|
- collection coverage status and API-family diagnostics;
|
|
109
112
|
- scoring, ranking, dominance, sensitivity, and limitation explanations;
|
|
@@ -58,7 +58,7 @@ Interactive setup can add a release PR class rule from a confirmed title convent
|
|
|
58
58
|
|
|
59
59
|
## Workflow Context
|
|
60
60
|
|
|
61
|
-
`workflow` is optional user-configured context. It records repository workflow assumptions that
|
|
61
|
+
`workflow` is optional user-configured context. It records repository workflow assumptions that reports can surface as configured profile context, but the analyzer does not infer these values from GitHub and does not change scoring, rankings, collection, CSV exports, or PR class matching.
|
|
62
62
|
|
|
63
63
|
When provided, `workflow` must include at least one supported field.
|
|
64
64
|
|
package/package.json
CHANGED
package/release-log.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
### 2026-06-19 — Workflow Context Surfacing
|
|
6
|
+
|
|
7
|
+
- What changed: Friction reports and methodology now show configured repository workflow context from the profile when it is present.
|
|
8
|
+
- Why it matters: Maintainers can see merge, release, and branch assumptions beside report evidence without mistaking them for observed GitHub data or scoring inputs.
|
|
9
|
+
- Who is affected: Maintainers and contributors reviewing generated reports or authoring repository profiles with `workflow` context.
|
|
10
|
+
- Action needed: None.
|
|
11
|
+
- PR: https://github.com/hannasdev/delivery-friction-analyzer/pull/37
|
|
12
|
+
|
|
5
13
|
### 2026-06-18 — Workflow Profile Wizard
|
|
6
14
|
|
|
7
15
|
- What changed: Interactive setup can now create repository profiles or generated profile copies with confirmed workflow context and release PR title rules.
|
|
@@ -1123,7 +1123,10 @@ export async function runAnalyzeGithub(options, {
|
|
|
1123
1123
|
options.excludedPrClasses ?? [],
|
|
1124
1124
|
);
|
|
1125
1125
|
const metrics = computeRepositoryMetrics(normalized);
|
|
1126
|
-
const report = attachCollectionCoverage(
|
|
1126
|
+
const report = attachCollectionCoverage(
|
|
1127
|
+
generateRepositoryFrictionReport(metrics, { workflowContext: repositoryProfile.workflow }),
|
|
1128
|
+
sourceBundle,
|
|
1129
|
+
);
|
|
1127
1130
|
const markdown = `${renderRepositoryFrictionMarkdown(report)}${collectionCoverageMarkdown(sourceBundle)}`;
|
|
1128
1131
|
const methodology = renderRepositoryFrictionMethodology({
|
|
1129
1132
|
report,
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CONFIGURED_WORKFLOW_NOTE,
|
|
3
|
+
configuredWorkflowEntries,
|
|
4
|
+
hasConfiguredWorkflowContext,
|
|
5
|
+
} from "./friction-report.js";
|
|
6
|
+
|
|
1
7
|
const BOT_OR_SCANNER_SOURCES = new Set([
|
|
2
8
|
"copilot",
|
|
3
9
|
"github_actions_bot",
|
|
@@ -336,6 +342,21 @@ function formatSensitivitySummaries(report) {
|
|
|
336
342
|
}).join("\n");
|
|
337
343
|
}
|
|
338
344
|
|
|
345
|
+
function formatConfiguredWorkflowContext(report) {
|
|
346
|
+
const configuredWorkflow = report.configuredWorkflow;
|
|
347
|
+
if (!hasConfiguredWorkflowContext(configuredWorkflow)) return [];
|
|
348
|
+
|
|
349
|
+
return [
|
|
350
|
+
"## Configured Workflow Context",
|
|
351
|
+
"",
|
|
352
|
+
configuredWorkflow.note ?? CONFIGURED_WORKFLOW_NOTE,
|
|
353
|
+
"",
|
|
354
|
+
...configuredWorkflowEntries(configuredWorkflow)
|
|
355
|
+
.map(entry => `- ${entry.label}: ${entry.valueLabel}`),
|
|
356
|
+
"",
|
|
357
|
+
];
|
|
358
|
+
}
|
|
359
|
+
|
|
339
360
|
export function renderRepositoryFrictionMethodology({
|
|
340
361
|
report,
|
|
341
362
|
sourceBundle,
|
|
@@ -373,6 +394,7 @@ export function renderRepositoryFrictionMethodology({
|
|
|
373
394
|
"",
|
|
374
395
|
"The repository profile maps file paths to categories, roles, and functional surfaces. Those classifications drive non-generated changed-line counts, support-surface summaries, planning-document signals, and low-signal weighting.",
|
|
375
396
|
"",
|
|
397
|
+
...formatConfiguredWorkflowContext(report),
|
|
376
398
|
"## Scores And Rankings",
|
|
377
399
|
"",
|
|
378
400
|
"The report ranks bottlenecks by transparent component metrics from `friction-metrics.v1`: review churn, change scope (the internal changed-file-spread signal: core files touched plus directories touched plus functional surfaces touched), validation gap, planning gap, review surprise, and fix amplification. These are not an opaque composite score, and they are not individual contributor or reviewer rankings.",
|
|
@@ -61,6 +61,28 @@ const RANKING_SIGNAL_LABELS = new Map([
|
|
|
61
61
|
["fixAmplification", "fix amplification"],
|
|
62
62
|
]);
|
|
63
63
|
|
|
64
|
+
const WORKFLOW_CONTEXT_FIELDS = [
|
|
65
|
+
["primaryMergeMethod", "Primary merge method"],
|
|
66
|
+
["releaseStrategy", "Release strategy"],
|
|
67
|
+
["branchStrategy", "Branch strategy"],
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const WORKFLOW_CONTEXT_VALUE_LABELS = new Map([
|
|
71
|
+
["merge_commit", "Merge commit"],
|
|
72
|
+
["squash_merge", "Squash merge"],
|
|
73
|
+
["rebase_merge", "Rebase merge"],
|
|
74
|
+
["release_prs", "Release PRs"],
|
|
75
|
+
["direct_tags", "Direct tags"],
|
|
76
|
+
["release_branches", "Release branches"],
|
|
77
|
+
["trunk_based", "Trunk-based"],
|
|
78
|
+
["main_plus_release_branches", "Main plus release branches"],
|
|
79
|
+
["long_lived_development_branches", "Long-lived development branches"],
|
|
80
|
+
["mixed", "Mixed"],
|
|
81
|
+
["unknown", "Unknown"],
|
|
82
|
+
]);
|
|
83
|
+
|
|
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
|
+
|
|
64
86
|
const BOTTLENECK_DEFINITIONS = [
|
|
65
87
|
{
|
|
66
88
|
id: "review-churn",
|
|
@@ -563,6 +585,42 @@ function summarizeRecommendationCategories(bottlenecks) {
|
|
|
563
585
|
}));
|
|
564
586
|
}
|
|
565
587
|
|
|
588
|
+
export function normalizeConfiguredWorkflowContext(workflowContext) {
|
|
589
|
+
if (!workflowContext || typeof workflowContext !== "object" || Array.isArray(workflowContext)) {
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const configuredWorkflow = {
|
|
594
|
+
source: "repository_profile",
|
|
595
|
+
note: CONFIGURED_WORKFLOW_NOTE,
|
|
596
|
+
};
|
|
597
|
+
for (const [field] of WORKFLOW_CONTEXT_FIELDS) {
|
|
598
|
+
if (workflowContext[field] !== undefined) {
|
|
599
|
+
configuredWorkflow[field] = workflowContext[field];
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return WORKFLOW_CONTEXT_FIELDS.some(([field]) => configuredWorkflow[field] !== undefined)
|
|
604
|
+
? configuredWorkflow
|
|
605
|
+
: null;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
export function configuredWorkflowEntries(configuredWorkflow) {
|
|
609
|
+
if (!configuredWorkflow || typeof configuredWorkflow !== "object") return [];
|
|
610
|
+
return WORKFLOW_CONTEXT_FIELDS
|
|
611
|
+
.filter(([field]) => configuredWorkflow[field] !== undefined)
|
|
612
|
+
.map(([field, label]) => ({
|
|
613
|
+
field,
|
|
614
|
+
label,
|
|
615
|
+
value: configuredWorkflow[field],
|
|
616
|
+
valueLabel: WORKFLOW_CONTEXT_VALUE_LABELS.get(configuredWorkflow[field]) ?? configuredWorkflow[field],
|
|
617
|
+
}));
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
export function hasConfiguredWorkflowContext(configuredWorkflow) {
|
|
621
|
+
return configuredWorkflowEntries(configuredWorkflow).length > 0;
|
|
622
|
+
}
|
|
623
|
+
|
|
566
624
|
function evidenceSignature(bottleneck) {
|
|
567
625
|
return (bottleneck.observedData ?? [])
|
|
568
626
|
.map(evidence => evidence.number)
|
|
@@ -712,16 +770,19 @@ function summarizeSensitivity(metricsSummary, baselineBottlenecks) {
|
|
|
712
770
|
};
|
|
713
771
|
}
|
|
714
772
|
|
|
715
|
-
export function generateRepositoryFrictionReport(metricsSummary) {
|
|
773
|
+
export function generateRepositoryFrictionReport(metricsSummary, options = {}) {
|
|
774
|
+
const { workflowContext } = options ?? {};
|
|
716
775
|
const prClasses = summarizePrClasses(metricsSummary);
|
|
717
776
|
const bottlenecksWithSharedSignalKeys = summarizeBottlenecks(metricsSummary, prClasses);
|
|
718
777
|
const sharedSignals = summarizeSharedSignals(bottlenecksWithSharedSignalKeys);
|
|
719
778
|
const bottlenecks = bottlenecksWithSharedSignalKeys.map(({ rankingKey, ...bottleneck }) => bottleneck);
|
|
779
|
+
const configuredWorkflow = normalizeConfiguredWorkflowContext(workflowContext);
|
|
720
780
|
return {
|
|
721
781
|
reportVersion: FRICTION_REPORT_VERSION,
|
|
722
782
|
metricVersion: metricsSummary.metricVersion,
|
|
723
783
|
targetRepository: metricsSummary.targetRepository,
|
|
724
784
|
...(metricsSummary.analysisFilter ? { analysisFilter: metricsSummary.analysisFilter } : {}),
|
|
785
|
+
...(configuredWorkflow ? { configuredWorkflow } : {}),
|
|
725
786
|
summary: {
|
|
726
787
|
pullRequests: metricsSummary.totals?.pullRequests ?? 0,
|
|
727
788
|
changedLines: metricsSummary.totals?.changedLines ?? 0,
|
|
@@ -1152,6 +1213,23 @@ function renderPrClassContext(prClasses) {
|
|
|
1152
1213
|
].join("\n");
|
|
1153
1214
|
}
|
|
1154
1215
|
|
|
1216
|
+
function renderConfiguredWorkflowContext(configuredWorkflow) {
|
|
1217
|
+
const entries = configuredWorkflowEntries(configuredWorkflow);
|
|
1218
|
+
if (!entries.length) return "";
|
|
1219
|
+
|
|
1220
|
+
return [
|
|
1221
|
+
"## Configured Workflow Context",
|
|
1222
|
+
"",
|
|
1223
|
+
configuredWorkflow.note ?? CONFIGURED_WORKFLOW_NOTE,
|
|
1224
|
+
"",
|
|
1225
|
+
renderMarkdownTable(
|
|
1226
|
+
["Field", "Configured value"],
|
|
1227
|
+
entries.map(entry => [entry.label, entry.valueLabel]),
|
|
1228
|
+
),
|
|
1229
|
+
"",
|
|
1230
|
+
].join("\n");
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1155
1233
|
function classDominanceCaveat(bottleneck) {
|
|
1156
1234
|
return bottleneck.classDominance?.status === "single_class_dominates"
|
|
1157
1235
|
? bottleneck.classDominance.note
|
|
@@ -1299,7 +1377,13 @@ export function renderRepositoryFrictionMarkdown(report) {
|
|
|
1299
1377
|
"- Interpretation is the analyzer's explanation of what the observed evidence suggests.",
|
|
1300
1378
|
"- Recommendation is a workflow intervention to consider; the report does not modify repositories.",
|
|
1301
1379
|
"- Confidence and caveats call out outliers, missing coverage, and evidence-quality limits before you act.",
|
|
1380
|
+
...(hasConfiguredWorkflowContext(report.configuredWorkflow)
|
|
1381
|
+
? ["- Configured workflow context, when shown, comes from the repository profile and is not observed GitHub evidence."]
|
|
1382
|
+
: []),
|
|
1302
1383
|
"",
|
|
1384
|
+
...(hasConfiguredWorkflowContext(report.configuredWorkflow)
|
|
1385
|
+
? [renderConfiguredWorkflowContext(report.configuredWorkflow)]
|
|
1386
|
+
: []),
|
|
1303
1387
|
"## Evidence Quality And Coverage",
|
|
1304
1388
|
"",
|
|
1305
1389
|
renderCoverageSummary(report.coverage),
|
|
@@ -1370,6 +1454,9 @@ export function renderRepositoryFrictionMarkdown(report) {
|
|
|
1370
1454
|
"- Bottlenecks are ranked by their strongest representative observed signal, with stable category order only used to break ties.",
|
|
1371
1455
|
"- Recommendations are inferred from transparent component evidence and representative PR examples; they are not automated changes.",
|
|
1372
1456
|
"- Missing or partial GitHub data remains visible in coverage tables rather than being inferred from unrelated fields.",
|
|
1457
|
+
...(hasConfiguredWorkflowContext(report.configuredWorkflow)
|
|
1458
|
+
? ["- Configured workflow context is user-configured repository-profile context; it does not change scoring, ranking, CSV exports, or PR class matching."]
|
|
1459
|
+
: []),
|
|
1373
1460
|
"- Sensitivity analysis, when present, excludes one dominant representative PR at a time to show robustness context without changing the baseline ranking.",
|
|
1374
1461
|
report.analysisFilter?.excludedPrClasses?.length
|
|
1375
1462
|
? "- PR class filtering was explicitly applied before metrics and ranking; PR class context still supports interpretation of the filtered sample."
|