Skip to content

feat(cli): gap-report --to files a report where the person chose - #7724

Merged
josephfarina merged 4 commits into
mainfrom
josephfarina/gap-report-target-route
Oct 20, 2026
Merged

josephfarina merged 4 commits into
mainfrom
josephfarina/gap-report-target-route

Conversation

@josephfarina

@josephfarina josephfarina commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Problem

astryx gap-report always sends a report to the configured handlers (or, with none, the owning package's issuesUrl). When a handler is loaded, an integration that names its own tracker in issuesUrl can never receive a report, and nothing tells the person that other places exist.

Specification

architecture:cli-surface INV36 and DEC-9 (the person chooses), with spec:AST-031 FR13 pointing to them; merged in #7749.

Change

  • Default unchanged. Without --to, the report goes where it goes today.
  • --to <package> (API: options.to) sends the report only to that loaded integration's issuesUrl; handlers do not run. Same delivery as the zero-handler fallback: a GitHub issues URL is filed with gh only with --confirm-public (consent_required otherwise); any other URL comes back as routed_only.
  • Unknown choice. Any --to that names no loaded integration with an issuesUrl, including an empty, blank or over-long value, fails with ERR_UNKNOWN_PACKAGE before anything is sent; the message and suggestions list the destinations. to: null in the API counts as absent, like package: null.
  • The person decides. When at least one integration names its own issuesUrl:
    • --list-categories adds meta: {destinations, ask};
    • the receipt of a run without --to adds destinations and ask;
    • text output shows a "Destinations" section with the same fields as the JSON (to, default, issuesUrl) and the line "Ask the person where to file this report, then pass their choice with --to . Never pick a package destination yourself; leave --to out to use the default."
    • Each destination is {to, default, issuesUrl}; the default is {to: null, default: true, issuesUrl: null}.
  • Docs: the command and API docs, the --to option, a new example, and a "Let the person choose where a report goes" section in the debug and gap reports guide. Response-type rows updated (README regenerated).
  • This replaces the earlier automatic copy to the target integration's issuesUrl and its skip rules in this PR; they are gone.

Compatibility

Additive. New option; new optional meta on gap-report.categories and new optional destinations/ask on gap-report.file, present only when a destination exists. Existing fields, statuses and exit codes are unchanged. --to with --list-categories is refused with ERR_INVALID_ARGUMENT, like the other filing options. [feat] patch changeset.

Evidence

Tests that fail on current main and pass here (8):

  • the default route is unchanged and lists the destinations with the "ask the person" line; no destinations field when no integration names an issuesUrl;
  • --to with a GitHub issuesUrl: consent_required and no gh call without consent; with consent, gh issue create --repo acme/widgets; the shared handler never runs;
  • --to with a non-GitHub issuesUrl: routed_only;
  • --to naming an unloaded package, or a loaded integration without an issuesUrl: ERR_UNKNOWN_PACKAGE listing the choices, and the handler never runs;
  • --list-categories lists the destinations in meta, adds none with only the default, and refuses --to;
  • the CLI text output shows the Destinations section and the ask line, and --to --json routes.

After review: an empty, blank or over-long --to gets ERR_UNKNOWN_PACKAGE with the list, and to: null is the default route (4 more tests, failing on the previous head); the text Destinations section now mirrors the JSON fields.

Testing

  • pnpm check:repo, readme:check, typecheck:strict, typecheck:json-api, typecheck:authoring, typecheck:template-docs, cli-api-types-verify, eslint on the changed files
  • gap-report API and command tests (68)
  • vitest run --project node (full node project)

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 20, 2026
@vercel

vercel Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 20, 2026 7:09pm UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Oct 20, 2026, 7:32 PM UTC

@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR

🧪 Sandbox Preview

View Sandbox for this PR

No new or modified components detected.

Bundle Size Summary

No component packages changed.

Accessibility Audit

Status: No accessibility violations detected.

Visual Regression

40 of 482 shot(s) changed.

A change here is a question, not a failure: check whether the after is the
picture you intended. Record that review on the PR. Baseline maintenance is an
explicit dispatch of CI; this report never rewrites the baseline or adds a release gate.

component story theme mode pixels
Selector Indicator Space Bottom Sheet Narrow End RTL neutral light 754,077
Selector Indicator Space Bottom Sheet Wide Start neutral light 740,188
Chat Full AI Chat probe light 230,823
Chat Full AI Chat probe dark 229,805
RichTextEditor Markdown parity: side by side probe dark 94,866
RichTextEditor Markdown parity: side by side probe light 89,762
Collapsible Dividers probe dark 66,445
Collapsible Dividers probe light 66,445
Chat Default neutral dark 36,304
Chat Default neutral light 35,767
Chat Empty State probe light 23,531
Chat Empty State probe dark 22,622
ChatAutoScroll Scroll Behavior Comparison neutral dark 15,378
ChatAutoScroll Scroll Behavior Comparison neutral light 14,107
Chat With Attachments probe light 13,406
Chat With Attachments probe dark 11,232
ProgressBar With Target Mark probe dark 7,895
ProgressBar With Target Mark probe light 7,895
Markdown Default neutral dark 7,573
Markdown Default neutral light 7,566

and 20 more.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval needs a maintainer to provide or identify the governing spec for the new route: spec:AST-031 FR13 and DEC-4 are still authority: draft on main, so what must be settled is whether routing a report about an integration to that integration's own issuesUrl after the handlers, with its consent and skip rules, becomes current authority. Separately, three changes are needed before this can be approved:

  • The own-handler skip matches by package name, so when astryx.config re-exports the integration's gapReport (deduplicated into the project slot), the route still files at its issuesUrl (packages/cli/api/gap-report/gap-report.mjs:684-692).
  • isInside compares against the issuesUrl path, so an https://github.com/<owner>/<repo>/issues/new URL does not recognize an issue a handler already filed in that repository, and a second issue is filed (gap-report.mjs:664-665).
  • The change replaces documented behaviour, so spec:AST-017 FR5 and FR7 call for a [feat] changeset rather than [fix].

[Automated review]

Comment on lines +684 to +692
if (
entries.some(
entry =>
entry.handlerType === 'integration' &&
entry.handlerName === target.package,
)
) {
return false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This matches the target's own handler by package name only. When astryx.config re-exports the integration's gapReport, identity dedupe keeps just the project entry (line 315), so this check misses it and the report reaches the integration twice: through its handler and again at its issuesUrl. Comparing each entry's handler.handle with the target integration's own __gapReport.handle would cover that case, and a test with a re-exporting config would pin it.

Comment on lines +664 to +665
const base = outer.pathname.replace(/\/+$/u, '');
return inner.pathname === base || inner.pathname.startsWith(`${base}/`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a GitHub URL in the /issues/new form, which githubRoute accepts and the CLI's default issues URL uses, base becomes /<owner>/<repo>/issues/new, so a receipt such as https://github.com/<owner>/<repo>/issues/43 is not inside it and, with --confirm-public, a second issue is filed in the same repository. Testing containment against the repository's /issues root from githubRoute would fix it.

'@astryxdesign/cli': patch
---

[fix] `astryx gap-report` now also sends a report about an integration to that integration's own `issuesUrl` when report handlers are loaded, so a package's tracker is no longer skipped whenever any handler exists. A GitHub URL still needs `--confirm-public`. The route is skipped for Core, when the integration's own handler ran, and when a handler already filed the report inside that URL.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This replaces documented behaviour (one handler used to keep a report away from issuesUrl), and spec:AST-017 FR5 says such a change is not a fix; under FR7 a compatible new capability is [feat].

@astracat-bot astracat-bot Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval needs a maintainer to provide or identify the governing spec for the new target route: spec:AST-031 FR13 and DEC-4 are still authority: draft on main, so what must be settled is whether a report about an integration also goes to that integration's own issuesUrl after the handlers, with these consent and skip rules, including this branch's readings that a GitHub …/issues/new URL covers issues already filed in that repository and that “inside” stops at path segments. Independently of that decision, one change is needed before this can be approved:

  • With --confirm-public and no working gh, a run whose handler filed now ends partial and exits 1 instead of filed and 0, a breaking exit-behaviour change under spec:AST-017 FR1 and FR3 that a [feat] patch cannot carry, so keep a failed target route out of the overall status and exit code, or ship it as [breaking] in a declared minor.

Smaller points: the same aggregate change heads a lone failing handler “Gap report partially filed” with nothing filed, the route reuses the “No automatic handler is available” message right under a handler that filed, and the docs say the route is skipped when the integration's own handler “ran” although the code skips whenever that handler is configured.

[Automated review]

);
// The package the report is about also receives it at its own issuesUrl,
// unless that place already has it (spec:AST-031/FR13).
if (needsTargetRoute(target, entries, deliveries, project)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With --confirm-public and no working gh, this route's failed delivery makes aggregateStatus return partial, so a run whose handler filed now exits 1; on the base it exits 0, because gh is never called once a handler exists. Under spec:AST-017 FR1 and FR3 that is a breaking exit-behaviour change, which a [feat] patch cannot carry while main declares 0.7.2. Keeping a failed target route out of status and the exit code when handlers ran, with a test, keeps those runs green; otherwise this needs [breaking] in a declared minor.

status: 'routed_only',
url: target.issuesUrl,
message:
'No automatic handler is available; open the routed URL to file this report.',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When this helper serves the target route, this message appears directly under a handler that filed, so “No automatic handler is available” is no longer true there. A route-specific message (for example, that this is the integration's own tracker) would fix it.

'Sends a gap report to every configured handler: the project config handler first, then each integration handler in config order. ' +
'Each handler has 30 s to finish, and its output goes to stderr. Public handlers run only with confirmPublic; internal handlers always run. ' +
"With no handler, it files a GitHub issue for the owning package only with confirmPublic (without it nothing is sent), or returns the package's issues URL when that is not on GitHub. " +
"With handlers, a report about an integration that sets its own issues URL also goes there the same way, unless that integration's own handler ran or a handler already filed it there. " +

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needsTargetRoute skips the route when the integration's own handler is in the effective set, not only when it ran: a public own handler without --confirm-public is consent_required, never runs, and the route is still skipped. A routed_only or skipped receipt URL inside the issuesUrl also counts, not only a filed one. “is configured” and “a handler's receipt URL already points inside it” would match the code; the command doc (line 19) and the guide (line 170) use the same wording.

@josephfarina josephfarina changed the title fix(cli): a gap report also reaches the integration it is about feat(cli): gap-report --to files a report where the person chose Oct 20, 2026

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: --to and the destination listing follow architecture:cli-surface INV36 and DEC-9 and the default route is unchanged, but two small fixes are needed first.

  • Treat to: null as absent and match any other value against the destinations directly, so an empty, blank or over-long --to fails with ERR_UNKNOWN_PACKAGE and the destination list, as INV36 requires, instead of “to is required.” (packages/cli/api/gap-report/gap-report.mjs:803-804).
  • Render the Destinations text from the JSON fields (for example records over to, default and issuesUrl) so it stays a projection of the JSON under INV4 and INV5, since the default entry's prose at packages/cli/clients/cli/commands/gap-report.mjs:155 is not in the JSON and default never appears.

[Automated review]

/** @type {GapReportDestination | undefined} */
let chosen;
if (options.to !== undefined) {
const to = requiredText(options.to, 'to', 214);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to: null (the value GapReportDestination.to documents for the default), '' and blank values reach requiredText here and fail with ERR_MISSING_ARGUMENT “to is required.”, and a value over 214 characters fails with ERR_INVALID_ARGUMENT; none lists the destinations. INV36 asks for ERR_UNKNOWN_PACKAGE with the list for a --to that names no destination, and this function already treats package: null and detail: null as absent. Treating options.to == null as absent and matching the trimmed string against the destinations directly would cover both, with a test for to: null and for to: ''.

list(
destinations.map(destination =>
destination.default
? 'default (leave out --to): the configured report handlers, or the owning package issues URL when there are none'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This prose is not in the JSON entry ({to: null, default: true, issuesUrl: null}), and no entry's default field reaches the text, so the repository's text/JSON parity checks (INV4, INV5) fail once a destination exists; the only gap-report parity case runs without one. Rendering the entries with records(destinations, {fields: ['to', 'default', 'issuesUrl']}), or carrying this description in the JSON, keeps the two views in step.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval recommended: --to and the destination listing follow architecture:cli-surface INV36 and DEC-9, the default route is unchanged, and the two fixes requested on the previous revision (the to validation and the Destinations text) are in place with tests. No further changes are needed from you.

[Automated review]

@josephfarina
josephfarina merged commit 6a3ff0f into main Oct 20, 2026
50 checks passed
@github-actions
github-actions Bot deleted the josephfarina/gap-report-target-route branch October 21, 2026 06:22

This branch was successfully deployed

1 active deployment
Preview — fa3753ad Deployed Oct 20, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant