delivery-friction-analyzer 0.21.2 → 0.22.1
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
CHANGED
|
@@ -131,7 +131,7 @@ Each ranked bottleneck example includes source references, workflow-run conclusi
|
|
|
131
131
|
|
|
132
132
|
Use `--source sample` for the bundled synthetic tutorial data. Use `--source github` for live repository analysis. Existing live commands that pass `--repo`, `--profile`, or `--limit` without `--source` still run as GitHub analysis, but new users should prefer the explicit source flag.
|
|
133
133
|
|
|
134
|
-
Use `--dry-run` or `--metadata-only` to
|
|
134
|
+
Use `--dry-run` or `--metadata-only` with `--source github` to run a live GitHub coverage probe that validates repository access, profile JSON, output directory writability, and sampled API coverage without writing full report artifacts. The output directory may be created during this check, and a temporary probe file may be written and removed.
|
|
135
135
|
|
|
136
136
|
Use `--no-csv` when you want the Markdown, JSON, source, normalized, metrics, and methodology artifacts without spreadsheet-friendly CSV exports.
|
|
137
137
|
|
|
@@ -139,7 +139,7 @@ Use `--exclude-pr-class <class>` to remove a configured PR class from downstream
|
|
|
139
139
|
|
|
140
140
|
Use `--json` when automation needs the full machine-readable completion receipt on stdout.
|
|
141
141
|
|
|
142
|
-
Use `--interactive` only in a terminal when you want prompts. When combined with `--json`, prompts and progress stay off stdout so the final completion receipt remains parseable JSON.
|
|
142
|
+
Use `--interactive` only in a terminal when you want prompts for live GitHub setup. Enter `?` at supported prompts for contextual help without restarting the flow. When combined with `--json`, prompts and progress stay off stdout so the final completion receipt remains parseable JSON.
|
|
143
143
|
|
|
144
144
|
## How To Read A Report
|
|
145
145
|
|
|
@@ -28,7 +28,7 @@ The command reads local `friction-metrics.v1` JSON and writes deterministic `fri
|
|
|
28
28
|
- `source`: optional source-bundle provenance copied into live-generated report
|
|
29
29
|
artifacts, including `kind` and a human-readable label.
|
|
30
30
|
- `targetRepository`: analyzed repository identity; live analysis sample size is encoded as `targetRepository.analysisPullRequestLimit` from collection metadata.
|
|
31
|
-
- `analysisFilter`: optional metadata for explicit filters applied before metrics computation, including excluded PR classes and before/after PR counts.
|
|
31
|
+
- `analysisFilter`: optional metadata for explicit filters applied before metrics computation, including excluded PR classes and before/after PR counts. Live dry-run completion receipts may also include requested `analysisFilter.excludedPrClasses` with null before/after counts because dry-run coverage probes do not compute filtered metrics or report artifacts.
|
|
32
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.
|
|
33
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
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.
|
|
@@ -128,7 +128,7 @@ Full live analysis writes `methodology.md` as a hybrid artifact: stable explanat
|
|
|
128
128
|
|
|
129
129
|
The methodology artifact should stay aligned with this contract and the Markdown methodology summary, but it may be more detailed than the main report.
|
|
130
130
|
|
|
131
|
-
When PR class filtering is applied, `source-bundle.json` remains the full collected sample, while `normalized.json`, `metrics-summary.json`, `friction-report.json`, `friction-report.md`, `methodology.md`, and CSV exports describe the filtered sample. The methodology and CLI completion output must name the excluded PR classes and show the filtered PR count. If filtering excludes every collected PR, the analysis must fail without writing complete-looking empty reports.
|
|
131
|
+
When PR class filtering is applied to a full analysis, `source-bundle.json` remains the full collected sample, while `normalized.json`, `metrics-summary.json`, `friction-report.json`, `friction-report.md`, `methodology.md`, and CSV exports describe the filtered sample. The methodology and CLI completion output must name the excluded PR classes and show the filtered PR count. If filtering excludes every collected PR, the analysis must fail without writing complete-looking empty reports. Live dry-run receipts record requested PR class exclusions without a filtered count because dry-run does not build normalized metrics.
|
|
132
132
|
|
|
133
133
|
## CSV Evidence Exports
|
|
134
134
|
|
|
@@ -143,7 +143,7 @@ Minimum CSV column groups:
|
|
|
143
143
|
- `comment-sources.csv`: source name, total comments, bot/scanner classification, human/author classification, and share of all comments.
|
|
144
144
|
- `collection-coverage.csv`: source family, status, attempts, source label, diagnostics, and downstream impact.
|
|
145
145
|
|
|
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.
|
|
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 or sample-bundle coverage should keep source or coverage labels nearby so spreadsheet readers can tell unavailable evidence apart from observed zeroes. `pr-metrics.csv` `review_threads` is populated when normalized review-thread source evidence is available, including synthetic sample evidence and live GraphQL review-thread evidence. CSVs must not include raw comment bodies, raw workflow logs, tokens, secret-bearing environment details, or individual contributor/reviewer rankings.
|
|
147
147
|
|
|
148
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.
|
|
149
149
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# Run Presets
|
|
2
2
|
|
|
3
|
-
Run presets are optional local JSON files for reusing CLI run settings. They are intended for rerunning the same analysis without re-answering interactive prompts.
|
|
3
|
+
Run presets are optional local JSON files for reusing live GitHub CLI run settings. They are intended for rerunning the same live analysis without re-answering interactive prompts. The bundled `--source sample` tutorial path does not save or load presets.
|
|
4
4
|
|
|
5
|
-
Repository meaning stays in repository profiles. Put file rules, PR class rules, workflow context, branch or release strategy, and contributor-source declarations in a repository profile. A run preset may only point at a profile and store run inputs or preferences such as the target repository,
|
|
5
|
+
Repository meaning stays in repository profiles. Put file rules, PR class rules, workflow context, branch or release strategy, and contributor-source declarations in a repository profile. A run preset may only point at a profile and store live run inputs or preferences such as the target repository, pull request limit, output directory, dry-run coverage-probe mode, CSV preference, JSON completion preference, validation-target mode, and requested PR class exclusions.
|
|
6
6
|
|
|
7
7
|
When a preset supplies `profilePath`, the CLI validates that referenced profile exactly as if it had been passed with `--profile`. Presets do not copy, freeze, or override profile rules; fixing profile validation failures means editing the referenced profile or choosing a different profile path.
|
|
8
8
|
|
|
9
9
|
## Save A Preset
|
|
10
10
|
|
|
11
|
-
Interactive setup asks whether to save a local run preset near the end of the prompt flow. If you answer yes, you choose the preset path explicitly. The CLI does not invent a global or cloud-synced preset location.
|
|
11
|
+
Interactive live GitHub setup asks whether to save a local run preset near the end of the prompt flow. If you answer yes, you choose the preset path explicitly. The CLI does not invent a global or cloud-synced preset location.
|
|
12
12
|
|
|
13
13
|
Saving a preset may overwrite an existing regular file at that path, but the path must not be a directory, symbolic link, or other special file.
|
|
14
14
|
|
package/package.json
CHANGED
package/release-log.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
### 2026-06-28 — CLI Artifact Truthfulness
|
|
6
|
+
|
|
7
|
+
- What changed: Live dry-run JSON receipts now show requested PR-class exclusions without implying filtered metrics were computed, and `pr-metrics.csv` fills `review_threads` when sample or live review-thread evidence is available.
|
|
8
|
+
- Why it matters: Maintainers and scripts can trust completion receipts and CSV evidence to reflect accepted filters and available review-thread counts.
|
|
9
|
+
- Who is affected: Maintainers and users reading JSON completion receipts, CSV exports, and contract docs from sample or live GitHub analysis.
|
|
10
|
+
- Action needed: None.
|
|
11
|
+
- PR: https://github.com/hannasdev/delivery-friction-analyzer/pull/68
|
|
12
|
+
|
|
13
|
+
### 2026-06-28 — CLI Command Help Clarity
|
|
14
|
+
|
|
15
|
+
- What changed: CLI help now separates the bundled sample, live GitHub analysis, live coverage probes, output controls, interactive setup, and reusable live-run presets, with mode-specific help for `--source sample --help` and `--source github --help`.
|
|
16
|
+
- Why it matters: First-time users and maintainers can choose the right workflow before running into rejected flag combinations, and interactive setup can explain supported prompts without restarting the flow.
|
|
17
|
+
- Who is affected: Users trying the bundled sample, maintainers running live GitHub analysis or dry-run coverage probes, and contributors reviewing CLI setup behavior.
|
|
18
|
+
- Action needed: None.
|
|
19
|
+
- PR: https://github.com/hannasdev/delivery-friction-analyzer/pull/67
|
|
20
|
+
|
|
5
21
|
### 2026-06-27 — First-Run Profile Prompt Clarity
|
|
6
22
|
|
|
7
23
|
- What changed: Interactive GitHub setup now explains starter-profile limitations, gives clearer guidance before adding Conventional Commit PR class rules, and only prints the starter-profile completion reminder when a new profile was created.
|
|
@@ -135,30 +135,84 @@ const CSV_ARTIFACT_KEYS = new Set([
|
|
|
135
135
|
export const USAGE = `Usage:
|
|
136
136
|
delivery-friction-analyzer --source sample --out <directory>
|
|
137
137
|
delivery-friction-analyzer --source github --repo <owner/name> --limit <1-100> --profile <path> --out <directory>
|
|
138
|
-
delivery-friction-analyzer --repo <owner/name> --limit <1-100> --profile <path> --out <directory>
|
|
139
|
-
delivery-friction-analyzer --repo <owner/name> --limit <1-100> --profile <path> --out <directory> --dry-run
|
|
140
138
|
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
Workflows:
|
|
140
|
+
Sample tutorial:
|
|
141
|
+
Uses bundled synthetic data. Accepts output controls only.
|
|
142
|
+
delivery-friction-analyzer --source sample --out reports/tutorial
|
|
143
|
+
|
|
144
|
+
Live GitHub analysis:
|
|
145
|
+
Collects repository data from GitHub using a repository profile.
|
|
146
|
+
delivery-friction-analyzer --source github --repo owner/name --limit 30 --profile path/to/profile.json --out reports/owner-name
|
|
147
|
+
|
|
148
|
+
Live GitHub coverage probe:
|
|
149
|
+
--dry-run and --metadata-only validate live GitHub access, profile JSON, output writability, and sampled API coverage without report artifacts.
|
|
150
|
+
delivery-friction-analyzer --source github --repo owner/name --limit 30 --profile path/to/profile.json --out reports/owner-name --dry-run
|
|
151
|
+
|
|
152
|
+
Output controls:
|
|
153
|
+
--out <directory> Output directory for generated artifacts.
|
|
154
|
+
--json Print the machine-readable completion receipt to stdout.
|
|
155
|
+
--no-json Disable JSON completion output.
|
|
156
|
+
--csv Enable curated CSV evidence exports when a preset disabled them.
|
|
157
|
+
--no-csv Suppress curated CSV evidence exports.
|
|
158
|
+
|
|
159
|
+
Help:
|
|
160
|
+
--source sample --help Show sample-mode options.
|
|
161
|
+
--source github --help Show live GitHub, dry-run, interactive, and preset options.
|
|
162
|
+
`;
|
|
163
|
+
|
|
164
|
+
export const SAMPLE_USAGE = `Usage:
|
|
165
|
+
delivery-friction-analyzer --source sample --out <directory>
|
|
166
|
+
|
|
167
|
+
Sample tutorial:
|
|
168
|
+
Uses bundled synthetic data and never calls GitHub.
|
|
169
|
+
Sample mode writes the same report bundle shape as a live analysis for tutorial use.
|
|
170
|
+
|
|
171
|
+
Sample output controls:
|
|
172
|
+
--out <directory> Required output directory for generated sample artifacts.
|
|
173
|
+
--json Print the machine-readable completion receipt to stdout.
|
|
174
|
+
--no-json Disable JSON completion output.
|
|
175
|
+
--csv Enable curated CSV evidence exports.
|
|
176
|
+
--no-csv Suppress curated CSV evidence exports.
|
|
177
|
+
|
|
178
|
+
Live-only flags are not supported with --source sample:
|
|
179
|
+
--repo, --limit, --profile, --dry-run, --metadata-only, --validation-target,
|
|
180
|
+
--interactive, --preset, --save-preset, --exclude-pr-class, --allow-product-repository
|
|
181
|
+
`;
|
|
182
|
+
|
|
183
|
+
export const GITHUB_USAGE = `Usage:
|
|
184
|
+
delivery-friction-analyzer --source github --repo <owner/name> --limit <1-100> --profile <path> --out <directory>
|
|
185
|
+
delivery-friction-analyzer --source github --repo <owner/name> --limit <1-100> --profile <path> --out <directory> --dry-run
|
|
186
|
+
delivery-friction-analyzer --interactive --source github
|
|
187
|
+
|
|
188
|
+
Live GitHub analysis:
|
|
143
189
|
--repo <owner/name> Target GitHub repository to analyze.
|
|
144
190
|
--limit <1-100> Latest merged pull request count.
|
|
145
191
|
--profile <path> Repository profile JSON used for file role classification.
|
|
146
192
|
--out <directory> Output directory for generated artifacts.
|
|
147
|
-
|
|
148
|
-
|
|
193
|
+
|
|
194
|
+
Live GitHub coverage probes:
|
|
195
|
+
--dry-run Validate live GitHub access, profile JSON, output writability, and sampled API coverage without writing report artifacts.
|
|
149
196
|
--metadata-only Alias for --dry-run.
|
|
197
|
+
--no-dry-run Disable dry-run mode when a preset enabled it.
|
|
198
|
+
|
|
199
|
+
Live filters and metadata:
|
|
150
200
|
--validation-target Mark output metadata as an internal validation run; does not bypass target validation.
|
|
151
201
|
--no-validation-target Disable validation-target mode when a preset enabled it.
|
|
152
202
|
--allow-product-repository
|
|
153
203
|
Explicit live-analysis override for intentional self-analysis of this product repository.
|
|
154
|
-
--exclude-pr-class <cls> Exclude a PR class from normalized, metrics, report, methodology, and CSV artifacts. Repeat or comma-separate values.
|
|
155
|
-
|
|
156
|
-
|
|
204
|
+
--exclude-pr-class <cls> Exclude a configured PR class from downstream normalized, metrics, report, methodology, and CSV artifacts. Repeat or comma-separate values.
|
|
205
|
+
|
|
206
|
+
Output controls:
|
|
157
207
|
--json Print the machine-readable completion receipt to stdout.
|
|
158
208
|
--no-json Disable JSON completion output when a preset enabled it.
|
|
159
|
-
--
|
|
160
|
-
--
|
|
161
|
-
|
|
209
|
+
--csv Enable curated CSV evidence exports when a preset disabled them.
|
|
210
|
+
--no-csv Suppress curated CSV evidence exports.
|
|
211
|
+
|
|
212
|
+
Interactive setup and presets:
|
|
213
|
+
--interactive Prompt for missing live GitHub run options in a terminal; enter ? at supported prompts for contextual help.
|
|
214
|
+
--preset <path> Load reusable live GitHub run settings from a saved preset. Explicit CLI flags override preset values.
|
|
215
|
+
--save-preset <path> Save reusable live GitHub run settings for non-interactive reruns.
|
|
162
216
|
`;
|
|
163
217
|
|
|
164
218
|
export const SOURCE_SELECTION_GUIDANCE = `Choose what to analyze.
|
|
@@ -199,6 +253,13 @@ function explicitCliOptionKeys(argv) {
|
|
|
199
253
|
}
|
|
200
254
|
|
|
201
255
|
export function parseAnalyzeGithubArgs(argv) {
|
|
256
|
+
if (hasHelpOption(argv)) {
|
|
257
|
+
const parsed = { help: true };
|
|
258
|
+
const source = sourceForHelp(argv);
|
|
259
|
+
if (source !== undefined) parsed.source = source;
|
|
260
|
+
return attachOptionSource(parsed, "explicitCliOptions", new Set(source === undefined ? [] : ["source"]));
|
|
261
|
+
}
|
|
262
|
+
|
|
202
263
|
const options = {
|
|
203
264
|
dryRun: false,
|
|
204
265
|
isValidationTarget: false,
|
|
@@ -209,9 +270,6 @@ export function parseAnalyzeGithubArgs(argv) {
|
|
|
209
270
|
const explicitOptions = explicitCliOptionKeys(argv);
|
|
210
271
|
for (let index = 0; index < argv.length; index += 1) {
|
|
211
272
|
const arg = argv[index];
|
|
212
|
-
if (arg === "--help" || arg === "-h") {
|
|
213
|
-
return { help: true };
|
|
214
|
-
}
|
|
215
273
|
if (!arg.startsWith("--")) {
|
|
216
274
|
throw new Error(`Unexpected argument: ${arg}`);
|
|
217
275
|
}
|
|
@@ -268,6 +326,25 @@ export function parseAnalyzeGithubArgs(argv) {
|
|
|
268
326
|
return attachOptionSource(parsed, "explicitCliOptions", explicitOptions);
|
|
269
327
|
}
|
|
270
328
|
|
|
329
|
+
function hasHelpOption(argv) {
|
|
330
|
+
return argv.includes("--help") || argv.includes("-h");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function sourceForHelp(argv) {
|
|
334
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
335
|
+
if (argv[index] !== "--source") continue;
|
|
336
|
+
const source = argv[index + 1];
|
|
337
|
+
if (source === "sample" || source === "github") return source;
|
|
338
|
+
}
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function usageForOptions(options) {
|
|
343
|
+
if (options?.source === "sample") return SAMPLE_USAGE;
|
|
344
|
+
if (options?.source === "github") return GITHUB_USAGE;
|
|
345
|
+
return USAGE;
|
|
346
|
+
}
|
|
347
|
+
|
|
271
348
|
function normalizeExcludedPrClasses(values) {
|
|
272
349
|
return [...new Set(values
|
|
273
350
|
.flatMap(value => String(value).split(","))
|
|
@@ -537,7 +614,7 @@ function rejectSampleLiveOptions(options) {
|
|
|
537
614
|
incompatible.push("--exclude-pr-class");
|
|
538
615
|
}
|
|
539
616
|
if (!incompatible.length) return;
|
|
540
|
-
throw new Error(`--source sample cannot be combined with live GitHub option(s): ${incompatible.join(", ")}.
|
|
617
|
+
throw new Error(`--source sample cannot be combined with live GitHub option(s): ${incompatible.join(", ")}. Allowed sample output controls are --out, --json, --no-json, --csv, and --no-csv. Use --source github for live repository, dry-run, interactive, preset, validation, and PR-class filtering options.`);
|
|
541
618
|
}
|
|
542
619
|
|
|
543
620
|
function targetInputFromRepositorySlug(repository) {
|
|
@@ -590,21 +667,22 @@ function formatChoiceList(choices) {
|
|
|
590
667
|
}
|
|
591
668
|
|
|
592
669
|
function formatInteractivePrompt(prompt) {
|
|
670
|
+
const helpSuffix = prompt.help ? " Enter ? for help" : "";
|
|
593
671
|
const suffix = prompt.defaultValue === undefined
|
|
594
672
|
? ""
|
|
595
673
|
: Array.isArray(prompt.defaultValue)
|
|
596
674
|
? (prompt.defaultValue.length ? ` [${prompt.defaultValue.join(",")}]` : "")
|
|
597
675
|
: ` [${prompt.defaultValue}]`;
|
|
598
676
|
if (prompt.type === "confirm") {
|
|
599
|
-
return `${prompt.message}${prompt.defaultValue ? " [Y/n]" : " [y/N]"} `;
|
|
677
|
+
return `${prompt.message}${helpSuffix}${prompt.defaultValue ? " [Y/n]" : " [y/N]"} `;
|
|
600
678
|
}
|
|
601
679
|
if (prompt.type === "multi-select" && prompt.choices?.length) {
|
|
602
|
-
return `${prompt.message} (${prompt.choices.map(choiceValue).join(",")})${suffix}: `;
|
|
680
|
+
return `${prompt.message}${helpSuffix} (${prompt.choices.map(choiceValue).join(",")})${suffix}: `;
|
|
603
681
|
}
|
|
604
682
|
if (prompt.type === "select" && prompt.choices?.length) {
|
|
605
|
-
return `${prompt.message}\n${formatChoiceList(prompt.choices)}\nChoose a number or identifier${suffix}: `;
|
|
683
|
+
return `${prompt.message}${helpSuffix}\n${formatChoiceList(prompt.choices)}\nChoose a number or identifier${suffix}: `;
|
|
606
684
|
}
|
|
607
|
-
return `${prompt.message}${suffix}: `;
|
|
685
|
+
return `${prompt.message}${helpSuffix}${suffix}: `;
|
|
608
686
|
}
|
|
609
687
|
|
|
610
688
|
function createTerminalPromptAdapter({ input, output }) {
|
|
@@ -632,6 +710,16 @@ async function callPromptAdapter(promptAdapter, prompt) {
|
|
|
632
710
|
async function askUntilValid(promptAdapter, prompt, { normalize, validate, output }) {
|
|
633
711
|
for (;;) {
|
|
634
712
|
const raw = await callPromptAdapter(promptAdapter, prompt);
|
|
713
|
+
if (String(raw ?? "").trim() === "?" && prompt.help) {
|
|
714
|
+
if (typeof promptAdapter.writeHelp === "function") {
|
|
715
|
+
promptAdapter.writeHelp(prompt.help);
|
|
716
|
+
} else if (typeof promptAdapter.writeError === "function") {
|
|
717
|
+
promptAdapter.writeError(prompt.help);
|
|
718
|
+
} else if (output?.write) {
|
|
719
|
+
output.write(`${prompt.help}\n`);
|
|
720
|
+
}
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
635
723
|
try {
|
|
636
724
|
const value = normalize(raw, prompt);
|
|
637
725
|
await validate(value);
|
|
@@ -950,6 +1038,7 @@ async function promptConventionalCommitPrClassPreset(promptAdapter, output, prof
|
|
|
950
1038
|
id: "addConventionalCommitPrClasses",
|
|
951
1039
|
type: "confirm",
|
|
952
1040
|
message,
|
|
1041
|
+
help: "Optional profile setup: add reusable title rules when Conventional Commit prefixes match this repository. This changes profile classification only, not scoring or GitHub collection.",
|
|
953
1042
|
defaultValue: false,
|
|
954
1043
|
}, {
|
|
955
1044
|
output,
|
|
@@ -991,6 +1080,7 @@ async function promptWorkflowField(promptAdapter, output, { id, message, choices
|
|
|
991
1080
|
id,
|
|
992
1081
|
type: "select",
|
|
993
1082
|
message,
|
|
1083
|
+
help: "Workflow context is configured profile metadata. Choose the closest stable repository practice; it helps report interpretation but is not observed GitHub evidence.",
|
|
994
1084
|
choices: workflowChoices(choices),
|
|
995
1085
|
defaultValue,
|
|
996
1086
|
}, {
|
|
@@ -1030,6 +1120,7 @@ async function promptWorkflowProfileUpdate(promptAdapter, output, profile, { isN
|
|
|
1030
1120
|
id: "releasePrTitleIncludes",
|
|
1031
1121
|
type: "text",
|
|
1032
1122
|
message: `Release PR title includes (blank to skip PR class rule; suggested: ${suggestedReleaseTitle})`,
|
|
1123
|
+
help: "Use this only when release PR titles follow a stable phrase. Blank skips the generated release PR class rule.",
|
|
1033
1124
|
}, {
|
|
1034
1125
|
output,
|
|
1035
1126
|
normalize: normalizeTextAnswer,
|
|
@@ -1044,6 +1135,7 @@ async function promptWorkflowProfileUpdate(promptAdapter, output, profile, { isN
|
|
|
1044
1135
|
id: "updateReleasePrClass",
|
|
1045
1136
|
type: "confirm",
|
|
1046
1137
|
message: "Update existing title-based release PR class rule",
|
|
1138
|
+
help: "Updates the existing title-based release PR class rule in the repository profile. Existing unrelated profile rules are kept.",
|
|
1047
1139
|
defaultValue: false,
|
|
1048
1140
|
}, {
|
|
1049
1141
|
output,
|
|
@@ -1065,6 +1157,7 @@ async function promptWorkflowProfileUpdate(promptAdapter, output, profile, { isN
|
|
|
1065
1157
|
id: "addReleasePrClass",
|
|
1066
1158
|
type: "confirm",
|
|
1067
1159
|
message: "Add release PR class rule from title convention",
|
|
1160
|
+
help: "Adds a title-based release PR class rule to the repository profile when that convention matches this repository.",
|
|
1068
1161
|
defaultValue: true,
|
|
1069
1162
|
}, {
|
|
1070
1163
|
output,
|
|
@@ -1097,6 +1190,7 @@ async function maybeConfigureInteractiveProfile(promptAdapter, output, profileSt
|
|
|
1097
1190
|
id: "createProfile",
|
|
1098
1191
|
type: "confirm",
|
|
1099
1192
|
message: `Create repository profile at this path. ${STARTER_PROFILE_PROMPT_COPY}`,
|
|
1193
|
+
help: "A repository profile is required for live GitHub analysis. Interactive setup can create a starter profile at the path you entered, then you can review or refine it before a full run.",
|
|
1100
1194
|
defaultValue: true,
|
|
1101
1195
|
}, {
|
|
1102
1196
|
output,
|
|
@@ -1111,6 +1205,7 @@ async function maybeConfigureInteractiveProfile(promptAdapter, output, profileSt
|
|
|
1111
1205
|
id: "configureWorkflow",
|
|
1112
1206
|
type: "confirm",
|
|
1113
1207
|
message: "Configure repository workflow profile fields",
|
|
1208
|
+
help: "Workflow profile fields are configured context for report interpretation. Choose yes to review merge, release, and branch strategy prompts; choose no to keep the existing profile values.",
|
|
1114
1209
|
defaultValue: false,
|
|
1115
1210
|
}, {
|
|
1116
1211
|
output,
|
|
@@ -1220,6 +1315,7 @@ export async function collectInteractiveAnalyzeGithubOptions(options, {
|
|
|
1220
1315
|
id: "repository",
|
|
1221
1316
|
type: "text",
|
|
1222
1317
|
message: "Target GitHub repository",
|
|
1318
|
+
help: "Enter the live GitHub repository as owner/name. Sample mode is not part of interactive setup; use --source sample --out <directory> for the bundled tutorial.",
|
|
1223
1319
|
}, {
|
|
1224
1320
|
output,
|
|
1225
1321
|
normalize: normalizeTextAnswer,
|
|
@@ -1232,6 +1328,7 @@ export async function collectInteractiveAnalyzeGithubOptions(options, {
|
|
|
1232
1328
|
id: "limit",
|
|
1233
1329
|
type: "integer",
|
|
1234
1330
|
message: "Latest merged pull request count",
|
|
1331
|
+
help: "Choose how many recent merged pull requests to collect from live GitHub. Smaller limits are faster; larger limits give reports more evidence.",
|
|
1235
1332
|
defaultValue: 30,
|
|
1236
1333
|
}, {
|
|
1237
1334
|
output,
|
|
@@ -1245,6 +1342,7 @@ export async function collectInteractiveAnalyzeGithubOptions(options, {
|
|
|
1245
1342
|
id: "profilePath",
|
|
1246
1343
|
type: "path",
|
|
1247
1344
|
message: "Repository profile path",
|
|
1345
|
+
help: "Enter a local repository-profile.v1 JSON path. If the path does not exist, interactive setup can create a starter profile there.",
|
|
1248
1346
|
});
|
|
1249
1347
|
profileState = prompted;
|
|
1250
1348
|
} else {
|
|
@@ -1284,6 +1382,7 @@ export async function collectInteractiveAnalyzeGithubOptions(options, {
|
|
|
1284
1382
|
id: "outDir",
|
|
1285
1383
|
type: "path",
|
|
1286
1384
|
message: "Output directory",
|
|
1385
|
+
help: "Choose where generated artifacts or dry-run probe files should be written. Interactive setup may create the directory and check that it is writable.",
|
|
1287
1386
|
defaultValue: defaultOutDirForRepository(resolved.repository),
|
|
1288
1387
|
}, {
|
|
1289
1388
|
output,
|
|
@@ -1299,7 +1398,8 @@ export async function collectInteractiveAnalyzeGithubOptions(options, {
|
|
|
1299
1398
|
resolved.dryRun = await askUntilValid(adapter, {
|
|
1300
1399
|
id: "dryRun",
|
|
1301
1400
|
type: "confirm",
|
|
1302
|
-
message: "Run metadata-only
|
|
1401
|
+
message: "Run live GitHub metadata-only coverage probe",
|
|
1402
|
+
help: "A dry run is a live GitHub coverage probe. It validates access, profile JSON, output writability, and sampled API coverage without writing report artifacts.",
|
|
1303
1403
|
defaultValue: false,
|
|
1304
1404
|
}, {
|
|
1305
1405
|
output,
|
|
@@ -1314,6 +1414,7 @@ export async function collectInteractiveAnalyzeGithubOptions(options, {
|
|
|
1314
1414
|
id: "csv",
|
|
1315
1415
|
type: "confirm",
|
|
1316
1416
|
message: "Write CSV evidence files",
|
|
1417
|
+
help: "CSV evidence files are optional spreadsheet-friendly artifacts for full report runs. They are not written during dry-run coverage probes.",
|
|
1317
1418
|
defaultValue: true,
|
|
1318
1419
|
}, {
|
|
1319
1420
|
output,
|
|
@@ -1328,6 +1429,7 @@ export async function collectInteractiveAnalyzeGithubOptions(options, {
|
|
|
1328
1429
|
id: "json",
|
|
1329
1430
|
type: "confirm",
|
|
1330
1431
|
message: "Print completion as JSON",
|
|
1432
|
+
help: "JSON completion writes the final machine-readable receipt to stdout. Prompts and progress stay on stderr so automation can parse stdout.",
|
|
1331
1433
|
defaultValue: false,
|
|
1332
1434
|
}, {
|
|
1333
1435
|
output,
|
|
@@ -1344,6 +1446,7 @@ export async function collectInteractiveAnalyzeGithubOptions(options, {
|
|
|
1344
1446
|
id: "excludedPrClasses",
|
|
1345
1447
|
type: "multi-select",
|
|
1346
1448
|
message: "Exclude PR classes (comma-separated, blank for none)",
|
|
1449
|
+
help: "Optionally exclude configured PR classes from downstream normalized data, metrics, reports, methodology, and CSV artifacts. The source bundle still preserves the full collected sample.",
|
|
1347
1450
|
choices: availablePrClasses,
|
|
1348
1451
|
defaultValue: [],
|
|
1349
1452
|
}, {
|
|
@@ -1362,6 +1465,7 @@ export async function collectInteractiveAnalyzeGithubOptions(options, {
|
|
|
1362
1465
|
id: "saveRunPreset",
|
|
1363
1466
|
type: "confirm",
|
|
1364
1467
|
message: "Save local run preset for non-interactive reruns",
|
|
1468
|
+
help: "Run presets store reusable live GitHub run settings such as repo, profile, limit, output, dry-run preference, JSON/CSV preference, and requested PR class exclusions. Sample mode does not save or load presets.",
|
|
1365
1469
|
defaultValue: false,
|
|
1366
1470
|
}, {
|
|
1367
1471
|
output,
|
|
@@ -1373,6 +1477,7 @@ export async function collectInteractiveAnalyzeGithubOptions(options, {
|
|
|
1373
1477
|
id: "runPresetPath",
|
|
1374
1478
|
type: "path",
|
|
1375
1479
|
message: "Run preset path",
|
|
1480
|
+
help: "Choose a local JSON file path for reusable live GitHub run settings. The path must be a regular file path, not a directory or symlink.",
|
|
1376
1481
|
}, {
|
|
1377
1482
|
output,
|
|
1378
1483
|
normalize: normalizeTextAnswer,
|
|
@@ -1590,6 +1695,15 @@ function summarizeResult({ dryRun, outDir, paths, sourceBundle, metrics, report,
|
|
|
1590
1695
|
return summary;
|
|
1591
1696
|
}
|
|
1592
1697
|
|
|
1698
|
+
function requestedAnalysisFilter(excludedPrClasses = []) {
|
|
1699
|
+
if (!excludedPrClasses.length) return null;
|
|
1700
|
+
return {
|
|
1701
|
+
excludedPrClasses,
|
|
1702
|
+
originalPullRequests: null,
|
|
1703
|
+
filteredPullRequests: null,
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1593
1707
|
function applyPrClassFilter(normalized, excludedPrClasses = []) {
|
|
1594
1708
|
if (!excludedPrClasses.length) return normalized;
|
|
1595
1709
|
const excluded = new Set(excludedPrClasses);
|
|
@@ -1719,7 +1833,7 @@ export async function runAnalyzeGithub(options, {
|
|
|
1719
1833
|
requestedLimit: options.limit,
|
|
1720
1834
|
sampledLimit: collectionLimit,
|
|
1721
1835
|
csv: false,
|
|
1722
|
-
analysisFilter:
|
|
1836
|
+
analysisFilter: requestedAnalysisFilter(options.excludedPrClasses ?? []),
|
|
1723
1837
|
savedProfilePath: options.savedProfilePath,
|
|
1724
1838
|
savedRunPresetPath: options.savedRunPresetPath,
|
|
1725
1839
|
prClassRulesWritten: options.prClassRulesWritten,
|
|
@@ -1883,10 +1997,13 @@ export function formatAnalyzeGithubCompletion(result) {
|
|
|
1883
1997
|
}
|
|
1884
1998
|
|
|
1885
1999
|
if (result.analysisFilter?.excludedPrClasses?.length) {
|
|
1886
|
-
lines.push(
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
2000
|
+
lines.push(`Analysis filter: excluded PR class(es): ${result.analysisFilter.excludedPrClasses.join(", ")}.`);
|
|
2001
|
+
if (Number.isInteger(result.analysisFilter.filteredPullRequests)
|
|
2002
|
+
&& Number.isInteger(result.analysisFilter.originalPullRequests)) {
|
|
2003
|
+
lines.push(`Filtered sample: ${result.analysisFilter.filteredPullRequests} of ${result.analysisFilter.originalPullRequests} collected pull request(s).`);
|
|
2004
|
+
} else if (result.dryRun) {
|
|
2005
|
+
lines.push("Filter application: requested only; dry-run coverage probes do not compute filtered metrics or report artifacts.");
|
|
2006
|
+
}
|
|
1890
2007
|
}
|
|
1891
2008
|
|
|
1892
2009
|
if (result.savedProfilePath) {
|
|
@@ -1938,7 +2055,7 @@ export async function runAnalyzeGithubCli(argv, {
|
|
|
1938
2055
|
try {
|
|
1939
2056
|
const parsedOptions = parseAnalyzeGithubArgs(argv);
|
|
1940
2057
|
if (parsedOptions.help) {
|
|
1941
|
-
stdout.write(
|
|
2058
|
+
stdout.write(usageForOptions(parsedOptions));
|
|
1942
2059
|
return null;
|
|
1943
2060
|
}
|
|
1944
2061
|
if (parsedOptions.source !== undefined) {
|
|
@@ -94,9 +94,12 @@ function hasObservedWorkflowRuns(workflowRuns = {}) {
|
|
|
94
94
|
return (workflowRuns.coverage ?? workflowRuns.workflowRunCoverage) === "observed";
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
function hasObservedReviewThreads(reviewThreads = {}) {
|
|
97
|
+
function hasObservedReviewThreads(reviewThreads = {}, coverage = {}) {
|
|
98
98
|
const source = reviewThreads.source ?? reviewThreads.reviewThreadSource ?? "unavailable";
|
|
99
|
-
|
|
99
|
+
if (String(source).startsWith("graphql")) return true;
|
|
100
|
+
if (String(source).startsWith("not_sampled") || source === "unavailable") return false;
|
|
101
|
+
if (String(source).includes("review_threads")) return true;
|
|
102
|
+
return coverage.status === "available";
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
function hasObservedReviewDecision(reviewDecision = {}) {
|
|
@@ -135,7 +138,7 @@ function prMetricsCsv(metricsSummary, analysisFilter) {
|
|
|
135
138
|
...SCORE_COLUMNS.map(([header]) => header),
|
|
136
139
|
];
|
|
137
140
|
const rows = sortedPullRequests(metricsSummary).map(pr => {
|
|
138
|
-
const observedReviewThreads = hasObservedReviewThreads(pr.review?.threads);
|
|
141
|
+
const observedReviewThreads = hasObservedReviewThreads(pr.review?.threads, pr.coverage?.reviewThreads);
|
|
139
142
|
const observedReviewDecision = hasObservedReviewDecision(pr.review?.decision);
|
|
140
143
|
const observedWorkflowRuns = hasObservedWorkflowRuns(pr.ci?.workflowRuns);
|
|
141
144
|
const row = {
|