delivery-friction-analyzer 0.16.3 → 0.18.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/README.md +3 -3
- package/docs/contracts/friction-report.md +10 -7
- package/docs/contracts/source-bundle.md +85 -15
- package/docs/reference/github-data-inventory.md +1 -1
- package/docs/reference/repository-profile.md +12 -0
- package/examples/tutorial/profile.json +110 -0
- package/examples/tutorial/report-excerpt.md +7 -0
- package/examples/tutorial/source-bundle.json +388 -0
- package/package.json +2 -1
- package/release-log.md +16 -0
- package/schemas/{github-source-bundle.schema.json → source-bundle.schema.json} +31 -17
- package/src/cli/analyze-github.js +10 -3
- package/src/collect/github-source-bundle.js +9 -5
- package/src/report/evidence-artifacts.js +12 -5
- package/src/report/friction-report.js +5 -2
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ The analyzer runs locally with your GitHub credentials. Generated artifacts pres
|
|
|
17
17
|
- GitHub CLI (`gh`) installed and authenticated with access to the target repository.
|
|
18
18
|
- A repository profile JSON for the repository you want to analyze. Interactive setup can create a starter profile for you.
|
|
19
19
|
|
|
20
|
-
For public repositories, ordinary read access is usually enough. Private repositories need a `gh` token with enough read access for the requested
|
|
20
|
+
For public repositories, ordinary read access is usually enough. Private repositories need a `gh` token with enough read access for the requested source families. With a classic PAT, that usually means the `repo` scope. With a fine-grained token or GitHub App, grant read permissions for repository metadata and contents, pull requests, Actions, and checks where available. Missing or partial source coverage is recorded in the generated methodology and coverage artifacts instead of being treated as complete data.
|
|
21
21
|
|
|
22
22
|
## Quickstart
|
|
23
23
|
|
|
@@ -94,7 +94,7 @@ Use these when you want to audit, automate, or build follow-up analysis:
|
|
|
94
94
|
- `normalized.json`: normalized repository, PR, file, review, and validation entities.
|
|
95
95
|
- `source-bundle.json`: collected source data for auditability. Its canonical
|
|
96
96
|
analyzer contract is documented in `docs/contracts/source-bundle.md` and
|
|
97
|
-
checked by `schemas/
|
|
97
|
+
checked by `schemas/source-bundle.schema.json`; it is not a full GitHub
|
|
98
98
|
API payload schema.
|
|
99
99
|
|
|
100
100
|
When CSV exports are enabled, the bundle also includes spreadsheet-friendly evidence files:
|
|
@@ -102,7 +102,7 @@ When CSV exports are enabled, the bundle also includes spreadsheet-friendly evid
|
|
|
102
102
|
- `pr-metrics.csv`: per-PR metrics for spreadsheet review.
|
|
103
103
|
- `bottleneck-examples.csv`: representative bottleneck examples.
|
|
104
104
|
- `comment-sources.csv`: review-comment source breakdowns.
|
|
105
|
-
- `collection-coverage.csv`:
|
|
105
|
+
- `collection-coverage.csv`: source-family coverage diagnostics.
|
|
106
106
|
|
|
107
107
|
Each ranked bottleneck example includes source references, workflow-run conclusions, review-thread source information, comment-source breakdowns, and a dominance note when one PR contributes most of the displayed signal.
|
|
108
108
|
|
|
@@ -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. Milestone 4 added configured workflow context. Milestone 5 adds sanitized contributor-source metadata for configured `.all-contributorsrc` coverage without raw contributor file contents or individual rankings. The report layer does not fetch
|
|
3
|
+
Milestone 3 introduced `friction-report.v1`, a deterministic report generated from a `friction-metrics.v1` repository metrics summary. Milestone 4 added configured workflow context. Milestone 5 adds sanitized contributor-source metadata for configured `.all-contributorsrc` coverage without raw contributor file contents or individual rankings. The report layer does not fetch source data, mutate repositories, rank individuals, or depend on services beyond the source collection path that produced the metrics summary.
|
|
4
4
|
|
|
5
5
|
## Outputs
|
|
6
6
|
|
|
@@ -19,16 +19,19 @@ Local artifact generation is available from an existing metrics summary:
|
|
|
19
19
|
node src/report/generate-report.js --metrics-summary fixtures/github/mcp-writing/metrics-summary.golden.json --json-out fixtures/github/mcp-writing/reports/friction-report.golden.json --markdown-out fixtures/github/mcp-writing/reports/friction-report.golden.md
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
The command reads local `friction-metrics.v1` JSON and writes deterministic `friction-report.v1` JSON and Markdown files. It does not fetch
|
|
22
|
+
The command reads local `friction-metrics.v1` JSON and writes deterministic `friction-report.v1` JSON and Markdown files. It does not fetch source data, mutate the analyzed repository, or write CSV/methodology companion artifacts.
|
|
23
23
|
|
|
24
24
|
## Report Shape
|
|
25
25
|
|
|
26
26
|
- `reportVersion`: report contract version.
|
|
27
27
|
- `metricVersion`: source metrics contract version.
|
|
28
|
+
- `source`: optional source-bundle provenance copied into live-generated report
|
|
29
|
+
artifacts, including `kind` and a human-readable label.
|
|
28
30
|
- `targetRepository`: analyzed repository identity; live analysis sample size is encoded as `targetRepository.analysisPullRequestLimit` from collection metadata.
|
|
29
31
|
- `analysisFilter`: optional metadata for explicit filters applied before metrics computation, including excluded PR classes and before/after PR counts.
|
|
30
32
|
- `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.
|
|
31
33
|
- `contributorSource`: optional sanitized contributor-source metadata from the repository profile and collection path. It records source type, path, coverage status, parsed hint count, and guardrail note. It does not include raw contributor file contents or contributor rankings.
|
|
34
|
+
- `collectionCoverage`: optional source collection coverage metadata copied into live-generated report JSON artifacts. `collectionCoverage.status` records aggregate coverage, and `collectionCoverage.sourceFamilies` lists source-family entries with `family`, `status`, `attempts`, `source`, `diagnostics`, and `downstreamImpact`. This uses the generic `source-bundle.v1` `coverage.sourceFamilies` name; legacy GitHub `apiFamilies` is not a live `friction-report.v1` field.
|
|
32
35
|
- `summary`: repository totals and top bottleneck identifiers.
|
|
33
36
|
- `coverage`: PR-open diff, workflow-run, and review-thread coverage counts plus caveats.
|
|
34
37
|
- `commentSources`: total and source-grouped review comments for Copilot, human, bot, scanner, author replies, and unknown sources.
|
|
@@ -99,7 +102,7 @@ The M3 report contract supports these recommendation categories:
|
|
|
99
102
|
|
|
100
103
|
## Coverage And Confidence
|
|
101
104
|
|
|
102
|
-
Reports must label unavailable or partial
|
|
105
|
+
Reports must label unavailable or partial source evidence instead of inferring unavailable values from merge-time data. Final/current PR metadata can come from source-bundle PR evidence, 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.
|
|
103
106
|
|
|
104
107
|
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.
|
|
105
108
|
|
|
@@ -119,7 +122,7 @@ Full live analysis writes `methodology.md` as a hybrid artifact: stable explanat
|
|
|
119
122
|
- contributor-source context when configured, including source type, path, coverage status, and parsed hint count, without raw contributor contents or rankings;
|
|
120
123
|
- 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;
|
|
121
124
|
- requested and collected PR counts;
|
|
122
|
-
- collection coverage status and
|
|
125
|
+
- collection coverage status and source-family diagnostics from `collectionCoverage.sourceFamilies`;
|
|
123
126
|
- scoring, ranking, dominance, sensitivity, and limitation explanations;
|
|
124
127
|
- generated artifact names and artifact-sensitivity guidance.
|
|
125
128
|
|
|
@@ -138,11 +141,11 @@ Minimum CSV column groups:
|
|
|
138
141
|
- `pr-metrics.csv`: PR number, title, URL, PR class, PR class source/rule evidence, changed lines, non-generated changed lines, review comments, review threads, review decision, human reviewer count, human approval / changes-requested booleans, failed checks, failed workflow runs, cancelled workflow runs, post-review commits, review-thread source, workflow-run source/coverage, and main ranking scores.
|
|
139
142
|
- `bottleneck-examples.csv`: bottleneck identity, recommendation category, PR identity, score/value, changed lines, validation counts, review counts, comment-source counts, workflow/review source and coverage labels, dominance, and source labels.
|
|
140
143
|
- `comment-sources.csv`: source name, total comments, bot/scanner classification, human/author classification, and share of all comments.
|
|
141
|
-
- `collection-coverage.csv`:
|
|
144
|
+
- `collection-coverage.csv`: source family, status, attempts, source label, diagnostics, and downstream impact.
|
|
142
145
|
|
|
143
146
|
Empty CSV cells mean unavailable or not applicable. Numeric zero should be used only for observed or computed zero counts. Count columns that depend on optional GitHub coverage should keep source or coverage labels nearby so spreadsheet readers can tell unavailable evidence apart from observed zeroes. CSVs must not include raw comment bodies, raw workflow logs, tokens, secret-bearing environment details, or individual contributor/reviewer rankings.
|
|
144
147
|
|
|
145
|
-
Contributor-source coverage appears in `collection-coverage.csv` as the `contributor_source`
|
|
148
|
+
Contributor-source coverage appears in `collection-coverage.csv` as the `contributor_source` source family when configured. CSVs may include aggregate comment-source counts influenced by contributor hints, but they must not include raw `.all-contributorsrc` contents, contributor names, contributor login lists, or person rankings.
|
|
146
149
|
|
|
147
150
|
## Optional Downstream Narrative Drafting
|
|
148
151
|
|
|
@@ -153,7 +156,7 @@ Use `friction-report.json` as the structured source of truth for report identity
|
|
|
153
156
|
- `pr-metrics.csv` for analyzed PR rows, class labels, review/validation counts, and ranking scores.
|
|
154
157
|
- `bottleneck-examples.csv` for representative examples tied to bottleneck identity, recommendation category, evidence sources, dominance, and score/value.
|
|
155
158
|
- `comment-sources.csv` for source-grouped comment totals and classification flags.
|
|
156
|
-
- `collection-coverage.csv` for
|
|
159
|
+
- `collection-coverage.csv` for source-family coverage, attempts, source labels, diagnostics, and downstream impact.
|
|
157
160
|
|
|
158
161
|
A guarded narrative-drafting workflow should:
|
|
159
162
|
|
|
@@ -1,15 +1,40 @@
|
|
|
1
1
|
# Source Bundle Contract
|
|
2
2
|
|
|
3
|
-
Schema: `schemas/
|
|
3
|
+
Schema: `schemas/source-bundle.schema.json`.
|
|
4
4
|
|
|
5
|
-
`source-bundle.json` is the analyzer's canonical
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
`source-bundle.json` is the analyzer's canonical source-evidence artifact. It is
|
|
6
|
+
the shared analyzer contract for live GitHub collection and bundled tutorial
|
|
7
|
+
sample data, not a raw provider dump. Both source kinds feed the same
|
|
8
|
+
normalization, metrics, report, methodology, and CSV pipeline, so they share one
|
|
9
|
+
contract with explicit provenance instead of pretending sample evidence is live
|
|
10
|
+
GitHub evidence.
|
|
8
11
|
|
|
9
|
-
The `
|
|
12
|
+
The current product contract is `source-bundle.v1`.
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
## Provenance
|
|
15
|
+
|
|
16
|
+
Every bundle records the source boundary:
|
|
17
|
+
|
|
18
|
+
- `schemaVersion`: must be `source-bundle.v1`.
|
|
19
|
+
- `source.kind`: `github` for live GitHub evidence or `sample` for bundled
|
|
20
|
+
synthetic sample evidence.
|
|
21
|
+
- `source.label`: human-readable label used by generated artifacts where source
|
|
22
|
+
provenance is shown.
|
|
23
|
+
- `collector`: collector identity and provider details. Collector names are not
|
|
24
|
+
constrained to a GitHub-only value.
|
|
25
|
+
|
|
26
|
+
Sample data must use labels that identify it as sample or synthetic evidence.
|
|
27
|
+
Live GitHub data should use labels such as `GitHub live collection` and
|
|
28
|
+
provider-specific source labels where the evidence came from GitHub APIs or
|
|
29
|
+
`gh`.
|
|
30
|
+
|
|
31
|
+
## Analyzer Evidence
|
|
32
|
+
|
|
33
|
+
The schema covers collector-owned fields:
|
|
34
|
+
|
|
35
|
+
- collection metadata, source provenance, target repository, repository
|
|
36
|
+
metadata, PR selection, source-family coverage diagnostics, and language
|
|
37
|
+
distribution context;
|
|
13
38
|
- optional sanitized contributor-source metadata: source type, repository path,
|
|
14
39
|
coverage/status diagnostics, and parsed hint count;
|
|
15
40
|
- pull request fields consumed downstream: identity, title, author object, URL,
|
|
@@ -25,11 +50,56 @@ than pretending zero runs were observed.
|
|
|
25
50
|
|
|
26
51
|
Contributor-source metadata must not persist raw contributor file contents or
|
|
27
52
|
parsed login lists. Parsed contributor hints may be used transiently during a
|
|
28
|
-
run, but generated artifacts keep only
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
53
|
+
run, but generated artifacts keep only sanitized metadata and `hintCount`.
|
|
54
|
+
|
|
55
|
+
## Coverage
|
|
56
|
+
|
|
57
|
+
Top-level `coverage.status` is retained as the aggregate source coverage status.
|
|
58
|
+
`coverage.sourceFamilies` lists the source-family entries that make up the
|
|
59
|
+
aggregate status. Each entry preserves the existing coverage-entry shape:
|
|
60
|
+
|
|
61
|
+
- `family`
|
|
62
|
+
- `source`
|
|
63
|
+
- `status`
|
|
64
|
+
- `attempts`
|
|
65
|
+
- `diagnostics`
|
|
66
|
+
- `downstreamImpact`
|
|
67
|
+
|
|
68
|
+
The `source` label is generic. Live entries may name GitHub REST, GraphQL, or
|
|
69
|
+
`gh` sources; sample entries may name bundled tutorial evidence. The per-PR
|
|
70
|
+
`pullRequests[*].coverage` object is retained in place with keys such as
|
|
71
|
+
`prOpenDiff`, `reviewThreads`, and `workflowRuns`.
|
|
72
|
+
|
|
73
|
+
## Field Migration From Legacy GitHub Bundles
|
|
74
|
+
|
|
75
|
+
`github-source-bundle.v1` is a legacy artifact shape. Current generated bundles
|
|
76
|
+
must use `source-bundle.v1`. There is no silent compatibility adapter in the
|
|
77
|
+
runtime; legacy artifacts need an explicit migration before they can validate
|
|
78
|
+
against the current schema.
|
|
79
|
+
|
|
80
|
+
| Legacy `github-source-bundle.v1` field | `source-bundle.v1` treatment |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| `schemaVersion` | Rename value to `source-bundle.v1`. |
|
|
83
|
+
| `collectedAt` | Retained as the materialization timestamp for live and sample bundles. |
|
|
84
|
+
| `collector` | Retained and generalized; `collector.name` is no longer constrained to a GitHub-only value. |
|
|
85
|
+
| `source` | New required object with `kind`, `label`, and optional metadata. |
|
|
86
|
+
| `targetRepository` | Retained. Sample bundles use fictional placeholder repository identity. |
|
|
87
|
+
| `repositoryMetadata` | Retained. Sample metadata must be fictional and clearly synthetic. |
|
|
88
|
+
| `selection` | Retained and generalized; selection strategies and source labels do not have to be GitHub-only. |
|
|
89
|
+
| `coverage.status` | Retained as the aggregate status over `coverage.sourceFamilies`. |
|
|
90
|
+
| `coverage.apiFamilies` | Renamed to `coverage.sourceFamilies`; entry shape is preserved. |
|
|
91
|
+
| `languageDistribution` | Retained with generalized source labels. |
|
|
92
|
+
| `contributorSource` | Retained as optional sanitized metadata with generalized source labels. |
|
|
93
|
+
| `pullRequests` | Retained as GitHub-shaped PR evidence consumed by normalization. |
|
|
94
|
+
| `pullRequests[*].coverage` | Retained as the per-PR coverage object with current per-family keys. |
|
|
95
|
+
| `raw` | Retained as the only subtree for provider- or sample-specific raw details. |
|
|
96
|
+
|
|
97
|
+
No analyzer-owned evidence field from `github-source-bundle.v1` is intentionally
|
|
98
|
+
removed in this migration.
|
|
99
|
+
|
|
100
|
+
The schema remains strict for analyzer-owned wrapper objects and mapped
|
|
101
|
+
entities. It does not attempt to schema every raw provider field, and normal
|
|
102
|
+
upstream GitHub API additions should not require schema updates unless the
|
|
103
|
+
collector maps them into canonical bundle fields. Future raw or
|
|
104
|
+
provider-specific payloads must live under an explicit `raw` subtree so
|
|
105
|
+
downstream normalization stays tied to canonical fields.
|
|
@@ -54,6 +54,6 @@ Availability decision:
|
|
|
54
54
|
|
|
55
55
|
Fixtures store compact, redacted source-shaped data rather than full raw payloads.
|
|
56
56
|
Live `source-bundle.json` artifacts use the analyzer-owned
|
|
57
|
-
`
|
|
57
|
+
`source-bundle.v1` contract in `schemas/source-bundle.schema.json`;
|
|
58
58
|
that schema covers the canonical fields consumed downstream, not the full GitHub
|
|
59
59
|
REST or GraphQL API response shape.
|
|
@@ -41,6 +41,12 @@ This keeps validation-target details in profile data rather than hardcoded produ
|
|
|
41
41
|
|
|
42
42
|
Each file rule must include a unique lowercase `id`, a non-empty `match` object, a supported `category`, and a supported `role`. Optional `functionalSurface` values use the same lowercase identifier shape as rule IDs. Optional `generated` values must be booleans. Unsupported rule or matcher keys fail validation, and invalid JavaScript regexes fail before collection with the rule ID and matcher field in the error.
|
|
43
43
|
|
|
44
|
+
File rules may include an optional `notes` string. Notes are human-readable
|
|
45
|
+
profile context for maintainers and reviewers. They can explain why a path rule
|
|
46
|
+
exists, what product surface it represents, or why a generated or low-signal
|
|
47
|
+
path is intentionally classified. Notes do not affect matching order,
|
|
48
|
+
classification output, scoring, collection, reports, or CSV exports.
|
|
49
|
+
|
|
44
50
|
## Pull Request Classes
|
|
45
51
|
|
|
46
52
|
`prClasses` is optional. Rules are evaluated in order and the first matching rule wins. The current profile contract supports title-only matchers:
|
|
@@ -64,6 +70,12 @@ Class identifiers are validated as lower-kebab-case or lower_snake_case strings.
|
|
|
64
70
|
|
|
65
71
|
PR class evidence is interpretive and profile-driven. It helps reports show class distributions, dominance notes, and explicit `--exclude-pr-class` filtering when you request filtering, but configured PR class rules do not change default scoring, ranking formulas, collection, or CSV export shape by themselves.
|
|
66
72
|
|
|
73
|
+
PR class rules may include an optional `notes` string. Use notes to document the
|
|
74
|
+
repository convention behind a class rule, such as Conventional Commit-style
|
|
75
|
+
title prefixes or release-train naming. Notes are valid JSON profile data for
|
|
76
|
+
humans only; they do not change title matching, PR class assignment, filtering,
|
|
77
|
+
scoring, collection, reports, or CSV exports.
|
|
78
|
+
|
|
67
79
|
Interactive setup can add a release PR class rule from a confirmed title convention using the current title-only matcher shape. Branch strategy answers stay in `workflow` context only; they do not create branch-based PR class matching. Branch-based class matching is deferred until a future matcher contract supports branch fields explicitly.
|
|
68
80
|
|
|
69
81
|
### Copyable PR Class Examples
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "repository-profile.v1",
|
|
3
|
+
"repository": {
|
|
4
|
+
"owner": "example-org",
|
|
5
|
+
"name": "delivery-dashboard"
|
|
6
|
+
},
|
|
7
|
+
"rules": [
|
|
8
|
+
{
|
|
9
|
+
"id": "frontend-ui-paths",
|
|
10
|
+
"match": { "prefix": "apps/web/" },
|
|
11
|
+
"category": "code",
|
|
12
|
+
"role": "product_ui",
|
|
13
|
+
"functionalSurface": "dashboard_ui",
|
|
14
|
+
"generated": false,
|
|
15
|
+
"notes": "Maps the customer-facing dashboard app to the UI surface so broad UI changes are not treated as unknown code."
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "api-runtime-paths",
|
|
19
|
+
"match": { "prefix": "services/api/" },
|
|
20
|
+
"category": "code",
|
|
21
|
+
"role": "core_product_code",
|
|
22
|
+
"functionalSurface": "delivery_api",
|
|
23
|
+
"generated": false,
|
|
24
|
+
"notes": "Represents backend delivery workflow behavior that usually needs API-focused review."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "worker-runtime-paths",
|
|
28
|
+
"match": { "prefix": "workers/" },
|
|
29
|
+
"category": "code",
|
|
30
|
+
"role": "core_product_code",
|
|
31
|
+
"functionalSurface": "background_jobs",
|
|
32
|
+
"generated": false
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "integration-tests",
|
|
36
|
+
"match": { "prefix": "tests/integration/" },
|
|
37
|
+
"category": "tests",
|
|
38
|
+
"role": "tests",
|
|
39
|
+
"functionalSurface": "test_suite",
|
|
40
|
+
"generated": false
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "workflow-config",
|
|
44
|
+
"match": { "prefix": ".github/workflows/" },
|
|
45
|
+
"category": "infrastructure",
|
|
46
|
+
"role": "infrastructure",
|
|
47
|
+
"functionalSurface": "ci_workflows",
|
|
48
|
+
"generated": false,
|
|
49
|
+
"notes": "Classifies CI workflow edits separately from product runtime changes."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "planning-notes",
|
|
53
|
+
"match": { "prefix": "docs/initiatives/" },
|
|
54
|
+
"category": "docs",
|
|
55
|
+
"role": "planning_docs",
|
|
56
|
+
"functionalSurface": "planning",
|
|
57
|
+
"generated": false
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "release-notes",
|
|
61
|
+
"match": { "exact": "CHANGELOG.md" },
|
|
62
|
+
"category": "docs",
|
|
63
|
+
"role": "release_notes",
|
|
64
|
+
"functionalSurface": "release_docs",
|
|
65
|
+
"generated": false
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"id": "generated-api-client",
|
|
69
|
+
"match": { "prefix": "packages/generated-client/" },
|
|
70
|
+
"category": "generated",
|
|
71
|
+
"role": "generated_or_vendored",
|
|
72
|
+
"functionalSurface": "generated_client",
|
|
73
|
+
"generated": true,
|
|
74
|
+
"notes": "Shows how generated code can stay visible while receiving low-signal weighting."
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
"prClasses": [
|
|
78
|
+
{
|
|
79
|
+
"id": "feature-title",
|
|
80
|
+
"class": "feature",
|
|
81
|
+
"match": { "titleRegex": "^feat(?:\\([^)]+\\))?:" },
|
|
82
|
+
"notes": "Use when feature PR titles follow a Conventional Commit-style prefix."
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"id": "fix-title",
|
|
86
|
+
"class": "fix",
|
|
87
|
+
"match": { "titleRegex": "^fix(?:\\([^)]+\\))?:" },
|
|
88
|
+
"notes": "Separates corrective work from feature delivery in the class distribution."
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"id": "test-title",
|
|
92
|
+
"class": "test",
|
|
93
|
+
"match": { "titleRegex": "^test(?:\\([^)]+\\))?:" }
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"id": "maintenance-title",
|
|
97
|
+
"class": "maintenance",
|
|
98
|
+
"match": { "titleRegex": "^(?:chore|ci|build|refactor)(?:\\([^)]+\\))?:" }
|
|
99
|
+
}
|
|
100
|
+
],
|
|
101
|
+
"workflow": {
|
|
102
|
+
"primaryMergeMethod": "squash_merge",
|
|
103
|
+
"releaseStrategy": "release_prs",
|
|
104
|
+
"branchStrategy": "trunk_based"
|
|
105
|
+
},
|
|
106
|
+
"contributors": {
|
|
107
|
+
"sourceType": "all_contributors",
|
|
108
|
+
"path": ".all-contributorsrc"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Tutorial Sample Report Excerpt
|
|
2
|
+
|
|
3
|
+
Source: Bundled synthetic sample, not live GitHub data.
|
|
4
|
+
|
|
5
|
+
The generated sample report opens by pointing readers to Change scope, Review churn, and Repo guidance gap. It also shows why the first findings should not be overgeneralized: PR #104 is intentionally broad and outlier-sensitive, PR-open diff coverage is partial, and one PR has unavailable workflow-run history.
|
|
6
|
+
|
|
7
|
+
Use the excerpt in public docs to set expectations: the sample is synthetic tutorial evidence, not observed GitHub data, and its value is showing how caveats, profile classifications, PR classes, workflow context, and CSV evidence appear in a local report bundle.
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "source-bundle.v1",
|
|
3
|
+
"collectedAt": "2026-06-15T12:00:00Z",
|
|
4
|
+
"source": {
|
|
5
|
+
"kind": "sample",
|
|
6
|
+
"label": "Bundled synthetic sample, not live GitHub data",
|
|
7
|
+
"metadata": {
|
|
8
|
+
"tutorial": true,
|
|
9
|
+
"publicSafety": "fully synthetic placeholder data"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"collector": {
|
|
13
|
+
"name": "tutorial-sample-collector",
|
|
14
|
+
"provider": "bundled-sample"
|
|
15
|
+
},
|
|
16
|
+
"targetRepository": {
|
|
17
|
+
"owner": "example-org",
|
|
18
|
+
"name": "delivery-dashboard",
|
|
19
|
+
"defaultBranch": "main",
|
|
20
|
+
"visibility": "public",
|
|
21
|
+
"analysisPullRequestLimit": 4,
|
|
22
|
+
"isValidationTarget": false
|
|
23
|
+
},
|
|
24
|
+
"repositoryMetadata": {
|
|
25
|
+
"id": "sample-repository-001",
|
|
26
|
+
"name": "delivery-dashboard",
|
|
27
|
+
"owner": "example-org",
|
|
28
|
+
"fullName": "example-org/delivery-dashboard",
|
|
29
|
+
"defaultBranch": "main",
|
|
30
|
+
"visibility": "public",
|
|
31
|
+
"isPrivate": false,
|
|
32
|
+
"htmlUrl": "https://example.com/example-org/delivery-dashboard"
|
|
33
|
+
},
|
|
34
|
+
"selection": {
|
|
35
|
+
"strategy": "bundled_tutorial_pull_requests",
|
|
36
|
+
"requestedLimit": 4,
|
|
37
|
+
"collectedCount": 4,
|
|
38
|
+
"source": "bundled tutorial sample"
|
|
39
|
+
},
|
|
40
|
+
"coverage": {
|
|
41
|
+
"status": "partial",
|
|
42
|
+
"sourceFamilies": [
|
|
43
|
+
{
|
|
44
|
+
"family": "repository_metadata",
|
|
45
|
+
"source": "bundled tutorial sample",
|
|
46
|
+
"status": "available",
|
|
47
|
+
"attempts": 1,
|
|
48
|
+
"diagnostics": [],
|
|
49
|
+
"downstreamImpact": "Fictional repository identity is available for report labeling."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"family": "pull_request_inventory",
|
|
53
|
+
"source": "bundled tutorial sample",
|
|
54
|
+
"status": "available",
|
|
55
|
+
"attempts": 1,
|
|
56
|
+
"diagnostics": [],
|
|
57
|
+
"downstreamImpact": "Four fictional merged pull requests are available for normalization and metrics."
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"family": "pull_request_details",
|
|
61
|
+
"source": "bundled tutorial sample",
|
|
62
|
+
"status": "available",
|
|
63
|
+
"attempts": 1,
|
|
64
|
+
"diagnostics": [],
|
|
65
|
+
"downstreamImpact": "Synthetic lifecycle, files, reviews, checks, and workflow summaries are populated."
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"family": "review_threads",
|
|
69
|
+
"source": "bundled tutorial sample",
|
|
70
|
+
"status": "available",
|
|
71
|
+
"attempts": 1,
|
|
72
|
+
"diagnostics": [],
|
|
73
|
+
"downstreamImpact": "Review-thread counts and comment sources are populated without comment text."
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"family": "workflow_runs",
|
|
77
|
+
"source": "bundled tutorial sample",
|
|
78
|
+
"status": "partial",
|
|
79
|
+
"attempts": 1,
|
|
80
|
+
"diagnostics": ["One PR intentionally has unavailable workflow-run history to demonstrate coverage caveats."],
|
|
81
|
+
"downstreamImpact": "Validation-gap metrics use observed runs where present and preserve unavailable coverage where missing."
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"family": "pr_open_diff",
|
|
85
|
+
"source": "bundled tutorial sample",
|
|
86
|
+
"status": "partial",
|
|
87
|
+
"attempts": 1,
|
|
88
|
+
"diagnostics": ["One PR intentionally lacks PR-open diff data to demonstrate partial diff-growth coverage."],
|
|
89
|
+
"downstreamImpact": "Diff-growth ratios are computed only for PRs with open-time snapshots."
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"family": "languages",
|
|
93
|
+
"source": "bundled tutorial sample",
|
|
94
|
+
"status": "available",
|
|
95
|
+
"attempts": 1,
|
|
96
|
+
"diagnostics": [],
|
|
97
|
+
"downstreamImpact": "Language distribution is illustrative context only."
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"family": "contributor_source",
|
|
101
|
+
"source": "bundled tutorial sample",
|
|
102
|
+
"status": "partial",
|
|
103
|
+
"attempts": 1,
|
|
104
|
+
"diagnostics": ["Synthetic contributor hints omit one reviewer to demonstrate partial profile context."],
|
|
105
|
+
"downstreamImpact": "Contributor-aware comment-source hints are available; scoring and person-level outputs are unchanged."
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
"languageDistribution": {
|
|
110
|
+
"source": "bundled tutorial sample",
|
|
111
|
+
"bytesByLanguage": {
|
|
112
|
+
"TypeScript": 420000,
|
|
113
|
+
"JavaScript": 180000,
|
|
114
|
+
"Markdown": 52000,
|
|
115
|
+
"YAML": 18000
|
|
116
|
+
},
|
|
117
|
+
"coverage": {
|
|
118
|
+
"family": "languages",
|
|
119
|
+
"source": "bundled tutorial sample",
|
|
120
|
+
"status": "available",
|
|
121
|
+
"attempts": 1,
|
|
122
|
+
"diagnostics": [],
|
|
123
|
+
"downstreamImpact": "Language distribution is illustrative context only."
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"contributorSource": {
|
|
127
|
+
"sourceType": "all_contributors",
|
|
128
|
+
"path": ".all-contributorsrc",
|
|
129
|
+
"coverage": {
|
|
130
|
+
"family": "contributor_source",
|
|
131
|
+
"source": "bundled tutorial sample",
|
|
132
|
+
"status": "partial",
|
|
133
|
+
"attempts": 1,
|
|
134
|
+
"diagnostics": ["Synthetic contributor hints omit one reviewer to demonstrate partial profile context."],
|
|
135
|
+
"downstreamImpact": "Contributor-aware comment-source hints are available; scoring and person-level outputs are unchanged."
|
|
136
|
+
},
|
|
137
|
+
"hintCount": 3
|
|
138
|
+
},
|
|
139
|
+
"pullRequests": [
|
|
140
|
+
{
|
|
141
|
+
"number": 101,
|
|
142
|
+
"title": "feat: add route health dashboard",
|
|
143
|
+
"author": { "login": "sample-author-ui", "type": "User" },
|
|
144
|
+
"url": "https://example.com/pull/101",
|
|
145
|
+
"state": "MERGED",
|
|
146
|
+
"createdAt": "2026-05-04T09:00:00Z",
|
|
147
|
+
"mergedAt": "2026-05-05T16:30:00Z",
|
|
148
|
+
"updatedAt": "2026-05-05T16:45:00Z",
|
|
149
|
+
"baseRefName": "main",
|
|
150
|
+
"headRefName": "sample/route-health-dashboard",
|
|
151
|
+
"headRefOid": "sample101merge",
|
|
152
|
+
"additions": 410,
|
|
153
|
+
"deletions": 96,
|
|
154
|
+
"changedFiles": 5,
|
|
155
|
+
"prOpenDiff": { "source": "direct", "confidence": "high", "additions": 190, "deletions": 44, "changedFiles": 5 },
|
|
156
|
+
"commits": [
|
|
157
|
+
{ "oid": "sample101a", "authoredDate": "2026-05-04T08:40:00Z", "committedDate": "2026-05-04T08:42:00Z", "messageHeadline": "feat: add route health dashboard" },
|
|
158
|
+
{ "oid": "sample101b", "authoredDate": "2026-05-05T12:15:00Z", "committedDate": "2026-05-05T12:16:00Z", "messageHeadline": "test: cover delayed route states" }
|
|
159
|
+
],
|
|
160
|
+
"files": [
|
|
161
|
+
{ "path": "apps/web/src/routes/RouteHealthPanel.tsx", "additions": 140, "deletions": 22, "changeType": "ADDED" },
|
|
162
|
+
{ "path": "apps/web/src/routes/RouteHealthChart.tsx", "additions": 94, "deletions": 16, "changeType": "ADDED" },
|
|
163
|
+
{ "path": "services/api/src/routes/health-summary.ts", "additions": 78, "deletions": 18, "changeType": "MODIFIED" },
|
|
164
|
+
{ "path": "tests/integration/route-health.test.ts", "additions": 82, "deletions": 20, "changeType": "ADDED" },
|
|
165
|
+
{ "path": "CHANGELOG.md", "additions": 16, "deletions": 20, "changeType": "MODIFIED" }
|
|
166
|
+
],
|
|
167
|
+
"reviews": [
|
|
168
|
+
{ "id": "sample-review-101-a", "author": { "login": "sample-reviewer-a", "type": "User" }, "submittedAt": "2026-05-04T14:20:00Z", "state": "CHANGES_REQUESTED", "commitOid": "sample101a", "generatedCommentCount": 0, "failedAttempt": true },
|
|
169
|
+
{ "id": "sample-review-101-b", "author": { "login": "sample-reviewer-a", "type": "User" }, "submittedAt": "2026-05-05T15:20:00Z", "state": "APPROVED", "commitOid": "sample101b", "generatedCommentCount": 0, "failedAttempt": false }
|
|
170
|
+
],
|
|
171
|
+
"reviewThreads": {
|
|
172
|
+
"source": "bundled tutorial sample:review_threads",
|
|
173
|
+
"totalCount": 2,
|
|
174
|
+
"nodes": [
|
|
175
|
+
{ "id": "sample-thread-101-a", "isResolved": true, "isOutdated": false, "path": "apps/web/src/routes/RouteHealthPanel.tsx", "line": 88, "comments": [
|
|
176
|
+
{ "databaseId": "sample-comment-101-a", "author": { "login": "sample-reviewer-a", "type": "User" }, "path": "apps/web/src/routes/RouteHealthPanel.tsx", "line": 88, "originalLine": 88, "createdAt": "2026-05-04T14:21:00Z", "updatedAt": "2026-05-04T14:21:00Z", "url": "https://example.com/pull/101#discussion-sample-101-a" },
|
|
177
|
+
{ "databaseId": "sample-comment-101-b", "author": { "login": "sample-author-ui", "type": "User" }, "path": "apps/web/src/routes/RouteHealthPanel.tsx", "line": 88, "originalLine": 88, "createdAt": "2026-05-05T12:18:00Z", "updatedAt": "2026-05-05T12:18:00Z", "url": "https://example.com/pull/101#discussion-sample-101-b" }
|
|
178
|
+
] },
|
|
179
|
+
{ "id": "sample-thread-101-b", "isResolved": true, "isOutdated": true, "path": "services/api/src/routes/health-summary.ts", "line": 41, "comments": [
|
|
180
|
+
{ "databaseId": "sample-comment-101-c", "author": { "login": "sample-reviewer-b", "type": "User" }, "path": "services/api/src/routes/health-summary.ts", "line": 41, "originalLine": 41, "createdAt": "2026-05-04T15:02:00Z", "updatedAt": "2026-05-04T15:02:00Z", "url": "https://example.com/pull/101#discussion-sample-101-c" }
|
|
181
|
+
] }
|
|
182
|
+
]
|
|
183
|
+
},
|
|
184
|
+
"statusCheckRollup": [
|
|
185
|
+
{ "__typename": "CheckRun", "name": "unit", "context": null, "workflowName": "CI", "status": "COMPLETED", "conclusion": "SUCCESS", "startedAt": "2026-05-04T09:10:00Z", "completedAt": "2026-05-04T09:16:00Z" },
|
|
186
|
+
{ "__typename": "CheckRun", "name": "integration", "context": null, "workflowName": "CI", "status": "COMPLETED", "conclusion": "FAILURE", "startedAt": "2026-05-04T09:12:00Z", "completedAt": "2026-05-04T09:24:00Z" },
|
|
187
|
+
{ "__typename": "CheckRun", "name": "integration", "context": null, "workflowName": "CI", "status": "COMPLETED", "conclusion": "SUCCESS", "startedAt": "2026-05-05T12:20:00Z", "completedAt": "2026-05-05T12:31:00Z" }
|
|
188
|
+
],
|
|
189
|
+
"workflowRuns": {
|
|
190
|
+
"source": "bundled tutorial sample:workflow_runs",
|
|
191
|
+
"totalCount": 2,
|
|
192
|
+
"conclusions": { "success": 1, "failure": 1 },
|
|
193
|
+
"runs": [
|
|
194
|
+
{ "id": "sample-run-101-a", "name": "CI", "workflowName": "CI", "headSha": "sample101a", "headBranch": "sample/route-health-dashboard", "event": "pull_request", "status": "completed", "conclusion": "failure", "createdAt": "2026-05-04T09:10:00Z", "updatedAt": "2026-05-04T09:24:00Z", "runStartedAt": "2026-05-04T09:12:00Z", "htmlUrl": "https://example.com/actions/runs/sample-run-101-a" },
|
|
195
|
+
{ "id": "sample-run-101-b", "name": "CI", "workflowName": "CI", "headSha": "sample101b", "headBranch": "sample/route-health-dashboard", "event": "pull_request", "status": "completed", "conclusion": "success", "createdAt": "2026-05-05T12:20:00Z", "updatedAt": "2026-05-05T12:31:00Z", "runStartedAt": "2026-05-05T12:21:00Z", "htmlUrl": "https://example.com/actions/runs/sample-run-101-b" }
|
|
196
|
+
]
|
|
197
|
+
},
|
|
198
|
+
"coverage": {
|
|
199
|
+
"prOpenDiff": { "family": "pr_open_diff", "source": "bundled tutorial sample", "status": "available", "attempts": 1, "diagnostics": [], "downstreamImpact": "Diff-growth metrics can compare open and merge sizes." },
|
|
200
|
+
"reviewThreads": { "family": "review_threads", "source": "bundled tutorial sample", "status": "available", "attempts": 1, "diagnostics": [], "downstreamImpact": "Review-thread evidence is available." },
|
|
201
|
+
"workflowRuns": { "family": "workflow_runs", "source": "bundled tutorial sample", "status": "available", "attempts": 1, "diagnostics": [], "downstreamImpact": "Workflow-run evidence is available." }
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"number": 102,
|
|
206
|
+
"title": "fix: retry stalled dispatch jobs",
|
|
207
|
+
"author": { "login": "sample-author-api", "type": "User" },
|
|
208
|
+
"url": "https://example.com/pull/102",
|
|
209
|
+
"state": "MERGED",
|
|
210
|
+
"createdAt": "2026-05-07T10:00:00Z",
|
|
211
|
+
"mergedAt": "2026-05-07T18:10:00Z",
|
|
212
|
+
"updatedAt": "2026-05-07T18:20:00Z",
|
|
213
|
+
"baseRefName": "main",
|
|
214
|
+
"headRefName": "sample/retry-dispatch-jobs",
|
|
215
|
+
"headRefOid": "sample102merge",
|
|
216
|
+
"additions": 126,
|
|
217
|
+
"deletions": 34,
|
|
218
|
+
"changedFiles": 4,
|
|
219
|
+
"prOpenDiff": { "source": "direct", "confidence": "high", "additions": 120, "deletions": 30, "changedFiles": 4 },
|
|
220
|
+
"commits": [
|
|
221
|
+
{ "oid": "sample102a", "authoredDate": "2026-05-07T09:45:00Z", "committedDate": "2026-05-07T09:46:00Z", "messageHeadline": "fix: retry stalled dispatch jobs" },
|
|
222
|
+
{ "oid": "sample102b", "authoredDate": "2026-05-07T15:10:00Z", "committedDate": "2026-05-07T15:12:00Z", "messageHeadline": "test: cover retry backoff" }
|
|
223
|
+
],
|
|
224
|
+
"files": [
|
|
225
|
+
{ "path": "workers/dispatch/retry.ts", "additions": 58, "deletions": 18, "changeType": "MODIFIED" },
|
|
226
|
+
{ "path": "services/api/src/jobs/dispatch.ts", "additions": 24, "deletions": 8, "changeType": "MODIFIED" },
|
|
227
|
+
{ "path": "tests/integration/dispatch-retry.test.ts", "additions": 42, "deletions": 8, "changeType": "ADDED" },
|
|
228
|
+
{ "path": "docs/initiatives/retry-jobs/checklist.md", "additions": 2, "deletions": 0, "changeType": "ADDED" }
|
|
229
|
+
],
|
|
230
|
+
"reviews": [
|
|
231
|
+
{ "id": "sample-review-102-a", "author": { "login": "sample-reviewer-b", "type": "User" }, "submittedAt": "2026-05-07T13:00:00Z", "state": "COMMENTED", "commitOid": "sample102a", "generatedCommentCount": 0, "failedAttempt": false },
|
|
232
|
+
{ "id": "sample-review-102-b", "author": { "login": "sample-reviewer-b", "type": "User" }, "submittedAt": "2026-05-07T17:40:00Z", "state": "APPROVED", "commitOid": "sample102b", "generatedCommentCount": 0, "failedAttempt": false }
|
|
233
|
+
],
|
|
234
|
+
"reviewThreads": {
|
|
235
|
+
"source": "bundled tutorial sample:review_threads",
|
|
236
|
+
"totalCount": 1,
|
|
237
|
+
"nodes": [
|
|
238
|
+
{ "id": "sample-thread-102-a", "isResolved": true, "isOutdated": false, "path": "workers/dispatch/retry.ts", "line": 30, "comments": [
|
|
239
|
+
{ "databaseId": "sample-comment-102-a", "author": { "login": "sample-reviewer-b", "type": "User" }, "path": "workers/dispatch/retry.ts", "line": 30, "originalLine": 30, "createdAt": "2026-05-07T13:01:00Z", "updatedAt": "2026-05-07T13:01:00Z", "url": "https://example.com/pull/102#discussion-sample-102-a" },
|
|
240
|
+
{ "databaseId": "sample-comment-102-b", "author": { "login": "sample-author-api", "type": "User" }, "path": "workers/dispatch/retry.ts", "line": 30, "originalLine": 30, "createdAt": "2026-05-07T15:15:00Z", "updatedAt": "2026-05-07T15:15:00Z", "url": "https://example.com/pull/102#discussion-sample-102-b" }
|
|
241
|
+
] }
|
|
242
|
+
]
|
|
243
|
+
},
|
|
244
|
+
"statusCheckRollup": [
|
|
245
|
+
{ "__typename": "CheckRun", "name": "unit", "context": null, "workflowName": "CI", "status": "COMPLETED", "conclusion": "SUCCESS", "startedAt": "2026-05-07T10:10:00Z", "completedAt": "2026-05-07T10:16:00Z" }
|
|
246
|
+
],
|
|
247
|
+
"workflowRuns": {
|
|
248
|
+
"source": "bundled tutorial sample:workflow_runs",
|
|
249
|
+
"totalCount": 1,
|
|
250
|
+
"conclusions": { "success": 1 },
|
|
251
|
+
"runs": [
|
|
252
|
+
{ "id": "sample-run-102-a", "name": "CI", "workflowName": "CI", "headSha": "sample102b", "headBranch": "sample/retry-dispatch-jobs", "event": "pull_request", "status": "completed", "conclusion": "success", "createdAt": "2026-05-07T15:20:00Z", "updatedAt": "2026-05-07T15:29:00Z", "runStartedAt": "2026-05-07T15:21:00Z", "htmlUrl": "https://example.com/actions/runs/sample-run-102-a" }
|
|
253
|
+
]
|
|
254
|
+
},
|
|
255
|
+
"coverage": {
|
|
256
|
+
"prOpenDiff": { "family": "pr_open_diff", "source": "bundled tutorial sample", "status": "available", "attempts": 1, "diagnostics": [], "downstreamImpact": "Diff-growth metrics can compare open and merge sizes." },
|
|
257
|
+
"reviewThreads": { "family": "review_threads", "source": "bundled tutorial sample", "status": "available", "attempts": 1, "diagnostics": [], "downstreamImpact": "Review-thread evidence is available." },
|
|
258
|
+
"workflowRuns": { "family": "workflow_runs", "source": "bundled tutorial sample", "status": "available", "attempts": 1, "diagnostics": [], "downstreamImpact": "Workflow-run evidence is available." }
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"number": 103,
|
|
263
|
+
"title": "test: add billing webhook contract coverage",
|
|
264
|
+
"author": { "login": "sample-author-test", "type": "User" },
|
|
265
|
+
"url": "https://example.com/pull/103",
|
|
266
|
+
"state": "MERGED",
|
|
267
|
+
"createdAt": "2026-05-10T11:00:00Z",
|
|
268
|
+
"mergedAt": "2026-05-10T14:40:00Z",
|
|
269
|
+
"updatedAt": "2026-05-10T14:45:00Z",
|
|
270
|
+
"baseRefName": "main",
|
|
271
|
+
"headRefName": "sample/billing-webhook-tests",
|
|
272
|
+
"headRefOid": "sample103merge",
|
|
273
|
+
"additions": 88,
|
|
274
|
+
"deletions": 10,
|
|
275
|
+
"changedFiles": 3,
|
|
276
|
+
"prOpenDiff": { "source": "unavailable", "confidence": "unavailable", "reason": "Open-time snapshot intentionally omitted from the bundled sample." },
|
|
277
|
+
"commits": [
|
|
278
|
+
{ "oid": "sample103a", "authoredDate": "2026-05-10T10:40:00Z", "committedDate": "2026-05-10T10:41:00Z", "messageHeadline": "test: add billing webhook contract coverage" }
|
|
279
|
+
],
|
|
280
|
+
"files": [
|
|
281
|
+
{ "path": "tests/integration/billing-webhooks.test.ts", "additions": 72, "deletions": 4, "changeType": "ADDED" },
|
|
282
|
+
{ "path": "services/api/src/billing/webhook-fixtures.ts", "additions": 10, "deletions": 4, "changeType": "MODIFIED" },
|
|
283
|
+
{ "path": "packages/generated-client/billing.ts", "additions": 6, "deletions": 2, "changeType": "MODIFIED" }
|
|
284
|
+
],
|
|
285
|
+
"reviews": [
|
|
286
|
+
{ "id": "sample-review-103-a", "author": { "login": "sample-reviewer-a", "type": "User" }, "submittedAt": "2026-05-10T13:30:00Z", "state": "APPROVED", "commitOid": "sample103a", "generatedCommentCount": 0, "failedAttempt": false }
|
|
287
|
+
],
|
|
288
|
+
"reviewThreads": { "source": "bundled tutorial sample:review_threads", "totalCount": 0, "nodes": [] },
|
|
289
|
+
"statusCheckRollup": [
|
|
290
|
+
{ "__typename": "CheckRun", "name": "unit", "context": null, "workflowName": "CI", "status": "COMPLETED", "conclusion": "SUCCESS", "startedAt": "2026-05-10T11:10:00Z", "completedAt": "2026-05-10T11:15:00Z" }
|
|
291
|
+
],
|
|
292
|
+
"workflowRuns": {
|
|
293
|
+
"source": "unavailable",
|
|
294
|
+
"totalCount": null,
|
|
295
|
+
"conclusions": {},
|
|
296
|
+
"runs": []
|
|
297
|
+
},
|
|
298
|
+
"coverage": {
|
|
299
|
+
"prOpenDiff": { "family": "pr_open_diff", "source": "bundled tutorial sample", "status": "unavailable", "attempts": 1, "diagnostics": ["Open-time snapshot intentionally omitted from the bundled sample."], "downstreamImpact": "Diff-growth metrics are unavailable for this PR." },
|
|
300
|
+
"reviewThreads": { "family": "review_threads", "source": "bundled tutorial sample", "status": "available", "attempts": 1, "diagnostics": [], "downstreamImpact": "Review-thread evidence is available and shows zero threads." },
|
|
301
|
+
"workflowRuns": { "family": "workflow_runs", "source": "bundled tutorial sample", "status": "unavailable", "attempts": 1, "diagnostics": ["Workflow-run history intentionally omitted for this PR."], "downstreamImpact": "Validation-gap metrics preserve unavailable workflow coverage." }
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"number": 104,
|
|
306
|
+
"title": "feat: consolidate dispatch settings",
|
|
307
|
+
"author": { "login": "sample-author-platform", "type": "User" },
|
|
308
|
+
"url": "https://example.com/pull/104",
|
|
309
|
+
"state": "MERGED",
|
|
310
|
+
"createdAt": "2026-05-14T08:00:00Z",
|
|
311
|
+
"mergedAt": "2026-05-17T19:00:00Z",
|
|
312
|
+
"updatedAt": "2026-05-17T19:15:00Z",
|
|
313
|
+
"baseRefName": "main",
|
|
314
|
+
"headRefName": "sample/consolidate-dispatch-settings",
|
|
315
|
+
"headRefOid": "sample104merge",
|
|
316
|
+
"additions": 980,
|
|
317
|
+
"deletions": 420,
|
|
318
|
+
"changedFiles": 8,
|
|
319
|
+
"prOpenDiff": { "source": "direct", "confidence": "high", "additions": 260, "deletions": 90, "changedFiles": 6 },
|
|
320
|
+
"commits": [
|
|
321
|
+
{ "oid": "sample104a", "authoredDate": "2026-05-14T07:45:00Z", "committedDate": "2026-05-14T07:46:00Z", "messageHeadline": "feat: consolidate dispatch settings" },
|
|
322
|
+
{ "oid": "sample104b", "authoredDate": "2026-05-15T10:20:00Z", "committedDate": "2026-05-15T10:22:00Z", "messageHeadline": "fix: split overloaded settings model" },
|
|
323
|
+
{ "oid": "sample104c", "authoredDate": "2026-05-16T16:30:00Z", "committedDate": "2026-05-16T16:31:00Z", "messageHeadline": "test: cover settings migration" },
|
|
324
|
+
{ "oid": "sample104d", "authoredDate": "2026-05-17T13:00:00Z", "committedDate": "2026-05-17T13:02:00Z", "messageHeadline": "ci: repair migration fixture" }
|
|
325
|
+
],
|
|
326
|
+
"files": [
|
|
327
|
+
{ "path": "apps/web/src/settings/DispatchSettingsPage.tsx", "additions": 210, "deletions": 90, "changeType": "MODIFIED" },
|
|
328
|
+
{ "path": "apps/web/src/settings/DispatchSettingsForm.tsx", "additions": 180, "deletions": 70, "changeType": "ADDED" },
|
|
329
|
+
{ "path": "services/api/src/settings/dispatch-settings.ts", "additions": 190, "deletions": 88, "changeType": "MODIFIED" },
|
|
330
|
+
{ "path": "workers/dispatch/settings-sync.ts", "additions": 150, "deletions": 64, "changeType": "ADDED" },
|
|
331
|
+
{ "path": "tests/integration/dispatch-settings.test.ts", "additions": 120, "deletions": 30, "changeType": "ADDED" },
|
|
332
|
+
{ "path": ".github/workflows/dispatch-settings.yml", "additions": 62, "deletions": 20, "changeType": "ADDED" },
|
|
333
|
+
{ "path": "docs/initiatives/dispatch-settings/prd.md", "additions": 48, "deletions": 22, "changeType": "MODIFIED" },
|
|
334
|
+
{ "path": "packages/generated-client/settings.ts", "additions": 20, "deletions": 36, "changeType": "MODIFIED" }
|
|
335
|
+
],
|
|
336
|
+
"reviews": [
|
|
337
|
+
{ "id": "sample-review-104-a", "author": { "login": "sample-reviewer-a", "type": "User" }, "submittedAt": "2026-05-14T16:00:00Z", "state": "CHANGES_REQUESTED", "commitOid": "sample104a", "generatedCommentCount": 0, "failedAttempt": true },
|
|
338
|
+
{ "id": "sample-review-104-b", "author": { "login": "sample-reviewer-b", "type": "User" }, "submittedAt": "2026-05-15T14:00:00Z", "state": "CHANGES_REQUESTED", "commitOid": "sample104b", "generatedCommentCount": 0, "failedAttempt": true },
|
|
339
|
+
{ "id": "sample-review-104-c", "author": { "login": "sample-reviewer-a", "type": "User" }, "submittedAt": "2026-05-16T18:30:00Z", "state": "COMMENTED", "commitOid": "sample104c", "generatedCommentCount": 0, "failedAttempt": false },
|
|
340
|
+
{ "id": "sample-review-104-d", "author": { "login": "sample-reviewer-b", "type": "User" }, "submittedAt": "2026-05-17T17:45:00Z", "state": "APPROVED", "commitOid": "sample104d", "generatedCommentCount": 0, "failedAttempt": false }
|
|
341
|
+
],
|
|
342
|
+
"reviewThreads": {
|
|
343
|
+
"source": "bundled tutorial sample:review_threads",
|
|
344
|
+
"totalCount": 3,
|
|
345
|
+
"nodes": [
|
|
346
|
+
{ "id": "sample-thread-104-a", "isResolved": true, "isOutdated": true, "path": "services/api/src/settings/dispatch-settings.ts", "line": 65, "comments": [
|
|
347
|
+
{ "databaseId": "sample-comment-104-a", "author": { "login": "sample-reviewer-a", "type": "User" }, "path": "services/api/src/settings/dispatch-settings.ts", "line": 65, "originalLine": 65, "createdAt": "2026-05-14T16:02:00Z", "updatedAt": "2026-05-14T16:02:00Z", "url": "https://example.com/pull/104#discussion-sample-104-a" },
|
|
348
|
+
{ "databaseId": "sample-comment-104-b", "author": { "login": "sample-author-platform", "type": "User" }, "path": "services/api/src/settings/dispatch-settings.ts", "line": 65, "originalLine": 65, "createdAt": "2026-05-15T10:24:00Z", "updatedAt": "2026-05-15T10:24:00Z", "url": "https://example.com/pull/104#discussion-sample-104-b" }
|
|
349
|
+
] },
|
|
350
|
+
{ "id": "sample-thread-104-b", "isResolved": true, "isOutdated": false, "path": "apps/web/src/settings/DispatchSettingsForm.tsx", "line": 112, "comments": [
|
|
351
|
+
{ "databaseId": "sample-comment-104-c", "author": { "login": "sample-reviewer-b", "type": "User" }, "path": "apps/web/src/settings/DispatchSettingsForm.tsx", "line": 112, "originalLine": 112, "createdAt": "2026-05-15T14:05:00Z", "updatedAt": "2026-05-15T14:05:00Z", "url": "https://example.com/pull/104#discussion-sample-104-c" },
|
|
352
|
+
{ "databaseId": "sample-comment-104-d", "author": { "login": "sample-author-platform", "type": "User" }, "path": "apps/web/src/settings/DispatchSettingsForm.tsx", "line": 112, "originalLine": 112, "createdAt": "2026-05-16T16:40:00Z", "updatedAt": "2026-05-16T16:40:00Z", "url": "https://example.com/pull/104#discussion-sample-104-d" }
|
|
353
|
+
] },
|
|
354
|
+
{ "id": "sample-thread-104-c", "isResolved": false, "isOutdated": false, "path": ".github/workflows/dispatch-settings.yml", "line": 18, "comments": [
|
|
355
|
+
{ "databaseId": "sample-comment-104-e", "author": { "login": "sample-ci-bot", "type": "Bot" }, "path": ".github/workflows/dispatch-settings.yml", "line": 18, "originalLine": 18, "createdAt": "2026-05-17T13:10:00Z", "updatedAt": "2026-05-17T13:10:00Z", "url": "https://example.com/pull/104#discussion-sample-104-e" }
|
|
356
|
+
] }
|
|
357
|
+
]
|
|
358
|
+
},
|
|
359
|
+
"statusCheckRollup": [
|
|
360
|
+
{ "__typename": "CheckRun", "name": "unit", "context": null, "workflowName": "CI", "status": "COMPLETED", "conclusion": "FAILURE", "startedAt": "2026-05-14T08:10:00Z", "completedAt": "2026-05-14T08:16:00Z" },
|
|
361
|
+
{ "__typename": "CheckRun", "name": "integration", "context": null, "workflowName": "CI", "status": "COMPLETED", "conclusion": "FAILURE", "startedAt": "2026-05-14T08:12:00Z", "completedAt": "2026-05-14T08:28:00Z" },
|
|
362
|
+
{ "__typename": "CheckRun", "name": "migration", "context": null, "workflowName": "Migration checks", "status": "COMPLETED", "conclusion": "SUCCESS", "startedAt": "2026-05-17T13:10:00Z", "completedAt": "2026-05-17T13:24:00Z" }
|
|
363
|
+
],
|
|
364
|
+
"workflowRuns": {
|
|
365
|
+
"source": "bundled tutorial sample:workflow_runs",
|
|
366
|
+
"totalCount": 4,
|
|
367
|
+
"conclusions": { "success": 1, "failure": 2, "cancelled": 1 },
|
|
368
|
+
"runs": [
|
|
369
|
+
{ "id": "sample-run-104-a", "name": "CI", "workflowName": "CI", "headSha": "sample104a", "headBranch": "sample/consolidate-dispatch-settings", "event": "pull_request", "status": "completed", "conclusion": "failure", "createdAt": "2026-05-14T08:10:00Z", "updatedAt": "2026-05-14T08:28:00Z", "runStartedAt": "2026-05-14T08:12:00Z", "htmlUrl": "https://example.com/actions/runs/sample-run-104-a" },
|
|
370
|
+
{ "id": "sample-run-104-b", "name": "CI", "workflowName": "CI", "headSha": "sample104b", "headBranch": "sample/consolidate-dispatch-settings", "event": "pull_request", "status": "completed", "conclusion": "failure", "createdAt": "2026-05-15T10:30:00Z", "updatedAt": "2026-05-15T10:48:00Z", "runStartedAt": "2026-05-15T10:31:00Z", "htmlUrl": "https://example.com/actions/runs/sample-run-104-b" },
|
|
371
|
+
{ "id": "sample-run-104-c", "name": "CI", "workflowName": "CI", "headSha": "sample104c", "headBranch": "sample/consolidate-dispatch-settings", "event": "pull_request", "status": "completed", "conclusion": "cancelled", "createdAt": "2026-05-16T16:36:00Z", "updatedAt": "2026-05-16T16:39:00Z", "runStartedAt": "2026-05-16T16:37:00Z", "htmlUrl": "https://example.com/actions/runs/sample-run-104-c" },
|
|
372
|
+
{ "id": "sample-run-104-d", "name": "Migration checks", "workflowName": "Migration checks", "headSha": "sample104d", "headBranch": "sample/consolidate-dispatch-settings", "event": "pull_request", "status": "completed", "conclusion": "success", "createdAt": "2026-05-17T13:10:00Z", "updatedAt": "2026-05-17T13:24:00Z", "runStartedAt": "2026-05-17T13:11:00Z", "htmlUrl": "https://example.com/actions/runs/sample-run-104-d" }
|
|
373
|
+
]
|
|
374
|
+
},
|
|
375
|
+
"coverage": {
|
|
376
|
+
"prOpenDiff": { "family": "pr_open_diff", "source": "bundled tutorial sample", "status": "available", "attempts": 1, "diagnostics": [], "downstreamImpact": "Diff-growth metrics can compare open and merge sizes." },
|
|
377
|
+
"reviewThreads": { "family": "review_threads", "source": "bundled tutorial sample", "status": "available", "attempts": 1, "diagnostics": [], "downstreamImpact": "Review-thread evidence is available." },
|
|
378
|
+
"workflowRuns": { "family": "workflow_runs", "source": "bundled tutorial sample", "status": "partial", "attempts": 1, "diagnostics": ["One cancelled run is included to demonstrate validation caveats."], "downstreamImpact": "Validation-gap metrics include observed failed and cancelled workflow runs." }
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
],
|
|
382
|
+
"raw": {
|
|
383
|
+
"sample": {
|
|
384
|
+
"description": "Fictional tutorial data for Delivery Friction Analyzer.",
|
|
385
|
+
"containsPrivateData": false
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "delivery-friction-analyzer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Local GitHub pull request analytics for delivery friction reports.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"src",
|
|
12
12
|
"schemas",
|
|
13
|
+
"examples/tutorial",
|
|
13
14
|
"docs/contracts",
|
|
14
15
|
"docs/reference",
|
|
15
16
|
"fixtures/github/mcp-writing/profile.json",
|
package/release-log.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
### 2026-06-27 — Tutorial Sample Bundle
|
|
6
|
+
|
|
7
|
+
- What changed: Added the packaged tutorial sample source bundle, educational sample profile, and generated-aligned report excerpt for a fully synthetic repository.
|
|
8
|
+
- Why it matters: Maintainers can review and ship a public-safe sample report experience without relying on private repository access or live GitHub data.
|
|
9
|
+
- Who is affected: Maintainers preparing the tutorial sample experience and future users who inspect the bundled sample artifacts after sample CLI wiring lands.
|
|
10
|
+
- Action needed: None.
|
|
11
|
+
- PR: https://github.com/hannasdev/delivery-friction-analyzer/pull/61
|
|
12
|
+
|
|
13
|
+
### 2026-06-26 — Generic Source Bundle Report Wording
|
|
14
|
+
|
|
15
|
+
- What changed: Report Markdown, methodology-adjacent contract docs, and CSV coverage expectations now use generic source-bundle and source-family wording for `source-bundle.v1`, including `collectionCoverage.status` and `collectionCoverage.sourceFamilies`.
|
|
16
|
+
- Why it matters: Maintainers can review live GitHub output and future bundled sample output without stale GitHub-only labels implying that every report source is live API evidence.
|
|
17
|
+
- Who is affected: Maintainers reviewing generated reports, `friction-report.v1` JSON, contract docs, or source coverage CSV exports during the tutorial sample migration.
|
|
18
|
+
- Action needed: Update any scripts or downstream tooling that parse source coverage fields from `apiFamilies`/`api_family` to `sourceFamilies`/`source_family`.
|
|
19
|
+
- PR: #60
|
|
20
|
+
|
|
5
21
|
### 2026-06-23 — Tutorial Target Guidance
|
|
6
22
|
|
|
7
23
|
- What changed: Public quickstart guidance no longer depends on `hannasdev/mcp-writing`, and product-repository rejection plus `--validation-target` help now make the analysis boundary clearer.
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://delivery-friction-analyzer.local/schemas/
|
|
4
|
-
"title": "
|
|
3
|
+
"$id": "https://delivery-friction-analyzer.local/schemas/source-bundle.schema.json",
|
|
4
|
+
"title": "SourceBundle",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"additionalProperties": false,
|
|
7
7
|
"required": [
|
|
8
8
|
"schemaVersion",
|
|
9
9
|
"collectedAt",
|
|
10
|
+
"source",
|
|
10
11
|
"collector",
|
|
11
12
|
"targetRepository",
|
|
12
13
|
"repositoryMetadata",
|
|
@@ -16,14 +17,27 @@
|
|
|
16
17
|
"pullRequests"
|
|
17
18
|
],
|
|
18
19
|
"properties": {
|
|
19
|
-
"schemaVersion": { "const": "
|
|
20
|
+
"schemaVersion": { "const": "source-bundle.v1" },
|
|
20
21
|
"collectedAt": { "type": "string" },
|
|
22
|
+
"source": {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"additionalProperties": false,
|
|
25
|
+
"required": ["kind", "label"],
|
|
26
|
+
"properties": {
|
|
27
|
+
"kind": { "enum": ["github", "sample"] },
|
|
28
|
+
"label": { "type": "string", "minLength": 1 },
|
|
29
|
+
"metadata": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"additionalProperties": true
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
21
35
|
"collector": {
|
|
22
36
|
"type": "object",
|
|
23
37
|
"additionalProperties": false,
|
|
24
38
|
"required": ["name", "provider"],
|
|
25
39
|
"properties": {
|
|
26
|
-
"name": { "
|
|
40
|
+
"name": { "type": "string", "minLength": 1 },
|
|
27
41
|
"provider": { "type": "string", "minLength": 1 }
|
|
28
42
|
}
|
|
29
43
|
},
|
|
@@ -48,7 +62,7 @@
|
|
|
48
62
|
"additionalProperties": false,
|
|
49
63
|
"required": ["strategy", "requestedLimit", "collectedCount", "source"],
|
|
50
64
|
"properties": {
|
|
51
|
-
"strategy": { "
|
|
65
|
+
"strategy": { "type": "string", "minLength": 1 },
|
|
52
66
|
"requestedLimit": { "type": "integer", "minimum": 1, "maximum": 100 },
|
|
53
67
|
"collectedCount": { "type": "integer", "minimum": 0, "maximum": 100 },
|
|
54
68
|
"source": { "type": "string", "minLength": 1 }
|
|
@@ -57,12 +71,12 @@
|
|
|
57
71
|
"coverage": {
|
|
58
72
|
"type": "object",
|
|
59
73
|
"additionalProperties": false,
|
|
60
|
-
"required": ["status", "
|
|
74
|
+
"required": ["status", "sourceFamilies"],
|
|
61
75
|
"properties": {
|
|
62
76
|
"status": { "$ref": "#/$defs/coverageStatus" },
|
|
63
|
-
"
|
|
77
|
+
"sourceFamilies": {
|
|
64
78
|
"type": "array",
|
|
65
|
-
"items": { "$ref": "#/$defs/
|
|
79
|
+
"items": { "$ref": "#/$defs/sourceCoverageEntry" }
|
|
66
80
|
}
|
|
67
81
|
}
|
|
68
82
|
},
|
|
@@ -71,7 +85,7 @@
|
|
|
71
85
|
"additionalProperties": false,
|
|
72
86
|
"required": ["source", "bytesByLanguage", "coverage"],
|
|
73
87
|
"properties": {
|
|
74
|
-
"source": { "
|
|
88
|
+
"source": { "type": "string", "minLength": 1 },
|
|
75
89
|
"bytesByLanguage": {
|
|
76
90
|
"type": "object",
|
|
77
91
|
"additionalProperties": { "type": "integer", "minimum": 0 }
|
|
@@ -83,7 +97,7 @@
|
|
|
83
97
|
"type": "object",
|
|
84
98
|
"properties": {
|
|
85
99
|
"family": { "const": "languages" },
|
|
86
|
-
"source": { "
|
|
100
|
+
"source": { "type": "string", "minLength": 1 }
|
|
87
101
|
}
|
|
88
102
|
}
|
|
89
103
|
]
|
|
@@ -104,7 +118,7 @@
|
|
|
104
118
|
"type": "object",
|
|
105
119
|
"properties": {
|
|
106
120
|
"family": { "const": "contributor_source" },
|
|
107
|
-
"source": { "
|
|
121
|
+
"source": { "type": "string", "minLength": 1 }
|
|
108
122
|
}
|
|
109
123
|
}
|
|
110
124
|
]
|
|
@@ -150,7 +164,7 @@
|
|
|
150
164
|
"downstreamImpact": { "type": ["string", "null"] }
|
|
151
165
|
}
|
|
152
166
|
},
|
|
153
|
-
"
|
|
167
|
+
"sourceCoverageEntry": {
|
|
154
168
|
"allOf": [
|
|
155
169
|
{ "$ref": "#/$defs/coverageEntry" },
|
|
156
170
|
{
|
|
@@ -319,7 +333,7 @@
|
|
|
319
333
|
"additionalProperties": false,
|
|
320
334
|
"required": ["source", "totalCount", "nodes"],
|
|
321
335
|
"properties": {
|
|
322
|
-
"source": { "
|
|
336
|
+
"source": { "type": "string", "minLength": 1 },
|
|
323
337
|
"totalCount": { "type": "integer", "minimum": 0 },
|
|
324
338
|
"nodes": {
|
|
325
339
|
"type": "array",
|
|
@@ -378,7 +392,7 @@
|
|
|
378
392
|
"additionalProperties": false,
|
|
379
393
|
"required": ["source", "totalCount", "conclusions", "runs"],
|
|
380
394
|
"properties": {
|
|
381
|
-
"source": { "
|
|
395
|
+
"source": { "type": "string", "minLength": 1 },
|
|
382
396
|
"totalCount": { "type": ["integer", "null"], "minimum": 0 },
|
|
383
397
|
"conclusions": {
|
|
384
398
|
"type": "object",
|
|
@@ -434,7 +448,7 @@
|
|
|
434
448
|
"type": "object",
|
|
435
449
|
"properties": {
|
|
436
450
|
"family": { "const": "pr_open_diff" },
|
|
437
|
-
"source": { "
|
|
451
|
+
"source": { "type": "string", "minLength": 1 },
|
|
438
452
|
"status": { "enum": ["available", "partial", "unavailable", "rate_limited"] }
|
|
439
453
|
}
|
|
440
454
|
}
|
|
@@ -447,7 +461,7 @@
|
|
|
447
461
|
"type": "object",
|
|
448
462
|
"properties": {
|
|
449
463
|
"family": { "const": "review_threads" },
|
|
450
|
-
"source": { "
|
|
464
|
+
"source": { "type": "string", "minLength": 1 }
|
|
451
465
|
}
|
|
452
466
|
}
|
|
453
467
|
]
|
|
@@ -459,7 +473,7 @@
|
|
|
459
473
|
"type": "object",
|
|
460
474
|
"properties": {
|
|
461
475
|
"family": { "const": "workflow_runs" },
|
|
462
|
-
"source": { "
|
|
476
|
+
"source": { "type": "string", "minLength": 1 }
|
|
463
477
|
}
|
|
464
478
|
}
|
|
465
479
|
]
|
|
@@ -1400,16 +1400,22 @@ export async function writeAnalysisArtifacts(outDir, paths, artifacts, { disable
|
|
|
1400
1400
|
}
|
|
1401
1401
|
|
|
1402
1402
|
function collectionCoverageMarkdown(sourceBundle) {
|
|
1403
|
+
const source = sourceBundle.source;
|
|
1404
|
+
const sourceLabel = source?.label
|
|
1405
|
+
? `${source.label}${source.kind ? ` (${source.kind})` : ""}`
|
|
1406
|
+
: "not recorded";
|
|
1403
1407
|
const lines = [
|
|
1404
1408
|
"",
|
|
1405
1409
|
"## Collection Coverage",
|
|
1406
1410
|
"",
|
|
1411
|
+
`Source: ${sourceLabel}`,
|
|
1412
|
+
"",
|
|
1407
1413
|
`Overall collection coverage: ${sourceBundle.coverage.status}`,
|
|
1408
1414
|
"",
|
|
1409
|
-
"
|
|
1415
|
+
"Source families:",
|
|
1410
1416
|
];
|
|
1411
1417
|
|
|
1412
|
-
for (const family of sourceBundle.coverage.
|
|
1418
|
+
for (const family of sourceBundle.coverage.sourceFamilies ?? []) {
|
|
1413
1419
|
const diagnostics = (family.diagnostics ?? []).length ? `; diagnostics: ${family.diagnostics.join(" | ")}` : "";
|
|
1414
1420
|
const impact = family.downstreamImpact ? `; impact: ${family.downstreamImpact}` : "";
|
|
1415
1421
|
lines.push(`- ${family.family}: ${family.status} (${family.attempts ?? 1} attempt(s))${diagnostics}${impact}`);
|
|
@@ -1422,6 +1428,7 @@ function collectionCoverageMarkdown(sourceBundle) {
|
|
|
1422
1428
|
function attachCollectionCoverage(report, sourceBundle) {
|
|
1423
1429
|
return {
|
|
1424
1430
|
...report,
|
|
1431
|
+
source: sourceBundle.source,
|
|
1425
1432
|
collectionCoverage: sourceBundle.coverage,
|
|
1426
1433
|
artifactSensitivity: "Generated artifacts may include repository names, PR URLs, titles, file paths, comment metadata, contributor-source metadata, curated CSV evidence, and coverage diagnostics. Raw contributor file contents and individual contributor rankings are not emitted. Treat artifacts as local/private unless intentionally shared.",
|
|
1427
1434
|
};
|
|
@@ -1607,7 +1614,7 @@ function coverageLine(family) {
|
|
|
1607
1614
|
}
|
|
1608
1615
|
|
|
1609
1616
|
function coverageCaveats(coverage) {
|
|
1610
|
-
return (coverage?.
|
|
1617
|
+
return (coverage?.sourceFamilies ?? []).filter(family => {
|
|
1611
1618
|
const diagnostics = (family.diagnostics ?? []).filter(Boolean);
|
|
1612
1619
|
return family.status !== "available" || diagnostics.length > 0;
|
|
1613
1620
|
});
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
redactDiagnostic,
|
|
19
19
|
} from "./coverage.js";
|
|
20
20
|
|
|
21
|
-
export const
|
|
21
|
+
export const SOURCE_BUNDLE_VERSION = "source-bundle.v1";
|
|
22
22
|
|
|
23
23
|
const REPOSITORY_SLUG = /^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/;
|
|
24
24
|
function parseRepositoryInput(input) {
|
|
@@ -507,7 +507,7 @@ export async function collectGitHubSourceBundle({
|
|
|
507
507
|
pullRequests.push(pr);
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
-
const
|
|
510
|
+
const sourceFamilies = [
|
|
511
511
|
repositoryCoverage,
|
|
512
512
|
languagesAttempt.coverage,
|
|
513
513
|
inventoryCoverage,
|
|
@@ -542,8 +542,12 @@ export async function collectGitHubSourceBundle({
|
|
|
542
542
|
];
|
|
543
543
|
|
|
544
544
|
return {
|
|
545
|
-
schemaVersion:
|
|
545
|
+
schemaVersion: SOURCE_BUNDLE_VERSION,
|
|
546
546
|
collectedAt,
|
|
547
|
+
source: {
|
|
548
|
+
kind: "github",
|
|
549
|
+
label: "GitHub live collection",
|
|
550
|
+
},
|
|
547
551
|
collector: {
|
|
548
552
|
name: "github-live-collector",
|
|
549
553
|
provider: provider.kind ?? "custom",
|
|
@@ -557,8 +561,8 @@ export async function collectGitHubSourceBundle({
|
|
|
557
561
|
source: "gh pr list --state merged --search \"is:merged sort:merged-desc\"",
|
|
558
562
|
},
|
|
559
563
|
coverage: {
|
|
560
|
-
status: buildCoverageSummary(
|
|
561
|
-
|
|
564
|
+
status: buildCoverageSummary(sourceFamilies),
|
|
565
|
+
sourceFamilies,
|
|
562
566
|
},
|
|
563
567
|
languageDistribution: {
|
|
564
568
|
source: "rest:/repos/{owner}/{repo}/languages",
|
|
@@ -247,17 +247,17 @@ function commentSourcesCsv(report, analysisFilter) {
|
|
|
247
247
|
|
|
248
248
|
function collectionCoverageCsv(collectionCoverage, analysisFilter) {
|
|
249
249
|
const headers = [
|
|
250
|
-
"
|
|
250
|
+
"source_family",
|
|
251
251
|
"status",
|
|
252
252
|
"attempts",
|
|
253
253
|
"source",
|
|
254
254
|
"diagnostics",
|
|
255
255
|
"downstream_impact",
|
|
256
256
|
];
|
|
257
|
-
const rows = [...(collectionCoverage?.
|
|
257
|
+
const rows = [...(collectionCoverage?.sourceFamilies ?? [])]
|
|
258
258
|
.sort((left, right) => String(left.family).localeCompare(String(right.family)))
|
|
259
259
|
.map(family => ({
|
|
260
|
-
|
|
260
|
+
source_family: family.family,
|
|
261
261
|
status: family.status,
|
|
262
262
|
attempts: family.attempts ?? 1,
|
|
263
263
|
source: family.source,
|
|
@@ -283,8 +283,13 @@ function repositoryLabel(report) {
|
|
|
283
283
|
: "unknown repository";
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
+
function sourceLabel(source) {
|
|
287
|
+
if (!source?.label) return "not recorded";
|
|
288
|
+
return `${source.label}${source.kind ? ` (${source.kind})` : ""}`;
|
|
289
|
+
}
|
|
290
|
+
|
|
286
291
|
function formatCoverageFamilies(collectionCoverage) {
|
|
287
|
-
const families = collectionCoverage?.
|
|
292
|
+
const families = collectionCoverage?.sourceFamilies ?? [];
|
|
288
293
|
if (!families.length) return "- No collection coverage families were recorded.";
|
|
289
294
|
return families
|
|
290
295
|
.map(family => {
|
|
@@ -419,12 +424,14 @@ export function renderRepositoryFrictionMethodology({
|
|
|
419
424
|
}) {
|
|
420
425
|
const selection = sourceBundle?.selection ?? {};
|
|
421
426
|
const collectionCoverage = sourceBundle?.coverage ?? report.collectionCoverage;
|
|
427
|
+
const source = sourceBundle?.source ?? report.source;
|
|
422
428
|
|
|
423
429
|
return `${[
|
|
424
430
|
`# Methodology: ${repositoryLabel(report)}`,
|
|
425
431
|
"",
|
|
426
432
|
`Report version: ${report.reportVersion}`,
|
|
427
433
|
`Metric version: ${report.metricVersion}`,
|
|
434
|
+
`Source: ${sourceLabel(source)}`,
|
|
428
435
|
`Repository: ${repositoryLabel(report)}`,
|
|
429
436
|
`Profile path: ${profilePath ?? "not recorded"}`,
|
|
430
437
|
`Requested pull requests: ${selection.requestedLimit ?? "unknown"}`,
|
|
@@ -434,7 +441,7 @@ export function renderRepositoryFrictionMethodology({
|
|
|
434
441
|
"",
|
|
435
442
|
"## What This Analysis Uses",
|
|
436
443
|
"",
|
|
437
|
-
"The analyzer
|
|
444
|
+
"The analyzer normalizes source-bundle pull request evidence through the supplied profile, computes transparent component metrics, and renders a repository-level report. It does not inspect local working trees, mutate repositories, rank people, or apply recommendations automatically.",
|
|
438
445
|
"",
|
|
439
446
|
"## Pull Request Selection",
|
|
440
447
|
"",
|
|
@@ -1624,6 +1624,9 @@ export function renderRepositoryFrictionMarkdown(report) {
|
|
|
1624
1624
|
"",
|
|
1625
1625
|
`Report version: ${report.reportVersion}`,
|
|
1626
1626
|
`Metric version: ${report.metricVersion}`,
|
|
1627
|
+
...(report.source?.label
|
|
1628
|
+
? [`Source: ${report.source.label}${report.source.kind ? ` (${report.source.kind})` : ""}`]
|
|
1629
|
+
: []),
|
|
1627
1630
|
`Pull requests analyzed: ${report.summary?.pullRequests ?? "unknown"}`,
|
|
1628
1631
|
"",
|
|
1629
1632
|
...analysisFilterLines,
|
|
@@ -1641,7 +1644,7 @@ export function renderRepositoryFrictionMarkdown(report) {
|
|
|
1641
1644
|
"",
|
|
1642
1645
|
"## How To Read This Report",
|
|
1643
1646
|
"",
|
|
1644
|
-
"- Observed evidence is measured from
|
|
1647
|
+
"- Observed evidence is measured from source-bundle evidence and repository-profile classifications.",
|
|
1645
1648
|
"- Interpretation is the analyzer's explanation of what the observed evidence suggests.",
|
|
1646
1649
|
"- Recommendation is a workflow intervention to consider; the report does not modify repositories.",
|
|
1647
1650
|
"- Confidence and caveats call out outliers, missing coverage, and evidence-quality limits before you act.",
|
|
@@ -1731,7 +1734,7 @@ export function renderRepositoryFrictionMarkdown(report) {
|
|
|
1731
1734
|
: "- No profile suggestion thresholds were triggered by this report's PR class, role, functional-surface, or workflow-coverage evidence.",
|
|
1732
1735
|
"- Bottlenecks are ranked by their strongest representative observed signal, with stable category order only used to break ties.",
|
|
1733
1736
|
"- Recommendations are inferred from transparent component evidence and representative PR examples; they are not automated changes.",
|
|
1734
|
-
"- Missing or partial
|
|
1737
|
+
"- Missing or partial source evidence remains visible in coverage tables rather than being inferred from unrelated fields.",
|
|
1735
1738
|
...(hasConfiguredWorkflowContext(report.configuredWorkflow)
|
|
1736
1739
|
? ["- Configured workflow context is user-configured repository-profile context; it does not change scoring, ranking, CSV exports, or PR class matching."]
|
|
1737
1740
|
: []),
|