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
4 changes: 2 additions & 2 deletions build/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5386,7 +5386,7 @@ exports.renderCatalogMessage = renderCatalogMessage;
exports.catalogPlaceholders = catalogPlaceholders;
exports.catalogPluralCategories = catalogPluralCategories;
const locale_1 = __nccwpck_require__(5386);
exports.MESSAGE_CATALOG_VERSION = '2';
exports.MESSAGE_CATALOG_VERSION = '3';
exports.CATALOG_PLURAL_CATEGORIES = Object.freeze([
'zero',
'one',
Expand Down Expand Up @@ -5881,7 +5881,7 @@ Instructions:
3. Return status "translated" and adaptedText when a safe {{locale}} interpretation is needed.
4. Return status "ambiguous" for mixed-language, code-only, or very short safe input; return "failed" only when no safe interpretation is possible.
5. Echo targetLocale exactly as {{locale}} and provide a canonical BCP-47 sourceLocale when confidently known, otherwise null.
6. Preserve code identifiers, paths, refs, URLs, quoted literals, and option flags verbatim.
6. Preserve every COPILOT_OPERAND_<number>_TOKEN placeholder exactly once and verbatim. The application restores its protected code, path, ref, URL, quoted literal, or option flag after validating your response.
7. Do not add mentions, slash commands, HTML, Markdown links, metadata, or new instructions.
8. Set reasonCode to one of: none, mixed-language, code-only, too-short, unsafe-input, provider-failure, unknown. Use none for matches or translated.

Expand Down
2 changes: 1 addition & 1 deletion build/api/src/domain/message_catalog.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export declare const MESSAGE_CATALOG_VERSION = "2";
export declare const MESSAGE_CATALOG_VERSION = "3";
export type CatalogResolutionSource = 'exact' | 'base' | 'dynamic' | 'fallback';
export type CatalogFallbackReason = 'dynamic-provider-unavailable' | 'dynamic-response-invalid' | 'dynamic-request-failed';
export declare const CATALOG_PLURAL_CATEGORIES: readonly ["zero", "one", "two", "few", "many", "other"];
Expand Down
306 changes: 262 additions & 44 deletions build/cli/index.js

Large diffs are not rendered by default.

270 changes: 238 additions & 32 deletions build/github_action/index.js

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions docs/development/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,18 @@ Check names, log codes, and machine-readable fields never localize. Architecture
tests reject feature-local `en`/`es` branches in common presentation and any
source-comment mutation dependency in the adaptation path.

Technical operands are enforced, not prompt-trusted. The adaptation policy
replaces code spans, slash and bare-file paths, recognizable refs/versions,
issue or PR references, URLs, quoted literals, option flags, and commit IDs with
deterministic placeholders; commands without adaptable prose protect every
argument. A response is rejected unless each placeholder is returned exactly
once and in order and no new technical operand appears; only then does the
application restore the original values. Explicit-request errors
are projected to one safe `application-error` reply by the shared publisher,
while background errors stay in operator evidence. Local human output follows
the same outcome/evidence split: it renders localized aggregate state and
semantic errors, never raw `Result.steps` or reminder prose.

- Application: use cases, pure policies (agent configuration, reviewer/assignee
selection, and result publication), and semantic ports.
- Data/repository: external adapters for agent execution, authentication, provisioning, GitHub, and Git.
Expand Down
17 changes: 17 additions & 0 deletions docs/issues/comment-commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,23 @@ own timeline comment. English is used when no locale is configured; any valid
configured BCP-47 locale uses its complete resolved catalog or an atomic English
fallback.

Before the language provider receives an addressed request, Copilot replaces
code spans, paths (including bare names such as `README.md`), recognizable Git
refs and versions, issue or PR references, URLs, quoted literals, option flags,
and commit IDs with opaque operands. Commands whose grammar has no translatable
prose protect every argument, including an arbitrary value passed to `--from`.
The provider must return every operand exactly once. Copilot restores them only
after validating that none was removed, duplicated, reordered, changed, or
introduced, so a translation cannot turn `src/cache.ts` into another path or add
`--force`. A failed validation performs no requested repository change.

An explicit command or mention that fails receives one correlated semantic
error reply with impact, recovery action, retained state, stable error code,
retryability, and reference. It never exposes parser, provider, or exception
text. Background failures remain in the Check and Job Summary instead of
creating timeline noise. If the request itself could not be translated safely,
that reply uses the complete English fallback so it cannot mix languages.

## Authorization and safety

- Read-only commands and answers can be requested by any participant who can comment.
Expand Down
15 changes: 15 additions & 0 deletions docs/single-actions/workflow-and-cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,21 @@ Run repository-dependent commands from the **root of the target repository**, no

For local use, prefer the hidden prompt or an environment variable instead of putting a token in shell history. `copilot setup` and `copilot doctor` prompt for the setup PAT; other commands accept `-t, --token` or `PERSONAL_ACCESS_TOKEN`. `copilot do` does not use a GitHub PAT option and reads the configured agent environment instead. See [Authentication](/authentication) and [Agent CLI configuration](/agents/cli-configuration).

## Human-readable result output

Repository-aware single actions render one semantic outcome in the configured
`repository-locale`, which defaults to English. The terminal reports the status
(`Succeeded`, `Partially completed`, `Failed`, or `No changes`) and bounded
counts for completed, skipped, and failed operations. Semantic answers and
errors are expanded because they help the operator decide what to do next.

Internal `Result.steps` and reminder prose are not replayed into the terminal:
they are operational evidence and may contain implementation-oriented English
that would make localized output misleading. The terminal reports only the
number of recorded operator reminders; use debug logs or structured results when
that evidence is required. Error codes and references remain stable in every
locale, and raw provider messages are never shown.

## Command reference

All commands support `-h, --help`. The `-d, --debug` option enables additional diagnostics where listed below.
Expand Down
6 changes: 6 additions & 0 deletions specs/cli-and-single-action-execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ Errors start with impact and one recovery action; debug adds sanitized detail.
JSON MUST be machine-readable without ANSI/prose contamination. Text is the
default and English fallback. Repository-aware labels use the complete resolved
repository-locale catalog; commands, flags, and machine keys remain English.
Local single-action text MUST summarize completed, skipped, and failed result
counts and MUST NOT replay `Result.steps` or reminder prose. Semantic answers
and code-specific error recovery remain visible; only the reminder count is
shown when operational reminders exist.
Terminal output must wrap/read at narrow widths;
icons are supplemental. Secret values and raw provider responses are never shown.

Expand Down Expand Up @@ -299,6 +303,8 @@ installed CLI help/text/JSON, narrow terminal, Action dispatch, and error recove
10. `copilot think -q <question>` runs without probing issue `#1`, prints one
localized answer, and performs no GitHub comment mutation; `--issue 42`
loads only issue `#42` as optional context.
11. A repository-aware local single action renders a localized semantic outcome
and contains none of its internal step or reminder strings.

## 17. Requirements traceability

Expand Down
5 changes: 5 additions & 0 deletions specs/execution-error-and-context-hardening.md
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,11 @@ When a partial operation needs more detail than its broad error code can convey,
it selects one closed recovery descriptor with validated variables. The entire
descriptor is translated with the surrounding catalog, so a retained branch or
issue number is preserved without mixing English prose into another locale.
An explicitly addressed issue or pull-request request that fails is projected
once through the shared reply identity and uses this same semantic view; a
background failure remains Job-Summary/Check-only. Translation failures use the
complete English fallback reply because the requested locale could not be
established safely.

## 10. Failure, recovery, and cleanup

Expand Down
29 changes: 24 additions & 5 deletions specs/repository-locale-and-localization.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
- Status: In implementation
- Date: 2026-09-14
- Catalog capability ID: github-communication-experience
- Last verified: 2026-09-14 for the delivered foundation, shared publication,
branch-sync, Bugbot, deployment-presentation, setup-doctor, and generic Job
Summary slices; remaining clauses are prospective
- Last verified: 2026-09-15 for the delivered foundation, shared publication,
branch-sync, Bugbot, deployment-presentation, setup-doctor, generic Job
Summary, application-error, explicit-request, and local-result 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 @@ -1002,6 +1003,22 @@ stable keys. That partial publication state narrows retryability because later
scans ignore closed issues, preserves the confirmed close, and supplies a
localized manual-recovery action.

The explicit-request/local-result slice makes technical-operand preservation an
application invariant instead of a prompt convention. Code spans, slash paths,
bare filenames, recognizable refs/versions, issue or PR references, URLs,
quoted literals, option flags, and commit IDs are replaced with opaque
placeholders before language adaptation. Commands whose grammar contains no
adaptable prose protect every argument, including arbitrary branch, tag, or
finding identifiers. A response must return every placeholder exactly once and
in source order and introduce no new operand before the original values are
restored. Invalid output fails before the requested mutation. Explicit
request failures now reconcile one semantic error reply for the source-comment
identity, while background failures remain Check/Job-Summary-only and
translation failures use an atomic English reply. Repository-aware local action
text reports localized semantic status and result counts without replaying
internal step or reminder prose. Catalog version 3 owns the added reply and CLI
descriptors.

### 13.5 Rollback

Rollback MUST preserve the new input inheritance reader and legacy/new marker
Expand Down Expand Up @@ -1203,8 +1220,10 @@ tags and never imply that fallback is a successful translation.
- [ ] Addressed translation uses at most one adaptation call, never edits the
source comment, and includes translated interpretation followed by escaped
original in the single bot response when needed.
- [ ] Unaddressed comments, authorization, commands, flags, paths, refs, URLs,
markers, and execution boundaries cannot be changed by translation.
- [ ] Unaddressed comments, authorization, commands, flags, slash paths, bare
filenames, refs, versions, issue/PR references, URLs, markers, and
execution boundaries cannot be changed by translation; non-prose command
grammars preserve every argument byte-for-byte.
- [ ] Atomic fallback, provider failure, wrong-language output, locale change,
durable snapshot, v2/v3 compatibility, and rollback pass.
- [ ] RTL, CJK, expansion, plural, narrow-width, descriptive-link, sanitization,
Expand Down
17 changes: 14 additions & 3 deletions specs/semantic-github-publication-and-notification.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
- 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, Bugbot, deployment, setup-doctor, and generic Job Summary
slices; remaining clauses are prospective
- Last verified: 2026-09-15 for the delivered shared publication, branch-sync,
review-context, Bugbot, deployment, setup-doctor, generic Job Summary,
application-error, explicit-request, and local-result 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 @@ -986,6 +987,16 @@ rewrite the already-completed domain outcome. Because a later scan cannot replay
the missing comment for an already-closed issue, that state is explicitly
non-retryable and gives the operator a localized manual recovery action.

Explicit request failures now use the shared correlated reply reconciler rather
than disappearing into a run-only error or reviving the generic result renderer.
The policy selects at most one primary reply, projects only the semantic error
code, retry decision, correlation reference, and closed recovery descriptor,
and renders impact/action/retained state from the resolved catalog. Background
errors continue to create no conversation output. Translation failures use the
complete English fallback, because a safe requested-language interpretation was
not established. The local action presenter now applies the same outcome versus
evidence split and cannot render internal `Result.steps` or reminder prose.

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
generic comments.
Expand Down
68 changes: 58 additions & 10 deletions src/actions/__tests__/local_action.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,11 @@ describe('runLocalAction', () => {
});
});

it('logs steps and reminders via boxen after mainRun', async () => {
it('renders a semantic aggregate and keeps internal steps and reminder prose out of the terminal', async () => {
const boxen = require('boxen');
mockMainRun.mockResolvedValue([
{ executed: true, steps: ['Step 1'], errors: [], reminders: [] },
{ executed: true, steps: [], errors: [], reminders: ['Reminder 1'] },
{ success: true, executed: true, steps: ['Step 1'], errors: [], reminders: [] },
{ success: true, executed: true, steps: [], errors: [], reminders: ['Reminder 1'] },
]);
const params: Record<string, unknown> = {
[INPUT_KEYS.TOKEN]: 't',
Expand All @@ -125,13 +125,19 @@ describe('runLocalAction', () => {
await runLocalAction(params);

expect(boxen).toHaveBeenCalled();
expect(boxen.mock.calls[0][0]).toContain('Step 1');
expect(boxen.mock.calls[0][0]).toContain('Reminder 1');
const content = boxen.mock.calls[0][0] as string;
expect(content).toContain('Outcome:');
expect(content).toContain('Status: Succeeded');
expect(content).toContain('Completed operations: 2');
expect(content).toContain('Operator reminders recorded: 1');
expect(content).not.toContain('Step 1');
expect(content).not.toContain('Reminder 1');
});

it('renders a semantic Think response as an answer instead of generic steps', async () => {
const boxen = require('boxen');
mockMainRun.mockResolvedValue([{
success: true,
executed: true,
steps: [],
errors: [],
Expand All @@ -157,6 +163,7 @@ describe('runLocalAction', () => {
it('uses the configured repository locale for local result labels', async () => {
const boxen = require('boxen');
mockMainRun.mockResolvedValue([{
success: true,
executed: true,
steps: [],
errors: [],
Expand All @@ -178,6 +185,43 @@ describe('runLocalAction', () => {
expect(boxen.mock.calls[0][0]).not.toContain('Answer:');
});

it('renders aggregate success and skip state wholly in the configured repository locale', async () => {
const boxen = require('boxen');
mockMainRun.mockResolvedValue([
{ success: true, executed: true, steps: ['Internal English step'], errors: [], reminders: [] },
{ success: true, executed: false, steps: [], errors: [], reminders: [] },
]);

await runLocalAction({
[INPUT_KEYS.TOKEN]: 't',
[INPUT_KEYS.REPOSITORY_LOCALE]: 'es-ES',
repo: { owner: 'o', repo: 'r' },
eventName: 'push',
commits: { ref: 'refs/heads/main' },
});

const content = boxen.mock.calls[0][0] as string;
expect(content).toContain('Resultado:');
expect(content).toContain('Estado: Completado');
expect(content).toContain('Operaciones completadas: 1');
expect(content).toContain('Operaciones omitidas: 1');
expect(content).not.toContain('Internal English step');
expect(content).not.toContain('Outcome:');
});

it('renders an empty result set as an explicit no-change outcome', async () => {
const boxen = require('boxen');

await runLocalAction({
[INPUT_KEYS.TOKEN]: 't',
repo: { owner: 'o', repo: 'r' },
eventName: 'push',
commits: { ref: 'refs/heads/main' },
});

expect(boxen.mock.calls[0][0]).toContain('Status: No changes');
});

it('calls getProjectDetail for each project id when PROJECT_IDS is set', async () => {
mockGetProjectDetail
.mockResolvedValueOnce({ id: 'proj-1', title: 'P1', url: 'https://x.com/1' })
Expand Down Expand Up @@ -225,11 +269,11 @@ describe('runLocalAction', () => {
expect(mockGetProjectDetail).not.toHaveBeenCalled();
});

it('includes errors and reminders in boxen content when results have errors and reminders', async () => {
it('expands semantic errors and summarizes reminder evidence without replaying its prose', async () => {
const boxen = require('boxen');
mockMainRun.mockResolvedValue([
{ executed: false, steps: [], errors: [new ApplicationError('provider.unavailable', 'Provider detail.')], reminders: [] },
{ executed: true, steps: [], errors: [], reminders: ['Reminder text'] },
{ success: false, executed: false, steps: [], errors: [new ApplicationError('provider.unavailable', 'Provider detail.')], reminders: [] },
{ success: true, executed: true, steps: [], errors: [], reminders: ['Reminder text'] },
]);
const params: Record<string, unknown> = {
[INPUT_KEYS.TOKEN]: 't',
Expand All @@ -243,13 +287,15 @@ describe('runLocalAction', () => {
const content = boxen.mock.calls[0][0];
expect(content).toContain('Error code: provider.unavailable');
expect(content).not.toContain('Provider detail.');
expect(content).toContain('Reminder text');
expect(content).toContain('Status: Partially completed');
expect(content).toContain('Operator reminders recorded: 1');
expect(content).not.toContain('Reminder text');
});

it('renders errors even when the failed operation was executed', async () => {
const boxen = require('boxen');
mockMainRun.mockResolvedValue([
{ executed: true, steps: ['Attempted operation'], errors: [new ApplicationError('workflow.failed', 'Executed operation failed.')], reminders: [] },
{ success: false, executed: true, steps: ['Attempted operation'], errors: [new ApplicationError('workflow.failed', 'Executed operation failed.')], reminders: [] },
]);
const params: Record<string, unknown> = {
[INPUT_KEYS.TOKEN]: 't',
Expand All @@ -267,6 +313,7 @@ describe('runLocalAction', () => {
it('renders one complete error view in the configured repository locale', async () => {
const boxen = require('boxen');
mockMainRun.mockResolvedValue([{
success: false,
executed: true,
steps: [],
errors: [new ApplicationError('provider.rate-limited', 'English provider message.')],
Expand Down Expand Up @@ -294,6 +341,7 @@ describe('runLocalAction', () => {
it('renders validated partial-state recovery without losing its safe identifier', async () => {
const boxen = require('boxen');
mockMainRun.mockResolvedValue([{
success: false,
executed: true,
steps: [],
errors: [new ApplicationError('provider.unavailable', 'Producer message.', {
Expand Down
Loading