Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
334 changes: 277 additions & 57 deletions build/github_action/index.js

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions docs/development/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -341,3 +341,10 @@ and deployment Job Summary. Provider mutations accept those rendered values but
cannot choose phase transitions. Architecture tests enforce the narrow context,
provider-neutral port vocabulary, mutation-free renderer, global dependency
direction, and acyclic production imports.

The generic GitHub Actions Job Summary follows the same boundary. One complete
typed catalog is resolved for `repository-locale`; bundled English and Spanish,
arbitrary dynamic locales, and atomic English fallback share the repository
localization resolver. The summary renders localization evidence once, keeps
machine event/state/error values stable, and retains sanitized internal result
detail without publishing it to an issue or pull-request conversation.
6 changes: 5 additions & 1 deletion docs/issues/notifications-and-auto-close.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ current progress, an action, a direct answer, or a terminal policy explanation.
When the push workflow runs for a branch linked to an issue, Copilot updates one
owned progress card only when the progress projection changes. It does not post
a commit list, image, reopen notice, debug report, or generic β€œActions” summary.
The workflow Job Summary remains the place for bounded operational detail.
The workflow Job Summary remains the place for bounded operational detail. Its
headings and explanatory labels use `repository-locale` (English by default),
while event names, lifecycle values, error codes, and other machine facts remain
stable. It contains one localization section showing the effective issue and PR
locales plus exact, base-language, dynamic, or English-fallback catalog evidence.

- **Where:** the issue associated with the branch (for example,
`feature/123-title` β†’ issue `123`).
Expand Down
4 changes: 4 additions & 0 deletions docs/pull-requests/capabilities.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ one stable `Copilot / Review` Check for the PR head. Actionable findings are
`neutral` by default and become `failure` only with
`bugbot-fail-on-unresolved: true`; `unknown`, incomplete reconciliation, and analysis failures always fail. Missing Checks
capability does not make an otherwise successful functional workflow fail.
The Job Summary uses `repository-locale` for headings and explanatory labels,
defaults to English, and records one non-duplicated localization section. Stable
event, lifecycle, finding-state, and error-code values remain suitable for
operator diagnosis even when the surrounding copy is localized.

## Next steps

Expand Down
2 changes: 2 additions & 0 deletions scripts/coverage-budgets.json
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@
"files": [
"src/domain/locale.ts",
"src/domain/message_catalog.ts",
"src/application/policies/action_summary_message_catalog.ts",
"src/application/policies/comment_translation_policy.ts",
"src/application/policies/merge_queue_message_catalog.ts",
"src/application/policies/publication_message_catalog.ts",
Expand All @@ -264,6 +265,7 @@
"src/actions/github_action_completion.ts",
"src/actions/github_action_execution.ts",
"src/application/policies/action_summary_policy.ts",
"src/application/policies/action_summary_message_catalog.ts",
"src/application/policies/comment_translation_policy.ts",
"src/application/policies/copilot_interaction_policy.ts",
"src/application/policies/publication_message_catalog.ts",
Expand Down
6 changes: 3 additions & 3 deletions specs/CATALOG.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions specs/catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"src/actions/github_action_locale_inputs.ts",
"src/application/ports/message_catalog_ports.ts",
"src/application/policies/resolved_message_catalog_policy.ts",
"src/application/policies/action_summary_message_catalog.ts",
"src/application/policies/branch_sync_message_catalog.ts",
"src/application/policies/merge_queue_message_catalog.ts",
"src/application/policies/setup_doctor_message_catalog.ts",
Expand Down Expand Up @@ -72,6 +73,7 @@
"src/actions/__tests__/configuration_builders.test.ts",
"src/actions/__tests__/github_action_completion.test.ts",
"src/application/policies/__tests__/comment_translation_policy.test.ts",
"src/application/policies/__tests__/action_summary_message_catalog.test.ts",
"src/application/usecases/localization/__tests__/resolve_message_catalog_use_case.test.ts",
"src/prompts/__tests__/localize_message_catalog.test.ts",
"src/domain/__tests__/github_publication.test.ts",
Expand Down
11 changes: 7 additions & 4 deletions specs/repository-locale-and-localization.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
- Date: 2026-09-14
- Catalog capability ID: github-communication-experience
- Last verified: 2026-09-14 for the delivered foundation, shared publication,
branch-sync, Bugbot, and deployment-presentation slices; remaining clauses
are prospective
branch-sync, Bugbot, deployment-presentation, setup-doctor, and generic Job
Summary slices; remaining clauses are prospective
- Owners: Copilot maintainers
- Scope: Define one English-default repository locale profile and apply it generically to deterministic UI, agent-generated content, and safe interpretation of addressed comments.
- Related issues/PRs: [issue #334](https://github.com/vypdev/copilot/issues/334), [PR #363](https://github.com/vypdev/copilot/pull/363), [PR #365](https://github.com/vypdev/copilot/pull/365)
Expand Down Expand Up @@ -958,8 +958,11 @@ the complete doctor artifact, reuses it for merge-readiness rows and terminal
presentation, replaces pull-request-mode prose with stable reason codes, and
keeps raw provider and credential-health diagnostics out of UI. Setup itself
remains one authoritative English artifact while it creates the repository
profile. Lifecycle and the remaining public surfaces are not claimed complete
by this evidence.
profile. The generic Actions Job Summary now resolves one complete catalog in
the repository locale, localizes its headings and explanatory labels, preserves
machine values, and emits localization evidence once instead of duplicating it
inside and below the main table. Lifecycle and the remaining public surfaces are
not claimed complete by this evidence.

### 13.5 Rollback

Expand Down
11 changes: 8 additions & 3 deletions specs/semantic-github-publication-and-notification.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
- Status: In implementation
- Date: 2026-09-14
- Catalog capability ID: github-communication-experience
- Last verified: 2026-09-14 for the delivered shared publication, branch-sync, review-context, and Bugbot slices; remaining clauses are prospective
- Last verified: 2026-09-14 for the delivered shared publication, branch-sync,
review-context, Bugbot, deployment, setup-doctor, and generic Job Summary
slices; remaining clauses are prospective
- Owners: Copilot maintainers
- Scope: Replace generic step-dump comments with bounded, semantic, idempotent GitHub messages across issues, pull requests, reviews, pushes, and single actions.
- Related issues/PRs: [issue #334](https://github.com/vypdev/copilot/issues/334), [issue #344](https://github.com/vypdev/copilot/issues/344), [issue #355](https://github.com/vypdev/copilot/issues/355), [PR #358](https://github.com/vypdev/copilot/pull/358), [PR #363](https://github.com/vypdev/copilot/pull/363), [PR #365](https://github.com/vypdev/copilot/pull/365)
Expand Down Expand Up @@ -932,8 +934,11 @@ feature-owned review/thread model while replacing ad hoc public strings with a
single typed catalog per publication operation, proper plural forms, stable
machine markers, and bounded English-default/localized renderers. The executable
mutation inventory, locale-branch ratchet, and pseudo-plural ratchet protect
these boundaries. Other capability rows and the global numeric budget remain
open and are not claimed complete by this milestone.
these boundaries. Subsequent deployment and setup-doctor slices cover their
feature-owned views, and the generic Job Summary slice resolves repository-
locale copy atomically, retains sanitized operator steps, and renders one
localization evidence section instead of two. Other capability rows and the
global numeric budget remain open and are not claimed complete by this milestone.

No remote product flag is required. Each phase must be independently releasable
and its compatibility adapter must fail closed to Job Summary, not fall back to
Expand Down
25 changes: 25 additions & 0 deletions src/actions/__tests__/github_action_completion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,31 @@ describe('finishGithubAction', () => {
expect(mockSummaryPublish).toHaveBeenCalledWith(expect.stringContaining('test-owner/test-repo'));
});

it('uses one repository-locale section in a localized generic summary', async () => {
const action = Object.assign(execution(), {
locale: { repository: 'es-ES', issue: 'es-ES', pullRequest: 'es-ES' },
});
const resolver = new ResolveMessageCatalogUseCase();

await finishGithubAction(
action,
[new Result({ id: 'MetadataUseCase', success: true, executed: true, steps: ['Updated labels.'] })],
{} as never,
{} as never,
undefined,
{ publish: mockSummaryPublish },
resolver,
);

const summary = mockSummaryPublish.mock.calls[0][0] as string;
expect(summary).toContain('# EjecuciΓ³n de Copilot');
expect(summary).toContain('| Estado | βœ… Correcto |');
expect(summary).toContain('## Detalles del resultado');
expect(summary).toContain('| Locale del repositorio | `es-ES` |');
expect(summary.match(/## LocalizaciΓ³n/gu)).toHaveLength(1);
expect(summary).not.toContain('## Localization');
});

it('uses the durable repository locale for deployment summaries without replaying internal steps', async () => {
const action = Object.assign(singleActionExecution(), {
singleAction: {
Expand Down
22 changes: 16 additions & 6 deletions src/actions/github_action_completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
renderLocalizationSummarySection,
type LocalizationSummaryLabels,
} from '../application/policies/action_summary_policy';
import { resolveActionSummaryCatalog } from '../application/policies/action_summary_message_catalog';
import { lifecycleStateFromLabels } from '../domain/copilot_lifecycle';
import type { CopilotEvidencePort } from '../application/ports/copilot_evidence_ports';
import { buildCopilotEvidence } from '../application/policies/copilot_evidence_policy';
Expand Down Expand Up @@ -92,6 +93,7 @@ async function writeActionSummary(
: locale;
let body: string;
let localizationLabels: LocalizationSummaryLabels | undefined;
let appendLocalizationEvidence = false;
if (execution.singleAction.isDeploymentOrchestrationAction && operation) {
const effectiveLocale = operation.locale ?? locale;
const catalog = await resolveDeploymentCatalog(
Expand All @@ -111,6 +113,7 @@ async function writeActionSummary(
descriptors: messages.descriptors,
reason: messages.reason,
};
appendLocalizationEvidence = true;
body = renderDeploymentJobSummary(operation, {
owner: execution.owner,
repository: execution.repo,
Expand All @@ -124,6 +127,11 @@ async function writeActionSummary(
: undefined,
}, operation.lastFailure?.previousPhase, catalog);
} else {
const catalog = await resolveActionSummaryCatalog(
locale.repository,
execution.ai.getAgentConfiguration('planner'),
catalogResolver,
);
body = buildActionSummary({
owner: execution.owner,
repository: execution.repo,
Expand All @@ -145,13 +153,15 @@ async function writeActionSummary(
},
catalogResolutions: catalogResolver?.observations?.() ?? [],
results: execution.currentConfiguration.results,
});
}, catalog);
}
const localizationEvidence = renderLocalizationSummarySection({
repository: summaryLocale.repository,
issue: summaryLocale.issue,
pullRequest: summaryLocale.pullRequest,
}, catalogResolver?.observations?.() ?? [], localizationLabels);
const localizationEvidence = appendLocalizationEvidence
? renderLocalizationSummarySection({
repository: summaryLocale.repository,
issue: summaryLocale.issue,
pullRequest: summaryLocale.pullRequest,
}, catalogResolver?.observations?.() ?? [], localizationLabels)
: '';
const summaryText = [body, localizationEvidence].filter(Boolean).join('\n\n');
if (!summaryPort) return summaryText;
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type { CatalogMessage } from '../../../domain/message_catalog';
import { catalogPlaceholders, validateCatalogDefinition } from '../../../domain/message_catalog';
import { ResolveMessageCatalogUseCase } from '../../usecases/localization/resolve_message_catalog_use_case';
import {
ACTION_SUMMARY_CATALOG_DEFINITIONS,
ACTION_SUMMARY_MESSAGE_IDS,
ENGLISH_ACTION_SUMMARY_DEFINITION,
SPANISH_ACTION_SUMMARY_DEFINITION,
resolveActionSummaryCatalog,
resolveStaticActionSummaryCatalog,
} from '../action_summary_message_catalog';

const configuration = { provider: 'codex' as const, model: 'model' };

function translatedMessages(): Record<string, CatalogMessage> {
return Object.fromEntries(Object.entries(ENGLISH_ACTION_SUMMARY_DEFINITION.messages).map(([id, message]) => [
id,
`FR ${message as string}`,
]));
}

describe('action summary message catalog', () => {
it('ships complete, unique, immutable English and Spanish definitions', () => {
expect(new Set(ACTION_SUMMARY_MESSAGE_IDS).size).toBe(ACTION_SUMMARY_MESSAGE_IDS.length);
for (const definition of ACTION_SUMMARY_CATALOG_DEFINITIONS) {
expect(validateCatalogDefinition(definition, ACTION_SUMMARY_MESSAGE_IDS)).toEqual([]);
expect(Object.isFrozen(definition)).toBe(true);
expect(Object.isFrozen(definition.messages)).toBe(true);
}
for (const id of ACTION_SUMMARY_MESSAGE_IDS) {
expect(catalogPlaceholders(SPANISH_ACTION_SUMMARY_DEFINITION.messages[id]))
.toEqual(catalogPlaceholders(ENGLISH_ACTION_SUMMARY_DEFINITION.messages[id]));
}
});

it.each([
['en-US', 'en-US', 'exact'],
['en-AU', 'en-US', 'base'],
['es-ES', 'es-ES', 'exact'],
['es-AR', 'es-ES', 'base'],
['fr-FR', 'en-US', 'fallback'],
] as const)('resolves %s as %s from %s', (requested, resolved, source) => {
expect(resolveStaticActionSummaryCatalog(requested)).toMatchObject({
requestedLocale: requested,
locale: resolved,
resolutionSource: source,
});
});

it('resolves and caches one complete catalog for an arbitrary locale', async () => {
const query = jest.fn().mockResolvedValue({ targetLocale: 'fr-FR', messages: translatedMessages() });
const resolver = new ResolveMessageCatalogUseCase({ query });
const first = await resolveActionSummaryCatalog('fr-FR', configuration, resolver);
const replay = await resolveActionSummaryCatalog('fr-FR', configuration, resolver);

expect(first).toMatchObject({ requestedLocale: 'fr-FR', locale: 'fr-FR', resolutionSource: 'dynamic' });
expect(first.message('summary.target.issue', { number: 12 })).toBe('FR Issue #12');
expect(replay.message('summary.heading')).toBe('FR Copilot execution');
expect(query).toHaveBeenCalledTimes(1);
});

it('falls back atomically to English when a dynamic descriptor is missing', async () => {
const messages = translatedMessages();
delete messages['summary.status'];
const resolver = new ResolveMessageCatalogUseCase({
query: jest.fn().mockResolvedValue({ targetLocale: 'fr-FR', messages }),
});

const catalog = await resolveActionSummaryCatalog('fr-FR', configuration, resolver);

expect(catalog).toMatchObject({
requestedLocale: 'fr-FR',
locale: 'en-US',
resolutionSource: 'fallback',
fallbackReason: 'dynamic-response-invalid',
});
expect(catalog.message('summary.status')).toBe('Status');
expect(catalog.message('summary.heading')).not.toContain('FR ');
});
});
Loading