delivery-friction-analyzer 0.3.0 → 0.4.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.
|
@@ -53,3 +53,29 @@ If both matchers are present on one rule, both must match. If no rule matches, t
|
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
Class identifiers are validated as lower-kebab-case or lower_snake_case strings. Profile validation rejects duplicate PR class rule IDs, empty match objects, invalid class identifiers, and invalid title regexes.
|
|
56
|
+
|
|
57
|
+
## Workflow Context
|
|
58
|
+
|
|
59
|
+
`workflow` is optional user-configured context. It records repository workflow assumptions that later setup and report milestones can rely on, but M2 does not infer these values from GitHub and does not change scoring, rankings, collection, PR class matching, or report wording.
|
|
60
|
+
|
|
61
|
+
When provided, `workflow` must include at least one supported field.
|
|
62
|
+
|
|
63
|
+
Supported fields:
|
|
64
|
+
|
|
65
|
+
- `primaryMergeMethod`: `merge_commit`, `squash_merge`, `rebase_merge`, `mixed`, or `unknown`.
|
|
66
|
+
- `releaseStrategy`: `release_prs`, `direct_tags`, `release_branches`, `mixed`, or `unknown`.
|
|
67
|
+
- `branchStrategy`: `trunk_based`, `main_plus_release_branches`, `long_lived_development_branches`, `mixed`, or `unknown`.
|
|
68
|
+
|
|
69
|
+
Example:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"workflow": {
|
|
74
|
+
"primaryMergeMethod": "squash_merge",
|
|
75
|
+
"releaseStrategy": "release_prs",
|
|
76
|
+
"branchStrategy": "main_plus_release_branches"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Use stable identifiers exactly as shown above. Display labels such as "squash merges" or "release PRs" belong in CLI prompts or documentation, not in profile data.
|
package/package.json
CHANGED
package/release-log.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
### 2026-06-17 — Workflow Profile Contract
|
|
6
|
+
|
|
7
|
+
- What changed: Repository profiles can now declare optional workflow context for merge method, release strategy, and branch strategy using validated stable identifiers.
|
|
8
|
+
- Why it matters: Future interactive setup and report milestones can rely on a documented profile contract without inferring workflow assumptions from GitHub history or changing scoring.
|
|
9
|
+
- Who is affected: Maintainers authoring or validating repository profiles.
|
|
10
|
+
- Action needed: Add `workflow` context only when you want to record repository workflow assumptions; existing profiles remain valid without it.
|
|
11
|
+
- PR: https://github.com/hannasdev/delivery-friction-analyzer/pull/35
|
|
12
|
+
|
|
5
13
|
### 2026-06-17 — Opt-In Interactive CLI Setup
|
|
6
14
|
|
|
7
15
|
- What changed: GitHub analysis now supports `--interactive` to prompt for existing run options such as repository, PR limit, profile path, output directory, dry-run mode, CSV exports, JSON completion output, and configured PR class exclusions.
|
|
@@ -87,6 +87,22 @@
|
|
|
87
87
|
"notes": { "type": "string" }
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
},
|
|
91
|
+
"workflow": {
|
|
92
|
+
"type": "object",
|
|
93
|
+
"additionalProperties": false,
|
|
94
|
+
"properties": {
|
|
95
|
+
"primaryMergeMethod": {
|
|
96
|
+
"enum": ["merge_commit", "squash_merge", "rebase_merge", "mixed", "unknown"]
|
|
97
|
+
},
|
|
98
|
+
"releaseStrategy": {
|
|
99
|
+
"enum": ["release_prs", "direct_tags", "release_branches", "mixed", "unknown"]
|
|
100
|
+
},
|
|
101
|
+
"branchStrategy": {
|
|
102
|
+
"enum": ["trunk_based", "main_plus_release_branches", "long_lived_development_branches", "mixed", "unknown"]
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"minProperties": 1
|
|
90
106
|
}
|
|
91
107
|
}
|
|
92
108
|
}
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
renderRepositoryFrictionMarkdown,
|
|
18
18
|
} from "../report/friction-report.js";
|
|
19
19
|
import { assertValidPrClassRules } from "../profile/pr-class.js";
|
|
20
|
+
import { assertValidWorkflowContext } from "../profile/workflow.js";
|
|
20
21
|
|
|
21
22
|
const ALLOWED_OPTIONS = new Set([
|
|
22
23
|
"repo",
|
|
@@ -194,6 +195,7 @@ async function readProfile(profilePath) {
|
|
|
194
195
|
}
|
|
195
196
|
try {
|
|
196
197
|
assertValidPrClassRules(profile);
|
|
198
|
+
assertValidWorkflowContext(profile);
|
|
197
199
|
} catch (error) {
|
|
198
200
|
throw new Error(`profile is invalid: ${error.message}`);
|
|
199
201
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { classifyCommentSource, groupByCommentSource } from "../github/comment-source.js";
|
|
2
2
|
import { classifyFilePath } from "../profile/file-role.js";
|
|
3
3
|
import { assertValidPrClassRules, classifyPullRequest } from "../profile/pr-class.js";
|
|
4
|
+
import { assertValidWorkflowContext } from "../profile/workflow.js";
|
|
4
5
|
|
|
5
6
|
function minDate(values) {
|
|
6
7
|
return values.filter(Boolean).sort()[0] ?? null;
|
|
@@ -112,6 +113,7 @@ function normalizeCommit(commit) {
|
|
|
112
113
|
export function normalizeFixtureBundle(bundle, { repositoryProfile } = {}) {
|
|
113
114
|
const profile = repositoryProfile ?? {};
|
|
114
115
|
assertValidPrClassRules(profile);
|
|
116
|
+
assertValidWorkflowContext(profile);
|
|
115
117
|
|
|
116
118
|
const pullRequests = (bundle.pullRequests ?? []).map(pr => {
|
|
117
119
|
const reviewDates = (pr.reviews ?? []).map(review => review.submittedAt);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export const WORKFLOW_PRIMARY_MERGE_METHODS = Object.freeze([
|
|
2
|
+
"merge_commit",
|
|
3
|
+
"squash_merge",
|
|
4
|
+
"rebase_merge",
|
|
5
|
+
"mixed",
|
|
6
|
+
"unknown",
|
|
7
|
+
]);
|
|
8
|
+
|
|
9
|
+
export const WORKFLOW_RELEASE_STRATEGIES = Object.freeze([
|
|
10
|
+
"release_prs",
|
|
11
|
+
"direct_tags",
|
|
12
|
+
"release_branches",
|
|
13
|
+
"mixed",
|
|
14
|
+
"unknown",
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
export const WORKFLOW_BRANCH_STRATEGIES = Object.freeze([
|
|
18
|
+
"trunk_based",
|
|
19
|
+
"main_plus_release_branches",
|
|
20
|
+
"long_lived_development_branches",
|
|
21
|
+
"mixed",
|
|
22
|
+
"unknown",
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
const WORKFLOW_FIELDS = Object.freeze({
|
|
26
|
+
primaryMergeMethod: WORKFLOW_PRIMARY_MERGE_METHODS,
|
|
27
|
+
releaseStrategy: WORKFLOW_RELEASE_STRATEGIES,
|
|
28
|
+
branchStrategy: WORKFLOW_BRANCH_STRATEGIES,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
function allowedValues(values) {
|
|
32
|
+
return values.join(", ");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function validateWorkflowContext(profile = {}) {
|
|
36
|
+
const errors = [];
|
|
37
|
+
if (!profile || typeof profile !== "object" || Array.isArray(profile)) {
|
|
38
|
+
return errors;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!Object.prototype.hasOwnProperty.call(profile, "workflow")) {
|
|
42
|
+
return errors;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const workflow = profile.workflow;
|
|
46
|
+
if (!workflow || typeof workflow !== "object" || Array.isArray(workflow)) {
|
|
47
|
+
return ["workflow must be an object when provided"];
|
|
48
|
+
}
|
|
49
|
+
if (Object.keys(workflow).length === 0) {
|
|
50
|
+
return ["workflow must include at least one field when provided"];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const key of Object.keys(workflow)) {
|
|
54
|
+
if (!Object.prototype.hasOwnProperty.call(WORKFLOW_FIELDS, key)) {
|
|
55
|
+
errors.push(`workflow.${key} is not supported`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (const [field, values] of Object.entries(WORKFLOW_FIELDS)) {
|
|
60
|
+
const value = workflow[field];
|
|
61
|
+
if (value === undefined) continue;
|
|
62
|
+
if (!values.includes(value)) {
|
|
63
|
+
errors.push(`workflow.${field} must be one of: ${allowedValues(values)}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return errors;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function assertValidWorkflowContext(profile = {}) {
|
|
71
|
+
const errors = validateWorkflowContext(profile);
|
|
72
|
+
if (errors.length > 0) {
|
|
73
|
+
throw new Error(`invalid workflow profile context: ${errors.join("; ")}`);
|
|
74
|
+
}
|
|
75
|
+
}
|