From cd2363f9fc1038fb4d130c79061951fda586be02 Mon Sep 17 00:00:00 2001 From: Efra Espada Date: Tue, 15 Sep 2026 14:04:14 +0200 Subject: [PATCH 1/3] codex-communication-contract-closure: Close communication specification contracts --- docs/development/testing.mdx | 11 +- package.json | 4 +- ...idate-github-communication-test-budget.cjs | 151 +++++++++++++++++ specs/CATALOG.md | 10 +- specs/catalog.json | 16 +- specs/repository-locale-and-localization.md | 112 ++++++++----- ...tic-github-publication-and-notification.md | 123 ++++++++------ ...ublication_concurrency.integration.test.ts | 157 ++++++++++++++++++ .../github_communication_test_budget.json | 62 +++++++ ...e_github_communication_test_budget.test.ts | 80 +++++++++ 10 files changed, 618 insertions(+), 108 deletions(-) create mode 100644 scripts/validate-github-communication-test-budget.cjs create mode 100644 src/application/usecases/steps/common/__tests__/publication_concurrency.integration.test.ts create mode 100644 src/architecture/github_communication_test_budget.json create mode 100644 src/tooling/__tests__/validate_github_communication_test_budget.test.ts diff --git a/docs/development/testing.mdx b/docs/development/testing.mdx index faddddd98..d53c0d15f 100644 --- a/docs/development/testing.mdx +++ b/docs/development/testing.mdx @@ -30,7 +30,16 @@ inputs, actions, commands, and orchestration modes. `validate:specifications` validates unique capability ownership, safe existing repository-relative evidence paths, complete registration of top-level SDDs, retrospective-baseline structure, and byte-for-byte freshness of the generated -`specs/CATALOG.md` view. +`specs/CATALOG.md` view. It also enforces the communication-program test ledger: +at least 128 semantic-publication cases and 136 localization cases, allocated to +disjoint test files. Parameterized declarations count once, and removing tests, +inflating an allocation, or assigning one file to both budgets fails the gate. + +The ledger lives at +`src/architecture/github_communication_test_budget.json`. Its qualifying counts +are a conservative audit of new Jest `it`/`test` declarations since the two SDDs +landed at baseline `0bb83b75^`; a table-driven declaration is not multiplied by +its data rows. Merge-queue contract tests cover ruleset-only targets, classic/ruleset union, GitHub Actions workflow parsing on target and candidate refs, external exact diff --git a/package.json b/package.json index 72c2058a4..b77775b57 100644 --- a/package.json +++ b/package.json @@ -62,8 +62,8 @@ "validate:docs-page": "node scripts/validate-docs-page-assets.cjs", "validate:documentation": "node scripts/validate-documentation-contract.cjs", "validate:workflows": "node scripts/validate-workflow-contract.cjs", - "generate:specifications": "node scripts/validate-specification-catalog.cjs --write", - "validate:specifications": "node scripts/validate-specification-catalog.cjs", + "generate:specifications": "node scripts/validate-specification-catalog.cjs --write && node scripts/validate-github-communication-test-budget.cjs", + "validate:specifications": "node scripts/validate-specification-catalog.cjs && node scripts/validate-github-communication-test-budget.cjs", "lint:fix": "eslint src --fix", "postinstall": "node scripts/install-git-hooks.cjs" }, diff --git a/scripts/validate-github-communication-test-budget.cjs b/scripts/validate-github-communication-test-budget.cjs new file mode 100644 index 000000000..72b28edeb --- /dev/null +++ b/scripts/validate-github-communication-test-budget.cjs @@ -0,0 +1,151 @@ +#!/usr/bin/env node + +const fs = require('node:fs'); +const path = require('node:path'); +const { execFileSync } = require('node:child_process'); + +const DEFAULT_ROOT = path.resolve(__dirname, '..'); +const LEDGER_PATH = 'src/architecture/github_communication_test_budget.json'; +const EXPECTED_BUDGETS = Object.freeze({ + 'semantic-github-publication': Object.freeze({ + requiredCases: 128, + spec: 'specs/semantic-github-publication-and-notification.md', + }), + 'repository-locale-localization': Object.freeze({ + requiredCases: 136, + spec: 'specs/repository-locale-and-localization.md', + }), +}); +const TEST_DECLARATION = /^\s*(?:it|test)(?:\.each)?\s*\(/gmu; +const ADDED_TEST_DECLARATION = /^\+\s*(?:it|test)(?:\.each)?\s*\(/gmu; + +function readLedger(root = DEFAULT_ROOT) { + return JSON.parse(fs.readFileSync(path.join(root, LEDGER_PATH), 'utf8')); +} + +function validateCommunicationTestBudget(root, ledger) { + const errors = []; + if (!ledger || typeof ledger !== 'object' || Array.isArray(ledger)) { + return ['communication test-budget ledger must be a JSON object.']; + } + if (ledger.version !== 1) errors.push('communication test-budget ledger version must be 1.'); + if (ledger.baseline !== '0bb83b75^') errors.push('communication test-budget baseline must remain 0bb83b75^.'); + if (!gitObjectExists(root, `${ledger.baseline}^{commit}`)) { + errors.push(`communication test-budget baseline is unavailable: ${String(ledger.baseline)}.`); + } + if (!Array.isArray(ledger.budgets)) return [...errors, 'communication test-budget ledger must declare budgets.']; + if (ledger.budgets.length !== Object.keys(EXPECTED_BUDGETS).length) { + errors.push(`communication test-budget ledger must contain exactly ${Object.keys(EXPECTED_BUDGETS).length} budgets.`); + } + + const seenBudgets = new Set(); + const seenFiles = new Set(); + for (const budget of ledger.budgets) { + const expected = EXPECTED_BUDGETS[budget?.id]; + if (!expected) { + errors.push(`unknown communication test budget: ${String(budget?.id)}.`); + continue; + } + if (seenBudgets.has(budget.id)) errors.push(`duplicate communication test budget: ${budget.id}.`); + seenBudgets.add(budget.id); + if (budget.spec !== expected.spec) errors.push(`${budget.id} must reference ${expected.spec}.`); + if (budget.requiredCases !== expected.requiredCases) { + errors.push(`${budget.id} must require exactly ${expected.requiredCases} cases.`); + } + const specPath = path.join(root, expected.spec); + if (!fs.existsSync(specPath) + || !fs.readFileSync(specPath, 'utf8').includes(`at least **${expected.requiredCases} distinct`)) { + errors.push(`${budget.id} specification does not declare its ${expected.requiredCases}-case budget.`); + } + if (!Array.isArray(budget.files) || budget.files.length === 0) { + errors.push(`${budget.id} must allocate cases to at least one test file.`); + continue; + } + let allocatedCases = 0; + for (const entry of budget.files) { + if (!isTestPath(entry?.path)) { + errors.push(`${budget.id} has an invalid test path: ${String(entry?.path)}.`); + continue; + } + if (seenFiles.has(entry.path)) { + errors.push(`${entry.path} is allocated to more than one communication budget.`); + } + seenFiles.add(entry.path); + if (!Number.isSafeInteger(entry.qualifyingCases) || entry.qualifyingCases <= 0) { + errors.push(`${budget.id} has an invalid qualifying-case count for ${entry.path}.`); + continue; + } + allocatedCases += entry.qualifyingCases; + const testPath = path.join(root, entry.path); + if (!fs.existsSync(testPath) || !fs.statSync(testPath).isFile()) { + errors.push(`${budget.id} test file does not exist: ${entry.path}.`); + continue; + } + const qualifyingDeclarations = countQualifyingDeclarations(root, ledger.baseline, entry.path); + if (qualifyingDeclarations < entry.qualifyingCases) { + errors.push(`${entry.path} has ${qualifyingDeclarations} qualifying test declarations since ${ledger.baseline}, below its allocation of ${entry.qualifyingCases}.`); + } + } + if (budget.allocatedCases !== allocatedCases) { + errors.push(`${budget.id} allocatedCases is ${String(budget.allocatedCases)} but its file allocation totals ${allocatedCases}.`); + } + if (allocatedCases < expected.requiredCases) { + errors.push(`${budget.id} allocates ${allocatedCases} cases, below its required ${expected.requiredCases}.`); + } + } + for (const budgetId of Object.keys(EXPECTED_BUDGETS)) { + if (!seenBudgets.has(budgetId)) errors.push(`missing communication test budget: ${budgetId}.`); + } + return errors; +} + +function countQualifyingDeclarations(root, baseline, testPath) { + if (!gitObjectExists(root, `${baseline}:${testPath}`)) { + return (fs.readFileSync(path.join(root, testPath), 'utf8').match(TEST_DECLARATION) ?? []).length; + } + const diff = execFileSync( + 'git', + ['diff', baseline, '--unified=0', '--', testPath], + { cwd: root, encoding: 'utf8' }, + ); + return (diff.match(ADDED_TEST_DECLARATION) ?? []).length; +} + +function gitObjectExists(root, object) { + try { + execFileSync('git', ['cat-file', '-e', object], { cwd: root, stdio: 'ignore' }); + return true; + } catch { + return false; + } +} + +function isTestPath(value) { + return typeof value === 'string' + && /^src\/.+(?:__tests__\/.+\.test\.ts|\.test\.ts)$/u.test(value) + && value === path.posix.normalize(value) + && !value.split('/').includes('..'); +} + +function main() { + const ledger = readLedger(DEFAULT_ROOT); + const errors = validateCommunicationTestBudget(DEFAULT_ROOT, ledger); + if (errors.length > 0) { + console.error(errors.map(error => `- ${error}`).join('\n')); + process.exitCode = 1; + return; + } + const summary = ledger.budgets + .map(budget => `${budget.id}=${budget.allocatedCases}/${budget.requiredCases}`) + .join(', '); + console.log(`GitHub communication test budgets: PASS (${summary}; no file is double-counted)`); +} + +if (require.main === module) main(); + +module.exports = { + LEDGER_PATH, + countQualifyingDeclarations, + readLedger, + validateCommunicationTestBudget, +}; diff --git a/specs/CATALOG.md b/specs/CATALOG.md index aaa00da77..86b2989f7 100644 --- a/specs/CATALOG.md +++ b/specs/CATALOG.md @@ -10,7 +10,7 @@ debt or convert unknown historic intent into a design decision. | Capability ID | Status | Scope | Primary SDD | Evidence | |---|---|---|---|---| -| `github-communication-experience` | Proposed | English-default, localized, semantic, bounded, and idempotent product messages across GitHub and repository-aware operator surfaces | [Semantic GitHub communication and repository localization](./semantic-github-publication-and-notification.md) + 1 companion | 198 paths · 2026-09-15 | +| `github-communication-experience` | Implemented | English-default, localized, semantic, bounded, and idempotent product messages across GitHub and repository-aware operator surfaces | [Semantic GitHub communication and repository localization](./semantic-github-publication-and-notification.md) + 1 companion | 204 paths · 2026-09-15 | | `release-orchestration` | Implemented | Release and hotfix promotion, publication, reconciliation, and durable recovery | [Configurable production-first release orchestration](./configurable-release-orchestration.md) + 2 companion | 52 paths · 2026-09-14 | | `merge-queue-readiness` | Implemented | Fail-closed validation of required checks and merge-group workflow support | [Merge queue readiness and effective target rules](./merge-queue-readiness.md) | 24 paths · 2026-09-15 | | `bugbot-review-state-reconciliation` | Implemented | Reconcile review snapshots, findings, threads, comments, and check conclusions | [Bugbot review-state reconciliation](./bugbot-review-state-reconciliation.md) | 56 paths · 2026-09-15 | @@ -33,10 +33,10 @@ debt or convert unknown historic intent into a design decision. - Last verified: 2026-09-15 - Specifications: [`specs/semantic-github-publication-and-notification.md`](./semantic-github-publication-and-notification.md) · [`specs/repository-locale-and-localization.md`](./repository-locale-and-localization.md) - Workflows: [`.github/workflows/copilot_issue.yml`](../.github/workflows/copilot_issue.yml) · [`.github/workflows/copilot_issue_comment.yml`](../.github/workflows/copilot_issue_comment.yml) · [`.github/workflows/copilot_pull_request.yml`](../.github/workflows/copilot_pull_request.yml) · [`.github/workflows/copilot_pull_request_review_state.yml`](../.github/workflows/copilot_pull_request_review_state.yml) · [`.github/workflows/copilot_pull_request_comment.yml`](../.github/workflows/copilot_pull_request_comment.yml) · [`.github/workflows/copilot_commit.yml`](../.github/workflows/copilot_commit.yml) · [`.github/workflows/copilot_close_inactive_issues.yml`](../.github/workflows/copilot_close_inactive_issues.yml) · [`.github/workflows/copilot_deployment_orchestration.yml`](../.github/workflows/copilot_deployment_orchestration.yml) -- Entrypoints: [`src/actions/github_action.ts`](../src/actions/github_action.ts) · [`src/actions/github_action_completion.ts`](../src/actions/github_action_completion.ts) · [`src/actions/github_event_inputs.ts`](../src/actions/github_event_inputs.ts) · [`src/cli_context.ts`](../src/cli_context.ts) · [`src/cli/commands/check_progress.ts`](../src/cli/commands/check_progress.ts) · [`src/cli/commands/issue_command_policy.ts`](../src/cli/commands/issue_command_policy.ts) · [`src/api.ts`](../src/api.ts) · [`src/cli.ts`](../src/cli.ts) -- Core code: [`scripts/coverage-budgets.json`](../scripts/coverage-budgets.json) · [`src/domain/implementation_plan.ts`](../src/domain/implementation_plan.ts) · [`src/domain/locale.ts`](../src/domain/locale.ts) · [`src/domain/message_catalog.ts`](../src/domain/message_catalog.ts) · [`src/data/model/locale.ts`](../src/data/model/locale.ts) · [`src/actions/github_action_locale_inputs.ts`](../src/actions/github_action_locale_inputs.ts) · [`src/application/ports/message_catalog_ports.ts`](../src/application/ports/message_catalog_ports.ts) · [`src/application/policies/resolved_message_catalog_policy.ts`](../src/application/policies/resolved_message_catalog_policy.ts) · [`src/application/policies/action_summary_message_catalog.ts`](../src/application/policies/action_summary_message_catalog.ts) · [`src/application/policies/branch_sync_message_catalog.ts`](../src/application/policies/branch_sync_message_catalog.ts) · [`src/application/policies/inactivity_message_catalog.ts`](../src/application/policies/inactivity_message_catalog.ts) · [`src/application/policies/inactivity_notification_policy.ts`](../src/application/policies/inactivity_notification_policy.ts) · [`src/application/policies/merge_queue_message_catalog.ts`](../src/application/policies/merge_queue_message_catalog.ts) · [`src/application/policies/setup_doctor_message_catalog.ts`](../src/application/policies/setup_doctor_message_catalog.ts) · [`src/application/policies/setup_doctor_report_policy.ts`](../src/application/policies/setup_doctor_report_policy.ts) · [`src/application/usecases/localization/resolve_message_catalog_use_case.ts`](../src/application/usecases/localization/resolve_message_catalog_use_case.ts) · [`src/application/usecases/setup/doctor_use_case.ts`](../src/application/usecases/setup/doctor_use_case.ts) · [`src/application/usecases/setup/merge_queue_readiness_use_case.ts`](../src/application/usecases/setup/merge_queue_readiness_use_case.ts) · [`src/application/usecases/steps/common/comment_language_translation_workflow.ts`](../src/application/usecases/steps/common/comment_language_translation_workflow.ts) · [`src/application/policies/comment_translation_policy.ts`](../src/application/policies/comment_translation_policy.ts) · [`src/application/usecases/steps/common/think_request_policy.ts`](../src/application/usecases/steps/common/think_request_policy.ts) · [`src/application/usecases/steps/common/think_workflow.ts`](../src/application/usecases/steps/common/think_workflow.ts) · [`src/application/usecases/steps/common/think_answer_workflow.ts`](../src/application/usecases/steps/common/think_answer_workflow.ts) · [`src/application/usecases/steps/common/think_use_case.ts`](../src/application/usecases/steps/common/think_use_case.ts) · [`src/application/usecases/comment_automation_use_case.ts`](../src/application/usecases/comment_automation_use_case.ts) · [`src/application/usecases/steps/common/publish_resume_workflow.ts`](../src/application/usecases/steps/common/publish_resume_workflow.ts) · [`src/domain/github_publication.ts`](../src/domain/github_publication.ts) · [`src/domain/git_object_id.ts`](../src/domain/git_object_id.ts) · [`src/application/ports/publication_freshness_ports.ts`](../src/application/ports/publication_freshness_ports.ts) · [`src/application/policies/publication_identity_policy.ts`](../src/application/policies/publication_identity_policy.ts) · [`src/application/policies/publication_outcome_policy.ts`](../src/application/policies/publication_outcome_policy.ts) · [`src/application/policies/publication_message_catalog.ts`](../src/application/policies/publication_message_catalog.ts) · [`src/application/policies/semantic_result_publication_policy.ts`](../src/application/policies/semantic_result_publication_policy.ts) · [`src/application/policies/agent_response_schemas.ts`](../src/application/policies/agent_response_schemas.ts) · [`src/application/usecases/actions/recommend_steps_result_policy.ts`](../src/application/usecases/actions/recommend_steps_result_policy.ts) · [`src/data/model/recommendation_state.ts`](../src/data/model/recommendation_state.ts) · [`src/prompts/recommend_steps.ts`](../src/prompts/recommend_steps.ts) · [`src/application/usecases/issue_use_case.ts`](../src/application/usecases/issue_use_case.ts) · [`src/application/usecases/issue_workflow.ts`](../src/application/usecases/issue_workflow.ts) · [`src/application/usecases/issue_workflow_context.ts`](../src/application/usecases/issue_workflow_context.ts) · [`src/application/usecases/steps/issue/answer_issue_help_use_case.ts`](../src/application/usecases/steps/issue/answer_issue_help_use_case.ts) · [`src/application/usecases/steps/issue/answer_issue_help_workflow.ts`](../src/application/usecases/steps/issue/answer_issue_help_workflow.ts) · [`src/application/ports/issue_lifecycle_ports.ts`](../src/application/ports/issue_lifecycle_ports.ts) · [`src/application/usecases/steps/common/status_card_publication_workflow.ts`](../src/application/usecases/steps/common/status_card_publication_workflow.ts) · [`src/application/usecases/steps/common/reply_publication_workflow.ts`](../src/application/usecases/steps/common/reply_publication_workflow.ts) · [`src/application/usecases/steps/common/transition_notification_workflow.ts`](../src/application/usecases/steps/common/transition_notification_workflow.ts) · [`src/application/usecases/steps/common/duplicate_comment_cleanup_workflow.ts`](../src/application/usecases/steps/common/duplicate_comment_cleanup_workflow.ts) · [`src/actions/local_action.ts`](../src/actions/local_action.ts) · [`src/actions/local_action_output.ts`](../src/actions/local_action_output.ts) · [`src/cli/commands/think.ts`](../src/cli/commands/think.ts) · [`src/cli/commands/think_command_handler.ts`](../src/cli/commands/think_command_handler.ts) · [`src/infrastructure/composition/local_action_composition_root.ts`](../src/infrastructure/composition/local_action_composition_root.ts) · [`src/infrastructure/composition/main_run_route_composition_root.ts`](../src/infrastructure/composition/main_run_route_composition_root.ts) · [`src/infrastructure/composition/issue_use_case_composition_root.ts`](../src/infrastructure/composition/issue_use_case_composition_root.ts) · [`src/infrastructure/composition/shared_capability_port_binding.ts`](../src/infrastructure/composition/shared_capability_port_binding.ts) · [`src/architecture/github_publication_mutation_baseline.json`](../src/architecture/github_publication_mutation_baseline.json) · [`src/application/policies/action_summary_policy.ts`](../src/application/policies/action_summary_policy.ts) · [`src/application/policies/application_error_message_catalog.ts`](../src/application/policies/application_error_message_catalog.ts) · [`src/application/policies/application_error_presentation_policy.ts`](../src/application/policies/application_error_presentation_policy.ts) · [`src/application/policies/branch_sync_notification_policy.ts`](../src/application/policies/branch_sync_notification_policy.ts) · [`src/application/policies/bugbot_message_catalog.ts`](../src/application/policies/bugbot_message_catalog.ts) · [`src/application/policies/deployment_message_catalog.ts`](../src/application/policies/deployment_message_catalog.ts) · [`src/application/usecases/actions/observe_branch_sync_use_case.ts`](../src/application/usecases/actions/observe_branch_sync_use_case.ts) · [`src/application/usecases/actions/close_inactive_issues_use_case.ts`](../src/application/usecases/actions/close_inactive_issues_use_case.ts) · [`src/application/usecases/actions/close_inactive_issues_workflow.ts`](../src/application/usecases/actions/close_inactive_issues_workflow.ts) · [`src/application/usecases/push_single_action_contexts.ts`](../src/application/usecases/push_single_action_contexts.ts) · [`src/infrastructure/composition/issue_inactivity_composition_root.ts`](../src/infrastructure/composition/issue_inactivity_composition_root.ts) · [`src/application/policies/bugbot_review_presentation_policy.ts`](../src/application/policies/bugbot_review_presentation_policy.ts) · [`src/application/usecases/steps/commit/detect_potential_problems_workflow.ts`](../src/application/usecases/steps/commit/detect_potential_problems_workflow.ts) · [`src/application/usecases/steps/commit/bugbot/publish_pr_review_comments.ts`](../src/application/usecases/steps/commit/bugbot/publish_pr_review_comments.ts) · [`src/application/usecases/steps/commit/bugbot/synchronize_bugbot_review_presentation_use_case.ts`](../src/application/usecases/steps/commit/bugbot/synchronize_bugbot_review_presentation_use_case.ts) · [`src/application/policies/deployment_presentation_policy.ts`](../src/application/policies/deployment_presentation_policy.ts) · [`src/application/usecases/actions/recommend_steps_workflow.ts`](../src/application/usecases/actions/recommend_steps_workflow.ts) · [`src/application/usecases/actions/check_progress_workflow.ts`](../src/application/usecases/actions/check_progress_workflow.ts) · [`src/application/usecases/actions/check_progress_use_case.ts`](../src/application/usecases/actions/check_progress_use_case.ts) · [`src/application/usecases/actions/progress_analysis_workflow.ts`](../src/application/usecases/actions/progress_analysis_workflow.ts) · [`src/data/repository/github_publication_source_repository.ts`](../src/data/repository/github_publication_source_repository.ts) · [`src/infrastructure/composition/check_progress_composition_root.ts`](../src/infrastructure/composition/check_progress_composition_root.ts) · [`src/data/repository/issue/issue_content_repository.ts`](../src/data/repository/issue/issue_content_repository.ts) · [`src/data/repository/github/github_error_policy.ts`](../src/data/repository/github/github_error_policy.ts) · [`src/infrastructure/github/ports/github_issue_provider_ports.ts`](../src/infrastructure/github/ports/github_issue_provider_ports.ts) -- Tests: [`src/domain/__tests__/implementation_plan.test.ts`](../src/domain/__tests__/implementation_plan.test.ts) · [`src/domain/__tests__/locale.test.ts`](../src/domain/__tests__/locale.test.ts) · [`src/domain/__tests__/message_catalog.test.ts`](../src/domain/__tests__/message_catalog.test.ts) · [`src/actions/__tests__/configuration_builders.test.ts`](../src/actions/__tests__/configuration_builders.test.ts) · [`src/actions/__tests__/github_event_inputs.test.ts`](../src/actions/__tests__/github_event_inputs.test.ts) · [`src/cli/commands/__tests__/issue_command_policy.test.ts`](../src/cli/commands/__tests__/issue_command_policy.test.ts) · [`src/actions/__tests__/github_action_completion.test.ts`](../src/actions/__tests__/github_action_completion.test.ts) · [`src/data/model/__tests__/config.test.ts`](../src/data/model/__tests__/config.test.ts) · [`src/application/policies/__tests__/agent_response_schemas.test.ts`](../src/application/policies/__tests__/agent_response_schemas.test.ts) · [`src/prompts/__tests__/recommend_steps.test.ts`](../src/prompts/__tests__/recommend_steps.test.ts) · [`src/application/policies/__tests__/comment_translation_policy.test.ts`](../src/application/policies/__tests__/comment_translation_policy.test.ts) · [`src/application/policies/__tests__/action_summary_message_catalog.test.ts`](../src/application/policies/__tests__/action_summary_message_catalog.test.ts) · [`src/application/policies/__tests__/application_error_message_catalog.test.ts`](../src/application/policies/__tests__/application_error_message_catalog.test.ts) · [`src/application/policies/__tests__/application_error_presentation_policy.test.ts`](../src/application/policies/__tests__/application_error_presentation_policy.test.ts) · [`src/application/usecases/localization/__tests__/resolve_message_catalog_use_case.test.ts`](../src/application/usecases/localization/__tests__/resolve_message_catalog_use_case.test.ts) · [`src/prompts/__tests__/localize_message_catalog.test.ts`](../src/prompts/__tests__/localize_message_catalog.test.ts) · [`src/domain/__tests__/github_publication.test.ts`](../src/domain/__tests__/github_publication.test.ts) · [`src/domain/__tests__/git_object_id.test.ts`](../src/domain/__tests__/git_object_id.test.ts) · [`src/data/repository/__tests__/github_publication_source_repository.test.ts`](../src/data/repository/__tests__/github_publication_source_repository.test.ts) · [`src/application/policies/__tests__/publication_identity_policy.test.ts`](../src/application/policies/__tests__/publication_identity_policy.test.ts) · [`src/application/policies/__tests__/publication_outcome_policy.test.ts`](../src/application/policies/__tests__/publication_outcome_policy.test.ts) · [`src/application/policies/__tests__/publication_message_catalog.test.ts`](../src/application/policies/__tests__/publication_message_catalog.test.ts) · [`src/application/policies/__tests__/semantic_result_publication_policy.test.ts`](../src/application/policies/__tests__/semantic_result_publication_policy.test.ts) · [`src/application/policies/__tests__/action_summary_policy.test.ts`](../src/application/policies/__tests__/action_summary_policy.test.ts) · [`src/application/policies/__tests__/branch_sync_notification_policy.test.ts`](../src/application/policies/__tests__/branch_sync_notification_policy.test.ts) · [`src/application/policies/__tests__/inactivity_message_catalog.test.ts`](../src/application/policies/__tests__/inactivity_message_catalog.test.ts) · [`src/application/policies/__tests__/inactivity_notification_policy.test.ts`](../src/application/policies/__tests__/inactivity_notification_policy.test.ts) · [`src/application/policies/__tests__/setup_doctor_message_catalog.test.ts`](../src/application/policies/__tests__/setup_doctor_message_catalog.test.ts) · [`src/application/policies/__tests__/setup_doctor_report_policy.test.ts`](../src/application/policies/__tests__/setup_doctor_report_policy.test.ts) · [`src/application/usecases/actions/__tests__/observe_branch_sync_use_case.test.ts`](../src/application/usecases/actions/__tests__/observe_branch_sync_use_case.test.ts) · [`src/application/usecases/setup/__tests__/doctor_use_case.test.ts`](../src/application/usecases/setup/__tests__/doctor_use_case.test.ts) · [`src/application/usecases/setup/__tests__/merge_queue_readiness_use_case.test.ts`](../src/application/usecases/setup/__tests__/merge_queue_readiness_use_case.test.ts) · [`src/application/policies/__tests__/bugbot_message_catalog.test.ts`](../src/application/policies/__tests__/bugbot_message_catalog.test.ts) · [`src/application/policies/__tests__/deployment_message_catalog.test.ts`](../src/application/policies/__tests__/deployment_message_catalog.test.ts) · [`src/application/policies/__tests__/bugbot_review_presentation_policy.test.ts`](../src/application/policies/__tests__/bugbot_review_presentation_policy.test.ts) · [`src/application/usecases/steps/commit/__tests__/detect_potential_problems_use_case.test.ts`](../src/application/usecases/steps/commit/__tests__/detect_potential_problems_use_case.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/dismiss_bugbot_findings_use_case.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/dismiss_bugbot_findings_use_case.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/synchronize_bugbot_review_presentation_use_case.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/synchronize_bugbot_review_presentation_use_case.test.ts) · [`src/application/policies/__tests__/deployment_presentation_policy.test.ts`](../src/application/policies/__tests__/deployment_presentation_policy.test.ts) · [`src/application/usecases/actions/__tests__/close_inactive_issues_use_case.test.ts`](../src/application/usecases/actions/__tests__/close_inactive_issues_use_case.test.ts) · [`src/application/usecases/__tests__/push_single_action_contexts.test.ts`](../src/application/usecases/__tests__/push_single_action_contexts.test.ts) · [`src/application/usecases/steps/common/__tests__/comment_language_translation_workflow.test.ts`](../src/application/usecases/steps/common/__tests__/comment_language_translation_workflow.test.ts) · [`src/application/usecases/steps/common/__tests__/think_request_policy.test.ts`](../src/application/usecases/steps/common/__tests__/think_request_policy.test.ts) · [`src/application/usecases/steps/common/__tests__/think_use_case.test.ts`](../src/application/usecases/steps/common/__tests__/think_use_case.test.ts) · [`src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts`](../src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts) · [`src/application/usecases/steps/common/__tests__/status_card_publication_workflow.test.ts`](../src/application/usecases/steps/common/__tests__/status_card_publication_workflow.test.ts) · [`src/application/usecases/steps/common/__tests__/reply_publication_workflow.test.ts`](../src/application/usecases/steps/common/__tests__/reply_publication_workflow.test.ts) · [`src/application/usecases/steps/common/__tests__/transition_notification_workflow.test.ts`](../src/application/usecases/steps/common/__tests__/transition_notification_workflow.test.ts) · [`src/application/usecases/steps/common/__tests__/duplicate_comment_cleanup_workflow.test.ts`](../src/application/usecases/steps/common/__tests__/duplicate_comment_cleanup_workflow.test.ts) · [`src/data/repository/issue/__tests__/issue_content_repository.test.ts`](../src/data/repository/issue/__tests__/issue_content_repository.test.ts) · [`src/data/repository/deployment/__tests__/deployment_presentation_repository.test.ts`](../src/data/repository/deployment/__tests__/deployment_presentation_repository.test.ts) · [`src/data/repository/__tests__/github_error_policy.test.ts`](../src/data/repository/__tests__/github_error_policy.test.ts) · [`src/application/usecases/steps/issue/__tests__/answer_issue_help_use_case.test.ts`](../src/application/usecases/steps/issue/__tests__/answer_issue_help_use_case.test.ts) · [`src/application/usecases/__tests__/issue_use_case.test.ts`](../src/application/usecases/__tests__/issue_use_case.test.ts) · [`src/application/usecases/__tests__/issue_pull_request_context_projection.test.ts`](../src/application/usecases/__tests__/issue_pull_request_context_projection.test.ts) · [`src/actions/__tests__/local_action.test.ts`](../src/actions/__tests__/local_action.test.ts) · [`src/__tests__/cli.test.ts`](../src/__tests__/cli.test.ts) · [`src/cli/commands/__tests__/think_command_handler.test.ts`](../src/cli/commands/__tests__/think_command_handler.test.ts) · [`src/infrastructure/composition/__tests__/local_action_composition_root.test.ts`](../src/infrastructure/composition/__tests__/local_action_composition_root.test.ts) · [`src/infrastructure/composition/__tests__/main_run_route_composition_root.test.ts`](../src/infrastructure/composition/__tests__/main_run_route_composition_root.test.ts) · [`src/infrastructure/composition/__tests__/issue_use_case_composition_root.test.ts`](../src/infrastructure/composition/__tests__/issue_use_case_composition_root.test.ts) · [`src/infrastructure/composition/__tests__/shared_capability_port_binding.test.ts`](../src/infrastructure/composition/__tests__/shared_capability_port_binding.test.ts) · [`src/infrastructure/composition/__tests__/pull_request_use_case_composition_root.test.ts`](../src/infrastructure/composition/__tests__/pull_request_use_case_composition_root.test.ts) · [`src/architecture/__tests__/github_publication_boundaries.test.ts`](../src/architecture/__tests__/github_publication_boundaries.test.ts) · [`src/tooling/__tests__/validate_workflow_contract.test.ts`](../src/tooling/__tests__/validate_workflow_contract.test.ts) · [`src/application/usecases/actions/__tests__/recommend_steps_use_case.test.ts`](../src/application/usecases/actions/__tests__/recommend_steps_use_case.test.ts) · [`src/application/usecases/actions/__tests__/check_progress_use_case.test.ts`](../src/application/usecases/actions/__tests__/check_progress_use_case.test.ts) · [`src/application/usecases/__tests__/comment_automation_use_case.test.ts`](../src/application/usecases/__tests__/comment_automation_use_case.test.ts) -- User documentation: [`docs/configuration.mdx`](../docs/configuration.mdx) · [`docs/configuration-checklist.mdx`](../docs/configuration-checklist.mdx) · [`docs/features.mdx`](../docs/features.mdx) · [`docs/issues/configuration.mdx`](../docs/issues/configuration.mdx) · [`docs/issues/comment-commands.mdx`](../docs/issues/comment-commands.mdx) · [`docs/issues/notifications-and-auto-close.mdx`](../docs/issues/notifications-and-auto-close.mdx) · [`docs/issues/branch-synchronization.mdx`](../docs/issues/branch-synchronization.mdx) · [`docs/issues/type/feature.mdx`](../docs/issues/type/feature.mdx) · [`docs/issues/type/bugfix.mdx`](../docs/issues/type/bugfix.mdx) · [`docs/issues/type/docs.mdx`](../docs/issues/type/docs.mdx) · [`docs/issues/type/chore.mdx`](../docs/issues/type/chore.mdx) · [`docs/issues/type/hotfix.mdx`](../docs/issues/type/hotfix.mdx) · [`docs/issues/type/release.mdx`](../docs/issues/type/release.mdx) · [`docs/issues/deployment-orchestration.mdx`](../docs/issues/deployment-orchestration.mdx) · [`docs/pull-requests/configuration.mdx`](../docs/pull-requests/configuration.mdx) · [`docs/pull-requests/capabilities.mdx`](../docs/pull-requests/capabilities.mdx) · [`docs/pull-requests/workflow-setup.mdx`](../docs/pull-requests/workflow-setup.mdx) · [`docs/pull-requests/examples.mdx`](../docs/pull-requests/examples.mdx) · [`docs/bugbot/configuration.mdx`](../docs/bugbot/configuration.mdx) · [`docs/bugbot/finding-publication.mdx`](../docs/bugbot/finding-publication.mdx) · [`docs/bugbot/detection.mdx`](../docs/bugbot/detection.mdx) · [`docs/bugbot/how-it-works.mdx`](../docs/bugbot/how-it-works.mdx) · [`docs/bugbot/programmatic-api.mdx`](../docs/bugbot/programmatic-api.mdx) · [`docs/bugbot/quality-observability.mdx`](../docs/bugbot/quality-observability.mdx) · [`docs/bugbot/failure-scenarios.mdx`](../docs/bugbot/failure-scenarios.mdx) · [`docs/bugbot/examples.mdx`](../docs/bugbot/examples.mdx) · [`docs/single-actions/configuration.mdx`](../docs/single-actions/configuration.mdx) · [`docs/single-actions/available-actions.mdx`](../docs/single-actions/available-actions.mdx) · [`docs/single-actions/workflow-and-cli.mdx`](../docs/single-actions/workflow-and-cli.mdx) · [`docs/single-actions/examples.mdx`](../docs/single-actions/examples.mdx) · [`docs/security-operations/security/prompt-injection.mdx`](../docs/security-operations/security/prompt-injection.mdx) · [`docs/security-operations/operations/verification.mdx`](../docs/security-operations/operations/verification.mdx) · [`docs/development/architecture.mdx`](../docs/development/architecture.mdx) +- Entrypoints: [`src/actions/github_action.ts`](../src/actions/github_action.ts) · [`src/actions/github_action_completion.ts`](../src/actions/github_action_completion.ts) · [`src/actions/github_event_inputs.ts`](../src/actions/github_event_inputs.ts) · [`src/cli_context.ts`](../src/cli_context.ts) · [`src/cli/commands/check_progress.ts`](../src/cli/commands/check_progress.ts) · [`src/cli/commands/issue_command_policy.ts`](../src/cli/commands/issue_command_policy.ts) · [`src/api.ts`](../src/api.ts) · [`src/cli.ts`](../src/cli.ts) · [`package.json`](../package.json) +- Core code: [`scripts/coverage-budgets.json`](../scripts/coverage-budgets.json) · [`scripts/validate-github-communication-test-budget.cjs`](../scripts/validate-github-communication-test-budget.cjs) · [`src/architecture/github_communication_test_budget.json`](../src/architecture/github_communication_test_budget.json) · [`src/domain/implementation_plan.ts`](../src/domain/implementation_plan.ts) · [`src/domain/locale.ts`](../src/domain/locale.ts) · [`src/domain/message_catalog.ts`](../src/domain/message_catalog.ts) · [`src/data/model/locale.ts`](../src/data/model/locale.ts) · [`src/actions/github_action_locale_inputs.ts`](../src/actions/github_action_locale_inputs.ts) · [`src/application/ports/message_catalog_ports.ts`](../src/application/ports/message_catalog_ports.ts) · [`src/application/policies/resolved_message_catalog_policy.ts`](../src/application/policies/resolved_message_catalog_policy.ts) · [`src/application/policies/action_summary_message_catalog.ts`](../src/application/policies/action_summary_message_catalog.ts) · [`src/application/policies/branch_sync_message_catalog.ts`](../src/application/policies/branch_sync_message_catalog.ts) · [`src/application/policies/inactivity_message_catalog.ts`](../src/application/policies/inactivity_message_catalog.ts) · [`src/application/policies/inactivity_notification_policy.ts`](../src/application/policies/inactivity_notification_policy.ts) · [`src/application/policies/merge_queue_message_catalog.ts`](../src/application/policies/merge_queue_message_catalog.ts) · [`src/application/policies/setup_doctor_message_catalog.ts`](../src/application/policies/setup_doctor_message_catalog.ts) · [`src/application/policies/setup_doctor_report_policy.ts`](../src/application/policies/setup_doctor_report_policy.ts) · [`src/application/usecases/localization/resolve_message_catalog_use_case.ts`](../src/application/usecases/localization/resolve_message_catalog_use_case.ts) · [`src/application/usecases/setup/doctor_use_case.ts`](../src/application/usecases/setup/doctor_use_case.ts) · [`src/application/usecases/setup/merge_queue_readiness_use_case.ts`](../src/application/usecases/setup/merge_queue_readiness_use_case.ts) · [`src/application/usecases/steps/common/comment_language_translation_workflow.ts`](../src/application/usecases/steps/common/comment_language_translation_workflow.ts) · [`src/application/policies/comment_translation_policy.ts`](../src/application/policies/comment_translation_policy.ts) · [`src/application/usecases/steps/common/think_request_policy.ts`](../src/application/usecases/steps/common/think_request_policy.ts) · [`src/application/usecases/steps/common/think_workflow.ts`](../src/application/usecases/steps/common/think_workflow.ts) · [`src/application/usecases/steps/common/think_answer_workflow.ts`](../src/application/usecases/steps/common/think_answer_workflow.ts) · [`src/application/usecases/steps/common/think_use_case.ts`](../src/application/usecases/steps/common/think_use_case.ts) · [`src/application/usecases/comment_automation_use_case.ts`](../src/application/usecases/comment_automation_use_case.ts) · [`src/application/usecases/steps/common/publish_resume_workflow.ts`](../src/application/usecases/steps/common/publish_resume_workflow.ts) · [`src/domain/github_publication.ts`](../src/domain/github_publication.ts) · [`src/domain/git_object_id.ts`](../src/domain/git_object_id.ts) · [`src/application/ports/publication_freshness_ports.ts`](../src/application/ports/publication_freshness_ports.ts) · [`src/application/policies/publication_identity_policy.ts`](../src/application/policies/publication_identity_policy.ts) · [`src/application/policies/publication_outcome_policy.ts`](../src/application/policies/publication_outcome_policy.ts) · [`src/application/policies/publication_message_catalog.ts`](../src/application/policies/publication_message_catalog.ts) · [`src/application/policies/semantic_result_publication_policy.ts`](../src/application/policies/semantic_result_publication_policy.ts) · [`src/application/policies/agent_response_schemas.ts`](../src/application/policies/agent_response_schemas.ts) · [`src/application/usecases/actions/recommend_steps_result_policy.ts`](../src/application/usecases/actions/recommend_steps_result_policy.ts) · [`src/data/model/recommendation_state.ts`](../src/data/model/recommendation_state.ts) · [`src/prompts/recommend_steps.ts`](../src/prompts/recommend_steps.ts) · [`src/application/usecases/issue_use_case.ts`](../src/application/usecases/issue_use_case.ts) · [`src/application/usecases/issue_workflow.ts`](../src/application/usecases/issue_workflow.ts) · [`src/application/usecases/issue_workflow_context.ts`](../src/application/usecases/issue_workflow_context.ts) · [`src/application/usecases/steps/issue/answer_issue_help_use_case.ts`](../src/application/usecases/steps/issue/answer_issue_help_use_case.ts) · [`src/application/usecases/steps/issue/answer_issue_help_workflow.ts`](../src/application/usecases/steps/issue/answer_issue_help_workflow.ts) · [`src/application/ports/issue_lifecycle_ports.ts`](../src/application/ports/issue_lifecycle_ports.ts) · [`src/application/usecases/steps/common/status_card_publication_workflow.ts`](../src/application/usecases/steps/common/status_card_publication_workflow.ts) · [`src/application/usecases/steps/common/reply_publication_workflow.ts`](../src/application/usecases/steps/common/reply_publication_workflow.ts) · [`src/application/usecases/steps/common/transition_notification_workflow.ts`](../src/application/usecases/steps/common/transition_notification_workflow.ts) · [`src/application/usecases/steps/common/duplicate_comment_cleanup_workflow.ts`](../src/application/usecases/steps/common/duplicate_comment_cleanup_workflow.ts) · [`src/actions/local_action.ts`](../src/actions/local_action.ts) · [`src/actions/local_action_output.ts`](../src/actions/local_action_output.ts) · [`src/cli/commands/think.ts`](../src/cli/commands/think.ts) · [`src/cli/commands/think_command_handler.ts`](../src/cli/commands/think_command_handler.ts) · [`src/infrastructure/composition/local_action_composition_root.ts`](../src/infrastructure/composition/local_action_composition_root.ts) · [`src/infrastructure/composition/main_run_route_composition_root.ts`](../src/infrastructure/composition/main_run_route_composition_root.ts) · [`src/infrastructure/composition/issue_use_case_composition_root.ts`](../src/infrastructure/composition/issue_use_case_composition_root.ts) · [`src/infrastructure/composition/shared_capability_port_binding.ts`](../src/infrastructure/composition/shared_capability_port_binding.ts) · [`src/architecture/github_publication_mutation_baseline.json`](../src/architecture/github_publication_mutation_baseline.json) · [`src/application/policies/action_summary_policy.ts`](../src/application/policies/action_summary_policy.ts) · [`src/application/policies/application_error_message_catalog.ts`](../src/application/policies/application_error_message_catalog.ts) · [`src/application/policies/application_error_presentation_policy.ts`](../src/application/policies/application_error_presentation_policy.ts) · [`src/application/policies/branch_sync_notification_policy.ts`](../src/application/policies/branch_sync_notification_policy.ts) · [`src/application/policies/bugbot_message_catalog.ts`](../src/application/policies/bugbot_message_catalog.ts) · [`src/application/policies/deployment_message_catalog.ts`](../src/application/policies/deployment_message_catalog.ts) · [`src/application/usecases/actions/observe_branch_sync_use_case.ts`](../src/application/usecases/actions/observe_branch_sync_use_case.ts) · [`src/application/usecases/actions/close_inactive_issues_use_case.ts`](../src/application/usecases/actions/close_inactive_issues_use_case.ts) · [`src/application/usecases/actions/close_inactive_issues_workflow.ts`](../src/application/usecases/actions/close_inactive_issues_workflow.ts) · [`src/application/usecases/push_single_action_contexts.ts`](../src/application/usecases/push_single_action_contexts.ts) · [`src/infrastructure/composition/issue_inactivity_composition_root.ts`](../src/infrastructure/composition/issue_inactivity_composition_root.ts) · [`src/application/policies/bugbot_review_presentation_policy.ts`](../src/application/policies/bugbot_review_presentation_policy.ts) · [`src/application/usecases/steps/commit/detect_potential_problems_workflow.ts`](../src/application/usecases/steps/commit/detect_potential_problems_workflow.ts) · [`src/application/usecases/steps/commit/bugbot/publish_pr_review_comments.ts`](../src/application/usecases/steps/commit/bugbot/publish_pr_review_comments.ts) · [`src/application/usecases/steps/commit/bugbot/synchronize_bugbot_review_presentation_use_case.ts`](../src/application/usecases/steps/commit/bugbot/synchronize_bugbot_review_presentation_use_case.ts) · [`src/application/policies/deployment_presentation_policy.ts`](../src/application/policies/deployment_presentation_policy.ts) · [`src/application/usecases/actions/recommend_steps_workflow.ts`](../src/application/usecases/actions/recommend_steps_workflow.ts) · [`src/application/usecases/actions/check_progress_workflow.ts`](../src/application/usecases/actions/check_progress_workflow.ts) · [`src/application/usecases/actions/check_progress_use_case.ts`](../src/application/usecases/actions/check_progress_use_case.ts) · [`src/application/usecases/actions/progress_analysis_workflow.ts`](../src/application/usecases/actions/progress_analysis_workflow.ts) · [`src/data/repository/github_publication_source_repository.ts`](../src/data/repository/github_publication_source_repository.ts) · [`src/infrastructure/composition/check_progress_composition_root.ts`](../src/infrastructure/composition/check_progress_composition_root.ts) · [`src/data/repository/issue/issue_content_repository.ts`](../src/data/repository/issue/issue_content_repository.ts) · [`src/data/repository/github/github_error_policy.ts`](../src/data/repository/github/github_error_policy.ts) · [`src/infrastructure/github/ports/github_issue_provider_ports.ts`](../src/infrastructure/github/ports/github_issue_provider_ports.ts) +- Tests: [`src/domain/__tests__/implementation_plan.test.ts`](../src/domain/__tests__/implementation_plan.test.ts) · [`src/domain/__tests__/locale.test.ts`](../src/domain/__tests__/locale.test.ts) · [`src/domain/__tests__/message_catalog.test.ts`](../src/domain/__tests__/message_catalog.test.ts) · [`src/actions/__tests__/configuration_builders.test.ts`](../src/actions/__tests__/configuration_builders.test.ts) · [`src/actions/__tests__/github_event_inputs.test.ts`](../src/actions/__tests__/github_event_inputs.test.ts) · [`src/cli/commands/__tests__/issue_command_policy.test.ts`](../src/cli/commands/__tests__/issue_command_policy.test.ts) · [`src/actions/__tests__/github_action_completion.test.ts`](../src/actions/__tests__/github_action_completion.test.ts) · [`src/data/model/__tests__/config.test.ts`](../src/data/model/__tests__/config.test.ts) · [`src/application/policies/__tests__/agent_response_schemas.test.ts`](../src/application/policies/__tests__/agent_response_schemas.test.ts) · [`src/prompts/__tests__/recommend_steps.test.ts`](../src/prompts/__tests__/recommend_steps.test.ts) · [`src/application/policies/__tests__/comment_translation_policy.test.ts`](../src/application/policies/__tests__/comment_translation_policy.test.ts) · [`src/application/policies/__tests__/action_summary_message_catalog.test.ts`](../src/application/policies/__tests__/action_summary_message_catalog.test.ts) · [`src/application/policies/__tests__/application_error_message_catalog.test.ts`](../src/application/policies/__tests__/application_error_message_catalog.test.ts) · [`src/application/policies/__tests__/application_error_presentation_policy.test.ts`](../src/application/policies/__tests__/application_error_presentation_policy.test.ts) · [`src/application/usecases/localization/__tests__/resolve_message_catalog_use_case.test.ts`](../src/application/usecases/localization/__tests__/resolve_message_catalog_use_case.test.ts) · [`src/prompts/__tests__/localize_message_catalog.test.ts`](../src/prompts/__tests__/localize_message_catalog.test.ts) · [`src/domain/__tests__/github_publication.test.ts`](../src/domain/__tests__/github_publication.test.ts) · [`src/domain/__tests__/git_object_id.test.ts`](../src/domain/__tests__/git_object_id.test.ts) · [`src/data/repository/__tests__/github_publication_source_repository.test.ts`](../src/data/repository/__tests__/github_publication_source_repository.test.ts) · [`src/application/policies/__tests__/publication_identity_policy.test.ts`](../src/application/policies/__tests__/publication_identity_policy.test.ts) · [`src/application/policies/__tests__/publication_outcome_policy.test.ts`](../src/application/policies/__tests__/publication_outcome_policy.test.ts) · [`src/application/policies/__tests__/publication_message_catalog.test.ts`](../src/application/policies/__tests__/publication_message_catalog.test.ts) · [`src/application/policies/__tests__/semantic_result_publication_policy.test.ts`](../src/application/policies/__tests__/semantic_result_publication_policy.test.ts) · [`src/application/policies/__tests__/action_summary_policy.test.ts`](../src/application/policies/__tests__/action_summary_policy.test.ts) · [`src/application/policies/__tests__/branch_sync_notification_policy.test.ts`](../src/application/policies/__tests__/branch_sync_notification_policy.test.ts) · [`src/application/policies/__tests__/inactivity_message_catalog.test.ts`](../src/application/policies/__tests__/inactivity_message_catalog.test.ts) · [`src/application/policies/__tests__/inactivity_notification_policy.test.ts`](../src/application/policies/__tests__/inactivity_notification_policy.test.ts) · [`src/application/policies/__tests__/setup_doctor_message_catalog.test.ts`](../src/application/policies/__tests__/setup_doctor_message_catalog.test.ts) · [`src/application/policies/__tests__/setup_doctor_report_policy.test.ts`](../src/application/policies/__tests__/setup_doctor_report_policy.test.ts) · [`src/application/usecases/actions/__tests__/observe_branch_sync_use_case.test.ts`](../src/application/usecases/actions/__tests__/observe_branch_sync_use_case.test.ts) · [`src/application/usecases/setup/__tests__/doctor_use_case.test.ts`](../src/application/usecases/setup/__tests__/doctor_use_case.test.ts) · [`src/application/usecases/setup/__tests__/merge_queue_readiness_use_case.test.ts`](../src/application/usecases/setup/__tests__/merge_queue_readiness_use_case.test.ts) · [`src/application/policies/__tests__/bugbot_message_catalog.test.ts`](../src/application/policies/__tests__/bugbot_message_catalog.test.ts) · [`src/application/policies/__tests__/deployment_message_catalog.test.ts`](../src/application/policies/__tests__/deployment_message_catalog.test.ts) · [`src/application/policies/__tests__/bugbot_review_presentation_policy.test.ts`](../src/application/policies/__tests__/bugbot_review_presentation_policy.test.ts) · [`src/application/usecases/steps/commit/__tests__/detect_potential_problems_use_case.test.ts`](../src/application/usecases/steps/commit/__tests__/detect_potential_problems_use_case.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/dismiss_bugbot_findings_use_case.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/dismiss_bugbot_findings_use_case.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/synchronize_bugbot_review_presentation_use_case.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/synchronize_bugbot_review_presentation_use_case.test.ts) · [`src/application/policies/__tests__/deployment_presentation_policy.test.ts`](../src/application/policies/__tests__/deployment_presentation_policy.test.ts) · [`src/application/usecases/actions/__tests__/close_inactive_issues_use_case.test.ts`](../src/application/usecases/actions/__tests__/close_inactive_issues_use_case.test.ts) · [`src/application/usecases/__tests__/push_single_action_contexts.test.ts`](../src/application/usecases/__tests__/push_single_action_contexts.test.ts) · [`src/application/usecases/steps/common/__tests__/comment_language_translation_workflow.test.ts`](../src/application/usecases/steps/common/__tests__/comment_language_translation_workflow.test.ts) · [`src/application/usecases/steps/common/__tests__/think_request_policy.test.ts`](../src/application/usecases/steps/common/__tests__/think_request_policy.test.ts) · [`src/application/usecases/steps/common/__tests__/think_use_case.test.ts`](../src/application/usecases/steps/common/__tests__/think_use_case.test.ts) · [`src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts`](../src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts) · [`src/application/usecases/steps/common/__tests__/status_card_publication_workflow.test.ts`](../src/application/usecases/steps/common/__tests__/status_card_publication_workflow.test.ts) · [`src/application/usecases/steps/common/__tests__/reply_publication_workflow.test.ts`](../src/application/usecases/steps/common/__tests__/reply_publication_workflow.test.ts) · [`src/application/usecases/steps/common/__tests__/transition_notification_workflow.test.ts`](../src/application/usecases/steps/common/__tests__/transition_notification_workflow.test.ts) · [`src/application/usecases/steps/common/__tests__/duplicate_comment_cleanup_workflow.test.ts`](../src/application/usecases/steps/common/__tests__/duplicate_comment_cleanup_workflow.test.ts) · [`src/data/repository/issue/__tests__/issue_content_repository.test.ts`](../src/data/repository/issue/__tests__/issue_content_repository.test.ts) · [`src/data/repository/deployment/__tests__/deployment_presentation_repository.test.ts`](../src/data/repository/deployment/__tests__/deployment_presentation_repository.test.ts) · [`src/data/repository/__tests__/github_error_policy.test.ts`](../src/data/repository/__tests__/github_error_policy.test.ts) · [`src/application/usecases/steps/issue/__tests__/answer_issue_help_use_case.test.ts`](../src/application/usecases/steps/issue/__tests__/answer_issue_help_use_case.test.ts) · [`src/application/usecases/__tests__/issue_use_case.test.ts`](../src/application/usecases/__tests__/issue_use_case.test.ts) · [`src/application/usecases/__tests__/issue_pull_request_context_projection.test.ts`](../src/application/usecases/__tests__/issue_pull_request_context_projection.test.ts) · [`src/actions/__tests__/local_action.test.ts`](../src/actions/__tests__/local_action.test.ts) · [`src/__tests__/cli.test.ts`](../src/__tests__/cli.test.ts) · [`src/cli/commands/__tests__/think_command_handler.test.ts`](../src/cli/commands/__tests__/think_command_handler.test.ts) · [`src/infrastructure/composition/__tests__/local_action_composition_root.test.ts`](../src/infrastructure/composition/__tests__/local_action_composition_root.test.ts) · [`src/infrastructure/composition/__tests__/main_run_route_composition_root.test.ts`](../src/infrastructure/composition/__tests__/main_run_route_composition_root.test.ts) · [`src/infrastructure/composition/__tests__/issue_use_case_composition_root.test.ts`](../src/infrastructure/composition/__tests__/issue_use_case_composition_root.test.ts) · [`src/infrastructure/composition/__tests__/shared_capability_port_binding.test.ts`](../src/infrastructure/composition/__tests__/shared_capability_port_binding.test.ts) · [`src/infrastructure/composition/__tests__/pull_request_use_case_composition_root.test.ts`](../src/infrastructure/composition/__tests__/pull_request_use_case_composition_root.test.ts) · [`src/architecture/__tests__/github_publication_boundaries.test.ts`](../src/architecture/__tests__/github_publication_boundaries.test.ts) · [`src/application/usecases/steps/common/__tests__/publication_concurrency.integration.test.ts`](../src/application/usecases/steps/common/__tests__/publication_concurrency.integration.test.ts) · [`src/tooling/__tests__/validate_github_communication_test_budget.test.ts`](../src/tooling/__tests__/validate_github_communication_test_budget.test.ts) · [`src/tooling/__tests__/validate_workflow_contract.test.ts`](../src/tooling/__tests__/validate_workflow_contract.test.ts) · [`src/application/usecases/actions/__tests__/recommend_steps_use_case.test.ts`](../src/application/usecases/actions/__tests__/recommend_steps_use_case.test.ts) · [`src/application/usecases/actions/__tests__/check_progress_use_case.test.ts`](../src/application/usecases/actions/__tests__/check_progress_use_case.test.ts) · [`src/application/usecases/__tests__/comment_automation_use_case.test.ts`](../src/application/usecases/__tests__/comment_automation_use_case.test.ts) +- User documentation: [`docs/configuration.mdx`](../docs/configuration.mdx) · [`docs/configuration-checklist.mdx`](../docs/configuration-checklist.mdx) · [`docs/features.mdx`](../docs/features.mdx) · [`docs/issues/configuration.mdx`](../docs/issues/configuration.mdx) · [`docs/issues/comment-commands.mdx`](../docs/issues/comment-commands.mdx) · [`docs/issues/notifications-and-auto-close.mdx`](../docs/issues/notifications-and-auto-close.mdx) · [`docs/issues/branch-synchronization.mdx`](../docs/issues/branch-synchronization.mdx) · [`docs/issues/type/feature.mdx`](../docs/issues/type/feature.mdx) · [`docs/issues/type/bugfix.mdx`](../docs/issues/type/bugfix.mdx) · [`docs/issues/type/docs.mdx`](../docs/issues/type/docs.mdx) · [`docs/issues/type/chore.mdx`](../docs/issues/type/chore.mdx) · [`docs/issues/type/hotfix.mdx`](../docs/issues/type/hotfix.mdx) · [`docs/issues/type/release.mdx`](../docs/issues/type/release.mdx) · [`docs/issues/deployment-orchestration.mdx`](../docs/issues/deployment-orchestration.mdx) · [`docs/pull-requests/configuration.mdx`](../docs/pull-requests/configuration.mdx) · [`docs/pull-requests/capabilities.mdx`](../docs/pull-requests/capabilities.mdx) · [`docs/pull-requests/workflow-setup.mdx`](../docs/pull-requests/workflow-setup.mdx) · [`docs/pull-requests/examples.mdx`](../docs/pull-requests/examples.mdx) · [`docs/bugbot/configuration.mdx`](../docs/bugbot/configuration.mdx) · [`docs/bugbot/finding-publication.mdx`](../docs/bugbot/finding-publication.mdx) · [`docs/bugbot/detection.mdx`](../docs/bugbot/detection.mdx) · [`docs/bugbot/how-it-works.mdx`](../docs/bugbot/how-it-works.mdx) · [`docs/bugbot/programmatic-api.mdx`](../docs/bugbot/programmatic-api.mdx) · [`docs/bugbot/quality-observability.mdx`](../docs/bugbot/quality-observability.mdx) · [`docs/bugbot/failure-scenarios.mdx`](../docs/bugbot/failure-scenarios.mdx) · [`docs/bugbot/examples.mdx`](../docs/bugbot/examples.mdx) · [`docs/single-actions/configuration.mdx`](../docs/single-actions/configuration.mdx) · [`docs/single-actions/available-actions.mdx`](../docs/single-actions/available-actions.mdx) · [`docs/single-actions/workflow-and-cli.mdx`](../docs/single-actions/workflow-and-cli.mdx) · [`docs/single-actions/examples.mdx`](../docs/single-actions/examples.mdx) · [`docs/security-operations/security/prompt-injection.mdx`](../docs/security-operations/security/prompt-injection.mdx) · [`docs/security-operations/operations/verification.mdx`](../docs/security-operations/operations/verification.mdx) · [`docs/development/architecture.mdx`](../docs/development/architecture.mdx) · [`docs/development/testing.mdx`](../docs/development/testing.mdx) ### `release-orchestration` — Configurable production-first release orchestration diff --git a/specs/catalog.json b/specs/catalog.json index 80350691d..d8b6c204d 100644 --- a/specs/catalog.json +++ b/specs/catalog.json @@ -4,7 +4,7 @@ { "id": "github-communication-experience", "title": "Semantic GitHub communication and repository localization", - "status": "proposed", + "status": "implemented", "scope": "English-default, localized, semantic, bounded, and idempotent product messages across GitHub and repository-aware operator surfaces", "owner": "Copilot maintainers", "lastVerified": "2026-09-15", @@ -30,10 +30,13 @@ "src/cli/commands/check_progress.ts", "src/cli/commands/issue_command_policy.ts", "src/api.ts", - "src/cli.ts" + "src/cli.ts", + "package.json" ], "code": [ "scripts/coverage-budgets.json", + "scripts/validate-github-communication-test-budget.cjs", + "src/architecture/github_communication_test_budget.json", "src/domain/implementation_plan.ts", "src/domain/locale.ts", "src/domain/message_catalog.ts", @@ -179,8 +182,10 @@ "src/infrastructure/composition/__tests__/issue_use_case_composition_root.test.ts", "src/infrastructure/composition/__tests__/shared_capability_port_binding.test.ts", "src/infrastructure/composition/__tests__/pull_request_use_case_composition_root.test.ts", - "src/architecture/__tests__/github_publication_boundaries.test.ts", - "src/tooling/__tests__/validate_workflow_contract.test.ts", + "src/architecture/__tests__/github_publication_boundaries.test.ts", + "src/application/usecases/steps/common/__tests__/publication_concurrency.integration.test.ts", + "src/tooling/__tests__/validate_github_communication_test_budget.test.ts", + "src/tooling/__tests__/validate_workflow_contract.test.ts", "src/application/usecases/actions/__tests__/recommend_steps_use_case.test.ts", "src/application/usecases/actions/__tests__/check_progress_use_case.test.ts", "src/application/usecases/__tests__/comment_automation_use_case.test.ts" @@ -218,7 +223,8 @@ "docs/single-actions/examples.mdx", "docs/security-operations/security/prompt-injection.mdx", "docs/security-operations/operations/verification.mdx", - "docs/development/architecture.mdx" + "docs/development/architecture.mdx", + "docs/development/testing.mdx" ] }, { diff --git a/specs/repository-locale-and-localization.md b/specs/repository-locale-and-localization.md index 4fbb6ce4e..f788fd8ee 100644 --- a/specs/repository-locale-and-localization.md +++ b/specs/repository-locale-and-localization.md @@ -1,15 +1,14 @@ # Repository Locale and End-to-End Localization -- Status: In implementation +- Status: Implemented — automated gates and controlled pull-request UX evidence complete - Date: 2026-09-14 - Catalog capability ID: github-communication-experience -- 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 +- Last verified: 2026-09-15; implementation is on `develop` through PR #389, + with the executable disjoint test-budget and multilingual fixture closure in + this specification revision - 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) +- Related issues/PRs: [issue #334](https://github.com/vypdev/copilot/issues/334), [PR #366](https://github.com/vypdev/copilot/pull/366) through [PR #389](https://github.com/vypdev/copilot/pull/389) - Required review gates: product UX, architecture, testing, documentation, security/operations - Open decisions blocking readiness: none @@ -45,23 +44,23 @@ Addressed input -> deterministic command/authorization -> one safe interpretatio This SDD is the localization foundation required by [`semantic-github-publication-and-notification.md`](./semantic-github-publication-and-notification.md). -## 2. Problem, current behavior, and evidence +## 2. Problem, baseline behavior, and evidence ### 2.1 Problem -The current product has locale-shaped inputs but no single repository-language -contract. Some features translate incoming comments, some render Spanish or -English with ad hoc branching, and many always emit English. As a result, -configuring `issues-locale` or `pull-requests-locale` does not guarantee that the -corresponding product surface uses that language. A conversation can mix an -English generic heading, agent prose in another language, English errors, and a -Spanish feature card. +Before this program, the product had locale-shaped inputs but no single +repository-language contract. Some features translated incoming comments, some +rendered Spanish or English with ad hoc branching, and many always emitted +English. As a result, configuring `issues-locale` or `pull-requests-locale` did +not guarantee that the corresponding product surface used that language. A +conversation could mix an English generic heading, agent prose in another +language, English errors, and a Spanish feature card. -The current translation workflow also mutates the human-authored addressed -comment. Although it preserves the original in a disclosure, editing another -participant's message is a poor authorship and trust boundary: the timeline no -longer shows exactly what that person wrote as the primary body, and downstream -automation can observe model-generated text under the human comment identity. +The baseline translation workflow also mutated the human-authored addressed +comment. Although it preserved the original in a disclosure, editing another +participant's message was a poor authorship and trust boundary: the timeline no +longer showed exactly what that person wrote as the primary body, and downstream +automation could observe model-generated text under the human comment identity. ### 2.2 Baseline behavior before rollout @@ -126,7 +125,7 @@ implementation: `src/application/policies/action_summary_policy.ts`, `src/application/policies/copilot_evidence_policy.ts`, and progress/plan policies and prompts. -- Current docs: `docs/configuration.mdx`, issue/PR configuration pages, +- Baseline documentation inventory: `docs/configuration.mdx`, issue/PR configuration pages, `docs/features.mdx`, comment-command docs, Bugbot docs, deployment docs, and agent CLI configuration docs. @@ -152,8 +151,10 @@ implementation: ### 2.4 Retrospective classification -Not applicable. This is a prospective behavior change. The current behavior is -recorded to define compatibility and migration, not as the desired contract. +This SDD began as a prospective behavior change. Section 2 preserves the +pre-implementation facts used to define compatibility and migration; sections +13–19 record the completed rollout and executable evidence. Baseline behavior +is not part of the implemented contract. ## 3. Actors, surfaces, and terminology @@ -256,9 +257,9 @@ Normative terms: 11. Machine contracts remain stable across locale changes. 12. Locale and translation safety rules are not user-configurable. -## 5. Current versus proposed product journey +## 5. Baseline versus implemented product journey -| Stage | Current | Proposed | User/operator effect | +| Stage | Baseline | Implemented | User/operator effect | |---|---|---|---| | Configure language | independent issue/PR values default to English | repository locale defaults to English; optional issue/PR values inherit | one understandable default and bounded overrides | | Validate locale | arbitrary strings flow to ad hoc checks | canonical BCP-47 value object validates before mutation | predictable configuration | @@ -924,7 +925,7 @@ pull-requests-locale: "" - Durable bot-owned cards rerender in the latest effective locale on their next real semantic update, subject to release snapshot rules. -### 13.4 Rollout +### 13.4 Implementation sequence (completed) 1. Add locale value objects/profile, action/setup migration, catalog manifest, English/Spanish catalogs, validation, and observability without changing @@ -940,7 +941,7 @@ pull-requests-locale: "" 7. Update all named docs, generated workflows/action bundles, and related SDD clauses before announcing support. -Implementation evidence as of 2026-09-14: PRs #366–#374 deliver the canonical +The first implementation slices in PRs #366–#374 delivered the canonical locale profile, typed and validated catalog resolution, English-default shared publication, non-mutating addressed-language adaptation, localized agent response contracts, branch synchronization, and bounded review context. The @@ -975,8 +976,8 @@ Closed recovery variants carry only validated branch-name or issue-number variables, preserving operation-specific partial-state guidance in the same atomic catalog locale. Failures before repository/agent resolution use a deterministic complete -bundled or English-default catalog. Other lifecycle surfaces are not claimed -complete by this evidence. +bundled or English-default catalog. The later slices below completed the +remaining lifecycle and explicit-request surfaces. The addressed-Think follow-up removes its feature-owned comment mutation and returns the same typed `direct-answer` projection as initial issue help. GitHub @@ -1042,6 +1043,17 @@ publication catalog renders source-correlated `sync-branch` outcomes in the requesting conversation's effective locale; branch names, commit IDs, command identity, markers, and correlation remain locale-independent. +The 2026-09-15 closure audit assigns 141 qualifying localization cases to test +files that are disjoint from the 160-case semantic-publication allocation. The +ledger counts each parameterized declaration once and is enforced by +`validate:specifications`, alongside exhaustive locale/catalog coverage and the +closed product-facing agent inventory. Controlled PRs #366–#389 verify the +English default, stable machine names, atomic Check/card updates, and honest +partial states in GitHub. Bundled Spanish, arbitrary dynamic locales, fallback, +right-to-left, CJK, 200% expansion, bidi neutralization, and narrow terminals +remain deterministic fixtures; the audit intentionally does not change the live +repository locale merely to manufacture evidence. + ### 13.5 Rollback Rollback MUST preserve the new input inheritance reader and legacy/new marker @@ -1056,6 +1068,11 @@ The implementation requires at least **136 distinct new or materially rewritten test cases**. Semantic message timing/count/idempotency belongs to the companion SDD and is not double-counted here. +`src/architecture/github_communication_test_budget.json` records 141 qualifying +localization cases from the implementation diff against the specification +baseline. No listed test file appears in the semantic allocation, and the +contract is enforced by `pnpm run validate:specifications`. + | Area | Minimum distinct cases | Behaviors/risks covered | |---|---:|---| | Domain/configuration/pure planning | 26 | defaults, inheritance, canonicalization, invalid/legacy tags, 255-char bound, scope/snapshot, locale equality | @@ -1178,6 +1195,11 @@ tags and never imply that fallback is a successful translation. 22. Given implementation completion, then related SDDs, catalog, action/setup defaults, generated bundles, 136-case budget, coverage, documentation, and all repository validations agree without stale en/es conditionals. +23. Given an unchanged issue whose stored structured plan was produced in a + different or unknown locale, when planning runs in the current effective + issue locale, then replay is forbidden, a configured agent must return a + complete replacement with the exact target locale, `unchanged` is rejected, + and an unconfigured run leaves the previous card untouched. ## 17. Requirements traceability @@ -1198,7 +1220,7 @@ tags and never imply that fallback is a successful translation. | §11 privacy/security | sanitizer, minimizer, telemetry projection | injection/bidi/secret/body-absence tests | security operations | | §12 observability/cost | summary/log/metric projections and run cache | fields, one-call, no-body assertions | quality observability | | §13 compatibility | legacy readers and profile migration | explicit override/v2/v3/rollback tests | migration guide | -| §14 quality budget | coverage/contract scripts | CI evidence | contributor testing guide | +| §14 quality budget | coverage scripts and disjoint communication test ledger | 141 allocated localization cases plus CI coverage evidence | contributor testing guide | ## 18. Implementation sequence @@ -1230,40 +1252,40 @@ tags and never imply that fallback is a successful translation. ## 19. Definition of Done -- [ ] `repository-locale` defaults to `en-US`; issue/PR overrides inherit when +- [x] `repository-locale` defaults to `en-US`; issue/PR overrides inherit when empty; explicit existing values migrate without loss. -- [ ] Any accepted BCP-47 target follows one canonical validation and atomic +- [x] Any accepted BCP-47 target follows one canonical validation and atomic exact/base/dynamic/English fallback contract. -- [ ] Authoritative English and reviewed Spanish catalogs are complete, typed, +- [x] Authoritative English and reviewed Spanish catalogs are complete, typed, versioned, and free of feature-local duplicate conditionals. -- [ ] Every product-facing deterministic and agent-generated artifact uses the +- [x] Every product-facing deterministic and agent-generated artifact uses the effective surface locale; stable machine contracts remain unchanged. -- [ ] Every product-facing agent task carries target locale and validates output +- [x] Every product-facing agent task carries target locale and validates output locale with prose separated from technical operands. -- [ ] Addressed translation uses at most one adaptation call, never edits the +- [x] 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, slash paths, bare +- [x] 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, +- [x] 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, +- [x] RTL, CJK, expansion, plural, narrow-width, descriptive-link, sanitization, and bidi tests/manual evidence pass. -- [ ] Architecture, source-string, agent-task, catalog-manifest, and no-comment- +- [x] Architecture, source-string, agent-task, catalog-manifest, and no-comment- update constraints are executable and blocking. -- [ ] The 136-case numeric budget and changed-module coverage gates pass without +- [x] The 136-case numeric budget and changed-module coverage gates pass without double counting semantic publication tests. -- [ ] Action/setup/doctor/CLI/workflow schemas, persisted variables, generated +- [x] Action/setup/doctor/CLI/workflow schemas, persisted variables, generated bundles, examples, and defaults agree. -- [ ] User, setup, operator, security, migration, and contributor documentation +- [x] User, setup, operator, security, migration, and contributor documentation is complete, fixture-backed, discoverable, and contains no obsolete claims. -- [ ] Related implemented/as-built SDD localization clauses are amended in the +- [x] Related implemented/as-built SDD localization clauses are amended in the implementation change so there is one current normative contract. -- [ ] `specs/catalog.json` and `specs/CATALOG.md` are current and +- [x] `specs/catalog.json` and `specs/CATALOG.md` are current and `pnpm run validate:specifications` passes. -- [ ] No readiness-blocking decision remains unresolved. +- [x] No readiness-blocking decision remains unresolved. ## 20. References and decisions diff --git a/specs/semantic-github-publication-and-notification.md b/specs/semantic-github-publication-and-notification.md index 95549d3d8..f0fa53aae 100644 --- a/specs/semantic-github-publication-and-notification.md +++ b/specs/semantic-github-publication-and-notification.md @@ -1,17 +1,14 @@ # Semantic GitHub Publication and Notification -- Status: In implementation +- Status: Implemented — automated gates and controlled pull-request UX evidence complete - Date: 2026-09-14 - Catalog capability ID: github-communication-experience -- Last verified: 2026-09-15 for the delivered shared publication, progress - source-freshness, exact-duplicate cleanup, transition-notification substrate, - branch-sync transition adoption, - review-context, Bugbot, deployment, setup-doctor, generic Job Summary, - application-error, explicit-request, and local-result slices; remaining - clauses are prospective +- Last verified: 2026-09-15; implementation is on `develop` through PR #389, + with the executable concurrency and disjoint test-budget closure in this + specification revision - 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) +- 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 #366](https://github.com/vypdev/copilot/pull/366) through [PR #389](https://github.com/vypdev/copilot/pull/389) - Required review gates: product UX, architecture, testing, documentation, security/operations - Open decisions blocking readiness: none @@ -45,16 +42,17 @@ GitHub event -> capability result -> semantic publication decision -> operator evidence in Job Summary and logs ``` -## 2. Problem, current behavior, and evidence +## 2. Problem, baseline behavior, and evidence ### 2.1 Problem -Maintainers currently have to scan many comments to discover whether anything -material happened. The dominant comment template describes the branch category -and internal steps rather than the outcome. A one-line fact such as “waiting -state cleared” can arrive inside a roughly one-kilobyte comment containing a -level-one heading, a GIF, a debug disclosure, and a footer. Repeated workflow -events produce new timeline entries instead of updating the state already shown. +Before this program, maintainers had to scan many comments to discover whether +anything material happened. The dominant comment template described the branch +category and internal steps rather than the outcome. A one-line fact such as +“waiting state cleared” could arrive inside a roughly one-kilobyte comment +containing a level-one heading, a GIF, a debug disclosure, and a footer. +Repeated workflow events produced new timeline entries instead of updating the +state already shown. The result has four product costs: @@ -66,9 +64,11 @@ The result has four product costs: 4. internal execution details, including runner paths, can leak into a public collaboration surface even when they belong only in operator evidence. -### 2.2 Current behavior +### 2.2 Baseline behavior before rollout -The following sequence is verified in the 2026-09-14 repository snapshot: +The following sequence was verified in the pre-implementation 2026-09-14 +repository snapshot. It is retained as migration evidence and is not a claim +about the implemented product: 1. Use cases append human-readable strings to `Result.steps`, regardless of whether those strings are product messages or operational evidence. @@ -92,7 +92,7 @@ The following sequence is verified in the 2026-09-14 repository snapshot: ### 2.3 Evidence -#### Repository evidence +#### Baseline repository evidence - Generic completion path: `src/application/usecases/steps/common/publish_resume_workflow.ts`, @@ -151,13 +151,13 @@ Representative failure modes: - issue #334 contains two very large, substantially overlapping implementation plans and exposed local runner-oriented details that are not useful issue UI. -#### Documentation and external evidence +#### Baseline documentation and external evidence -- `docs/configuration.mdx`, `docs/issues/configuration.mdx`, and - `docs/pull-requests/capabilities.mdx` currently present images as enabled by +- At the baseline, `docs/configuration.mdx`, `docs/issues/configuration.mdx`, + and `docs/pull-requests/capabilities.mdx` presented images as enabled by default. -- `docs/issues/notifications-and-auto-close.mdx` describes lifecycle comments - without a cross-capability notification budget. +- At the baseline, `docs/issues/notifications-and-auto-close.mdx` described + lifecycle comments without a cross-capability notification budget. - GitHub documents that creating issue, commit, and review comments can trigger notifications and secondary rate limiting. GitHub also provides update endpoints, so stable bot-owned comments are an available provider primitive. @@ -166,9 +166,10 @@ Representative failure modes: ### 2.4 Retrospective classification -Not applicable. This is a prospective behavior change. Current facts are -recorded only to establish the migration boundary; they are not presented as -the desired contract. +This SDD began as a prospective behavior change. Section 2 preserves the +pre-implementation facts that established the migration boundary; sections +13–19 record the completed rollout and executable evidence. Baseline behavior +is not part of the implemented contract. ## 3. Actors, surfaces, and terminology @@ -257,9 +258,9 @@ Terms used normatively: operation. 10. Notification and size budgets are safety limits and are not configurable. -## 5. Current versus proposed product journey +## 5. Baseline versus implemented product journey -| Stage | Current | Proposed | User/operator effect | +| Stage | Baseline | Implemented | User/operator effect | |---|---|---|---| | Capability work | Use cases accumulate prose in `Result.steps` | Use cases return semantic outcome plus operator evidence | Meaning is explicit and testable | | Publication decision | Any visible step/error usually enables a comment | Pure policy selects none, reply, status, transition, or inline finding | Routine work stays quiet | @@ -961,7 +962,7 @@ generic output is left intact. - The next major removes image pool inputs and all random-image selection code. - No compatibility flag restores generic result comments or debug disclosure. -### 13.3 Rollout +### 13.3 Implementation sequence (completed) 1. Land contracts, characterization tests, metrics, and conversation string/call inventories with no behavior change. @@ -977,10 +978,10 @@ generic output is left intact. 6. Remove the legacy generic renderer after one release with zero observed fallback use. -Implementation evidence as of 2026-09-14: PRs #367–#372 establish semantic +The first implementation slices in PRs #367–#372 established semantic reply/status publication, remove retired generic conversation chrome from the covered paths, localize branch-sync state, and keep automated reports out of -Bugbot's human-conversation context. The current Bugbot slice preserves its +Bugbot's human-conversation context. The Bugbot slice preserves its 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 @@ -988,10 +989,10 @@ mutation inventory, locale-branch ratchet, and pseudo-plural ratchet protect these boundaries. Subsequent deployment and setup-doctor slices cover their feature-owned views, and the generic Job Summary slice resolves repository- locale copy atomically, renders aggregate result counts and expands only safe -code-specific localized error recovery, excludes internal result names, steps, and -arbitrary error messages, 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. +code-specific localized error recovery, excludes internal result names, steps, +and arbitrary error messages, and renders one localization evidence section +instead of two. The later slices below completed the remaining capability rows; +the closure ledger provides the final non-overlapping numeric evidence. The shared error-presentation slice replaces route-local Action termination with semantic results and one final locale-aware conclusion boundary. Generic and @@ -1110,6 +1111,19 @@ An explicitly addressed `sync-branch` request produces one source-correlated semantic reply for aligned, dry-run, and successful merge outcomes; replay reuses the reply and never exposes the command's internal `Result.steps`. +The 2026-09-15 closure audit runs two first-publication calls simultaneously for +status cards, correlated replies, and immutable transition notifications. All +three paths converge on the lowest bot-owned comment ID and idempotently remove +the later exact duplicate. The checked-in budget ledger conservatively allocates +160 semantic cases and 141 localization cases to disjoint test files; matrix rows +count once, and `validate:specifications` fails if a file crosses budgets, an +allocation exceeds retained test declarations, or either SDD falls below its +numeric minimum. PRs #366–#389 provide controlled English-default GitHub +evidence for quiet updates, stable Checks, one edited Bugbot card, partial +coverage disclosure, and inline-finding resolution; multilingual, bidi, CJK, +expansion, and narrow-layout states are retained as deterministic renderer and +terminal fixtures so validation does not mutate repository-wide locale settings. + 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. @@ -1129,6 +1143,11 @@ test cases**. This budget covers semantic publication only; locale resolution, translation correctness, and multilingual matrices are counted in the companion SDD and MUST NOT be double-counted here. +`src/architecture/github_communication_test_budget.json` records 160 qualifying +semantic cases from the implementation diff against the specification baseline. +Its files are disjoint from the localization allocation and are enforced by +`pnpm run validate:specifications`. + | Area | Minimum distinct cases | Behaviors/risks covered | |---|---:|---| | Domain/configuration/pure planning | 26 | intent decision matrix, identity, digest, transition fingerprint, size/noise rules, image deprecation | @@ -1240,6 +1259,10 @@ removed. then Think performs no comment mutation and the shared reply boundary creates or reconciles exactly one `direct-answer` for the source-comment identity; local CLI execution prints the same semantic answer without GitHub writes. +21. Given two application calls that both observe no existing status, reply, or + transition comment before either create returns, then both provider creates + succeed, both calls select the lowest ID after re-listing, and the durable + conversation retains exactly one active bot-owned publication. ## 17. Requirements traceability @@ -1260,7 +1283,7 @@ removed. | §11 trust/ownership | marker parser, sanitizer, authorization | forged marker/mention/secret tests | security operations | | §12 evidence/metrics | Job Summary and telemetry ports | summary/metric assertions | quality observability | | §13 migration | legacy marker readers and deprecated inputs | adoption/rollback/deprecation tests | migration guide | -| §14 quality budget | coverage and validation scripts | CI evidence | contributor testing guide | +| §14 quality budget | coverage scripts and disjoint communication test ledger | 160 allocated semantic cases plus CI coverage evidence | contributor testing guide | ## 18. Implementation sequence @@ -1289,32 +1312,32 @@ removed. ## 19. Definition of Done -- [ ] Every conversation publication is created from a typed semantic intent; +- [x] Every conversation publication is created from a typed semantic intent; generic `Result.steps` publication is impossible. -- [ ] All capability rows in §6.2 satisfy their create/update budgets. -- [ ] One-card identity, unchanged replay, stale rejection, concurrent creation, +- [x] All capability rows in §6.2 satisfy their create/update budgets. +- [x] One-card identity, unchanged replay, stale rejection, concurrent creation, and exact duplicate cleanup pass. -- [ ] Plans, progress, branch sync, Bugbot, releases, lifecycle, failures, +- [x] Plans, progress, branch sync, Bugbot, releases, lifecycle, failures, explicit replies, and single actions implement the representative contract. -- [ ] No new conversation contains retired generic headings, debug logs, +- [x] No new conversation contains retired generic headings, debug logs, decorative media by default, “Happy coding”, or the visible default marketplace watermark. -- [ ] Job Summary and logs retain bounded, masked operator evidence and link from +- [x] Job Summary and logs retain bounded, masked operator evidence and link from actionable UI. -- [ ] Architecture boundaries and direct-mutation inventory are automatically +- [x] Architecture boundaries and direct-mutation inventory are automatically enforced. -- [ ] The 128-case numeric budget and changed-module coverage gates pass without +- [x] The 128-case numeric budget and changed-module coverage gates pass without double counting localization tests. -- [ ] Configuration defaults, deprecated image window, setup, doctor, action +- [x] Configuration defaults, deprecated image window, setup, doctor, action schema, and generated bundles agree. -- [ ] English-default and configured-locale output passes the companion SDD. -- [ ] User, setup, operator, security, migration, and contributor documentation +- [x] English-default and configured-locale output passes the companion SDD. +- [x] User, setup, operator, security, migration, and contributor documentation is complete, discoverable, fixture-backed, and contains no stale behavior. -- [ ] Failure, partial success, retry, idempotency, security, and cleanup tests +- [x] Failure, partial success, retry, idempotency, security, and cleanup tests pass without replaying domain mutations. -- [ ] `specs/catalog.json` and `specs/CATALOG.md` are current and +- [x] `specs/catalog.json` and `specs/CATALOG.md` are current and `pnpm run validate:specifications` passes. -- [ ] No readiness-blocking decision remains unresolved. +- [x] No readiness-blocking decision remains unresolved. ## 20. References and decisions diff --git a/src/application/usecases/steps/common/__tests__/publication_concurrency.integration.test.ts b/src/application/usecases/steps/common/__tests__/publication_concurrency.integration.test.ts new file mode 100644 index 000000000..7a52f7e76 --- /dev/null +++ b/src/application/usecases/steps/common/__tests__/publication_concurrency.integration.test.ts @@ -0,0 +1,157 @@ +import { Result } from '../../../../../data/model/result'; +import type { TransitionPublicationIntent } from '../../../../../domain/github_publication'; +import type { + BoundIssueCommentPublicationPort, + IssueCommentPublicationTarget, +} from '../../../../ports/issue_lifecycle_ports'; +import { + selectSemanticReplyIntents, + selectSemanticStatusIntents, +} from '../../../../policies/semantic_result_publication_policy'; +import { reconcileReply } from '../reply_publication_workflow'; +import { reconcileStatusCard } from '../status_card_publication_workflow'; +import { reconcileTransitionNotification } from '../transition_notification_workflow'; + +const SOURCE_HEAD = 'a'.repeat(40); + +function simultaneousCreatePort(): BoundIssueCommentPublicationPort & { + readonly comments: IssueCommentPublicationTarget[]; +} { + const comments: IssueCommentPublicationTarget[] = []; + let nextId = 1; + let additions = 0; + let releaseCreates: (() => void) | undefined; + const bothCreatesStarted = new Promise((resolve) => { + releaseCreates = resolve; + }); + return { + comments, + listIssueComments: jest.fn(async () => comments.map(comment => ({ ...comment }))), + addComment: jest.fn(async (_issueNumber: number, body: string) => { + comments.push({ id: nextId, body, user: { login: 'vypbot' } }); + nextId += 1; + additions += 1; + if (additions === 2) releaseCreates?.(); + await bothCreatesStarted; + }), + updateComment: jest.fn(async (_issueNumber: number, commentId: number, body: string) => { + const comment = comments.find(candidate => candidate.id === commentId); + if (comment) comment.body = body; + }), + removeComment: jest.fn(async (_issueNumber: number, commentId: number) => { + const index = comments.findIndex(comment => comment.id === commentId); + if (index >= 0) comments.splice(index, 1); + return 'removed' as const; + }), + }; +} + +function statusIntent() { + return selectSemanticStatusIntents({ + locale: 'en-US', + results: [new Result({ + id: 'CheckProgressUseCase', + success: true, + executed: true, + payload: { + issueNumber: 7, + progress: 50, + summary: 'Implementation is in progress.', + branch: 'feature/work', + sourceHeadSha: SOURCE_HEAD, + }, + })], + })[0]; +} + +function replyIntent() { + return selectSemanticReplyIntents({ + locale: 'en-US', + target: { kind: 'issue', number: 7 }, + correlationId: 'comment:41', + botLogin: 'vypbot', + results: [new Result({ + id: 'Comment.Help', + success: true, + executed: true, + payload: { publication: { kind: 'help', botLogin: 'vypbot' } }, + })], + })[0]; +} + +function transitionIntent(): TransitionPublicationIntent { + return { + kind: 'transition', + identity: { + topic: 'branch-sync', + target: { kind: 'issue', number: 7 }, + key: 'develop:feature-7', + }, + fingerprint: '0123abcd', + messageKey: 'branch-sync-action-required', + locale: 'en-US', + values: {}, + }; +} + +describe('semantic publication concurrency', () => { + it('converges simultaneous first status-card creates on the lowest comment id', async () => { + const comments = simultaneousCreatePort(); + const context = { + owner: 'acme', repository: 'widgets', botLogin: 'vypbot', intent: statusIntent(), + }; + const source = () => ({ getBranchHeadSha: jest.fn(async () => SOURCE_HEAD) }); + + const outcomes = await Promise.all([ + reconcileStatusCard(context, comments, source()), + reconcileStatusCard(context, comments, source()), + ]); + + expect(outcomes).toEqual([ + expect.objectContaining({ effect: 'created', canonicalCommentId: 1, duplicatesRemoved: 1 }), + expect.objectContaining({ effect: 'created', canonicalCommentId: 1, duplicatesRemoved: 1 }), + ]); + expect(comments.comments.map(comment => comment.id)).toEqual([1]); + }); + + it('converges simultaneous first correlated replies on the lowest comment id', async () => { + const comments = simultaneousCreatePort(); + const context = { + owner: 'acme', repository: 'widgets', botLogin: 'vypbot', intent: replyIntent(), + }; + + const outcomes = await Promise.all([ + reconcileReply(context, comments), + reconcileReply(context, comments), + ]); + + expect(outcomes).toEqual([ + expect.objectContaining({ effect: 'created', canonicalCommentId: 1, duplicatesRemoved: 1 }), + expect.objectContaining({ effect: 'created', canonicalCommentId: 1, duplicatesRemoved: 1 }), + ]); + expect(comments.comments.map(comment => comment.id)).toEqual([1]); + }); + + it('converges simultaneous first transition notifications on the lowest comment id', async () => { + const comments = simultaneousCreatePort(); + const context = { + owner: 'acme', + repository: 'widgets', + botLogin: 'vypbot', + intent: transitionIntent(), + message: 'Branch synchronization needs attention.', + duplicatePointer: (url: string) => `This duplicate was suppressed. [View the original](${url}).`, + }; + + const outcomes = await Promise.all([ + reconcileTransitionNotification(context, comments), + reconcileTransitionNotification(context, comments), + ]); + + expect(outcomes).toEqual([ + expect.objectContaining({ effect: 'created', canonicalCommentId: 1, duplicatesRemoved: 1 }), + expect.objectContaining({ effect: 'created', canonicalCommentId: 1, duplicatesRemoved: 1 }), + ]); + expect(comments.comments.map(comment => comment.id)).toEqual([1]); + }); +}); diff --git a/src/architecture/github_communication_test_budget.json b/src/architecture/github_communication_test_budget.json new file mode 100644 index 000000000..75322f577 --- /dev/null +++ b/src/architecture/github_communication_test_budget.json @@ -0,0 +1,62 @@ +{ + "version": 1, + "baseline": "0bb83b75^", + "method": "Conservative count of new Jest it/test declarations since the specification baseline; parameterized rows count once.", + "budgets": [ + { + "id": "semantic-github-publication", + "spec": "specs/semantic-github-publication-and-notification.md", + "requiredCases": 128, + "allocatedCases": 160, + "files": [ + { "path": "src/domain/__tests__/github_publication.test.ts", "qualifyingCases": 5 }, + { "path": "src/domain/__tests__/git_object_id.test.ts", "qualifyingCases": 2 }, + { "path": "src/domain/__tests__/implementation_plan.test.ts", "qualifyingCases": 2 }, + { "path": "src/application/policies/__tests__/publication_identity_policy.test.ts", "qualifyingCases": 14 }, + { "path": "src/application/policies/__tests__/publication_outcome_policy.test.ts", "qualifyingCases": 7 }, + { "path": "src/application/policies/__tests__/semantic_result_publication_policy.test.ts", "qualifyingCases": 30 }, + { "path": "src/application/usecases/steps/common/__tests__/status_card_publication_workflow.test.ts", "qualifyingCases": 16 }, + { "path": "src/application/usecases/steps/common/__tests__/reply_publication_workflow.test.ts", "qualifyingCases": 8 }, + { "path": "src/application/usecases/steps/common/__tests__/transition_notification_workflow.test.ts", "qualifyingCases": 10 }, + { "path": "src/application/usecases/steps/common/__tests__/duplicate_comment_cleanup_workflow.test.ts", "qualifyingCases": 5 }, + { "path": "src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts", "qualifyingCases": 21 }, + { "path": "src/application/usecases/steps/common/__tests__/publication_concurrency.integration.test.ts", "qualifyingCases": 3 }, + { "path": "src/application/usecases/actions/__tests__/observe_branch_sync_use_case.test.ts", "qualifyingCases": 7 }, + { "path": "src/application/usecases/actions/__tests__/check_progress_use_case.test.ts", "qualifyingCases": 5 }, + { "path": "src/application/usecases/actions/__tests__/close_inactive_issues_use_case.test.ts", "qualifyingCases": 6 }, + { "path": "src/application/usecases/__tests__/issue_use_case.test.ts", "qualifyingCases": 8 }, + { "path": "src/architecture/__tests__/github_publication_boundaries.test.ts", "qualifyingCases": 11 } + ] + }, + { + "id": "repository-locale-localization", + "spec": "specs/repository-locale-and-localization.md", + "requiredCases": 136, + "allocatedCases": 141, + "files": [ + { "path": "src/actions/__tests__/github_action_locale_inputs.test.ts", "qualifyingCases": 4 }, + { "path": "src/application/policies/__tests__/action_summary_message_catalog.test.ts", "qualifyingCases": 4 }, + { "path": "src/application/policies/__tests__/agent_output_locale_policy.test.ts", "qualifyingCases": 4 }, + { "path": "src/application/policies/__tests__/application_error_message_catalog.test.ts", "qualifyingCases": 5 }, + { "path": "src/application/policies/__tests__/bugbot_message_catalog.test.ts", "qualifyingCases": 4 }, + { "path": "src/application/policies/__tests__/comment_translation_policy.test.ts", "qualifyingCases": 15 }, + { "path": "src/application/policies/__tests__/deployment_message_catalog.test.ts", "qualifyingCases": 5 }, + { "path": "src/application/policies/__tests__/inactivity_message_catalog.test.ts", "qualifyingCases": 5 }, + { "path": "src/application/policies/__tests__/publication_message_catalog.test.ts", "qualifyingCases": 6 }, + { "path": "src/application/policies/__tests__/pull_request_description_content_policy.test.ts", "qualifyingCases": 7 }, + { "path": "src/application/policies/__tests__/resolved_message_catalog_policy.test.ts", "qualifyingCases": 2 }, + { "path": "src/application/policies/__tests__/setup_doctor_message_catalog.test.ts", "qualifyingCases": 5 }, + { "path": "src/application/policies/__tests__/setup_doctor_report_policy.test.ts", "qualifyingCases": 5 }, + { "path": "src/application/usecases/actions/__tests__/recommend_steps_use_case.test.ts", "qualifyingCases": 18 }, + { "path": "src/application/usecases/localization/__tests__/resolve_message_catalog_use_case.test.ts", "qualifyingCases": 8 }, + { "path": "src/application/usecases/setup/__tests__/doctor_use_case.test.ts", "qualifyingCases": 4 }, + { "path": "src/application/usecases/setup/__tests__/merge_queue_readiness_use_case.test.ts", "qualifyingCases": 1 }, + { "path": "src/application/usecases/steps/common/__tests__/comment_language_translation_workflow.test.ts", "qualifyingCases": 13 }, + { "path": "src/architecture/__tests__/agent_output_locale_boundaries.test.ts", "qualifyingCases": 2 }, + { "path": "src/cli/__tests__/setup_prompt_rendering.test.ts", "qualifyingCases": 2 }, + { "path": "src/domain/__tests__/locale.test.ts", "qualifyingCases": 12 }, + { "path": "src/domain/__tests__/message_catalog.test.ts", "qualifyingCases": 10 } + ] + } + ] +} diff --git a/src/tooling/__tests__/validate_github_communication_test_budget.test.ts b/src/tooling/__tests__/validate_github_communication_test_budget.test.ts new file mode 100644 index 000000000..654f06bbe --- /dev/null +++ b/src/tooling/__tests__/validate_github_communication_test_budget.test.ts @@ -0,0 +1,80 @@ +interface CommunicationBudgetLedger { + readonly version: number; + readonly baseline: string; + readonly budgets: readonly { + readonly id: string; + readonly spec: string; + readonly requiredCases: number; + readonly allocatedCases: number; + readonly files: readonly { readonly path: string; readonly qualifyingCases: number }[]; + }[]; +} + +interface CommunicationBudgetValidator { + countQualifyingDeclarations(root: string, baseline: string, testPath: string): number; + readLedger(root?: string): CommunicationBudgetLedger; + validateCommunicationTestBudget(root: string, ledger: CommunicationBudgetLedger): string[]; +} + +const { + countQualifyingDeclarations, + readLedger, + validateCommunicationTestBudget, +} = require('../../../scripts/validate-github-communication-test-budget.cjs') as CommunicationBudgetValidator; + +function mutableLedger(): any { + return structuredClone(readLedger(process.cwd())); +} + +describe('GitHub communication test-budget validator', () => { + it('keeps both specification budgets above target with disjoint test files', () => { + const ledger = readLedger(process.cwd()); + + expect(validateCommunicationTestBudget(process.cwd(), ledger)).toEqual([]); + expect(ledger.budgets.map(budget => ({ + id: budget.id, + allocated: budget.allocatedCases, + required: budget.requiredCases, + }))).toEqual([ + { id: 'semantic-github-publication', allocated: 160, required: 128 }, + { id: 'repository-locale-localization', allocated: 141, required: 136 }, + ]); + for (const budget of ledger.budgets) { + for (const entry of budget.files) { + expect(countQualifyingDeclarations(process.cwd(), ledger.baseline, entry.path)) + .toBeGreaterThanOrEqual(entry.qualifyingCases); + } + } + }); + + it('rejects cross-budget test-file double counting', () => { + const ledger = mutableLedger(); + ledger.budgets[1].files[0].path = ledger.budgets[0].files[0].path; + + expect(validateCommunicationTestBudget(process.cwd(), ledger)).toContain( + `${ledger.budgets[0].files[0].path} is allocated to more than one communication budget.`, + ); + }); + + it('rejects an allocation total that no longer matches its evidence rows', () => { + const ledger = mutableLedger(); + ledger.budgets[0].allocatedCases -= 1; + + expect(validateCommunicationTestBudget(process.cwd(), ledger)).toContain( + 'semantic-github-publication allocatedCases is 159 but its file allocation totals 160.', + ); + }); + + it('rejects unsafe paths and case allocations larger than the retained tests', () => { + const ledger = mutableLedger(); + ledger.budgets[0].files[0] = { path: '../outside.test.ts', qualifyingCases: 999 }; + ledger.budgets[0].files[1].qualifyingCases = 999; + + const errors = validateCommunicationTestBudget(process.cwd(), ledger); + + expect(errors).toContain('semantic-github-publication has an invalid test path: ../outside.test.ts.'); + expect(errors).toContain( + 'src/domain/__tests__/git_object_id.test.ts has 2 qualifying test declarations since 0bb83b75^, below its allocation of 999.', + ); + }); +}); From 97fbcfcf153a189e475a4d2dd3386ff916a68b35 Mon Sep 17 00:00:00 2001 From: Efra Espada Date: Tue, 15 Sep 2026 14:07:00 +0200 Subject: [PATCH 2/3] codex-communication-contract-closure: Reference closure pull request evidence --- specs/repository-locale-and-localization.md | 9 +++++---- ...mantic-github-publication-and-notification.md | 16 +++++++++------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/specs/repository-locale-and-localization.md b/specs/repository-locale-and-localization.md index f788fd8ee..af003d474 100644 --- a/specs/repository-locale-and-localization.md +++ b/specs/repository-locale-and-localization.md @@ -4,11 +4,11 @@ - Date: 2026-09-14 - Catalog capability ID: github-communication-experience - Last verified: 2026-09-15; implementation is on `develop` through PR #389, - with the executable disjoint test-budget and multilingual fixture closure in - this specification revision + with the executable disjoint test-budget and multilingual fixture closure + verified in PR #390 - 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 #366](https://github.com/vypdev/copilot/pull/366) through [PR #389](https://github.com/vypdev/copilot/pull/389) +- Related issues/PRs: [issue #334](https://github.com/vypdev/copilot/issues/334), [PR #366](https://github.com/vypdev/copilot/pull/366) through [PR #390](https://github.com/vypdev/copilot/pull/390) - Required review gates: product UX, architecture, testing, documentation, security/operations - Open decisions blocking readiness: none @@ -1049,7 +1049,8 @@ ledger counts each parameterized declaration once and is enforced by `validate:specifications`, alongside exhaustive locale/catalog coverage and the closed product-facing agent inventory. Controlled PRs #366–#389 verify the English default, stable machine names, atomic Check/card updates, and honest -partial states in GitHub. Bundled Spanish, arbitrary dynamic locales, fallback, +partial states in GitHub; PR #390 closes the executable concurrency and +disjoint-budget evidence. Bundled Spanish, arbitrary dynamic locales, fallback, right-to-left, CJK, 200% expansion, bidi neutralization, and narrow terminals remain deterministic fixtures; the audit intentionally does not change the live repository locale merely to manufacture evidence. diff --git a/specs/semantic-github-publication-and-notification.md b/specs/semantic-github-publication-and-notification.md index f0fa53aae..b07b5aba2 100644 --- a/specs/semantic-github-publication-and-notification.md +++ b/specs/semantic-github-publication-and-notification.md @@ -4,11 +4,11 @@ - Date: 2026-09-14 - Catalog capability ID: github-communication-experience - Last verified: 2026-09-15; implementation is on `develop` through PR #389, - with the executable concurrency and disjoint test-budget closure in this - specification revision + with the executable concurrency and disjoint test-budget closure verified in + PR #390 - 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 #366](https://github.com/vypdev/copilot/pull/366) through [PR #389](https://github.com/vypdev/copilot/pull/389) +- 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 #366](https://github.com/vypdev/copilot/pull/366) through [PR #390](https://github.com/vypdev/copilot/pull/390) - Required review gates: product UX, architecture, testing, documentation, security/operations - Open decisions blocking readiness: none @@ -1117,10 +1117,12 @@ three paths converge on the lowest bot-owned comment ID and idempotently remove the later exact duplicate. The checked-in budget ledger conservatively allocates 160 semantic cases and 141 localization cases to disjoint test files; matrix rows count once, and `validate:specifications` fails if a file crosses budgets, an -allocation exceeds retained test declarations, or either SDD falls below its -numeric minimum. PRs #366–#389 provide controlled English-default GitHub -evidence for quiet updates, stable Checks, one edited Bugbot card, partial -coverage disclosure, and inline-finding resolution; multilingual, bidi, CJK, +allocation exceeds qualifying declarations since the baseline, or either SDD +falls below its numeric minimum. PRs #366–#389 provide controlled +English-default GitHub evidence for quiet updates, stable Checks, one edited +Bugbot card, partial +coverage disclosure, and inline-finding resolution; PR #390 closes the +executable concurrency and disjoint-budget evidence. Multilingual, bidi, CJK, expansion, and narrow-layout states are retained as deterministic renderer and terminal fixtures so validation does not mutate repository-wide locale settings. From 5e19edc9e844b13eaf485192d109b4533849f3be Mon Sep 17 00:00:00 2001 From: Efra Espada Date: Tue, 15 Sep 2026 14:21:00 +0200 Subject: [PATCH 3/3] codex-communication-contract-closure: Polish implemented specification history --- specs/repository-locale-and-localization.md | 6 +++--- specs/semantic-github-publication-and-notification.md | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/specs/repository-locale-and-localization.md b/specs/repository-locale-and-localization.md index af003d474..382d97fd6 100644 --- a/specs/repository-locale-and-localization.md +++ b/specs/repository-locale-and-localization.md @@ -125,9 +125,9 @@ implementation: `src/application/policies/action_summary_policy.ts`, `src/application/policies/copilot_evidence_policy.ts`, and progress/plan policies and prompts. -- Baseline documentation inventory: `docs/configuration.mdx`, issue/PR configuration pages, - `docs/features.mdx`, comment-command docs, Bugbot docs, deployment docs, and - agent CLI configuration docs. +- Baseline documentation inventory: `docs/configuration.mdx`, issue/PR + configuration pages, `docs/features.mdx`, comment-command docs, Bugbot docs, + deployment docs, and agent CLI configuration docs. #### External primary sources diff --git a/specs/semantic-github-publication-and-notification.md b/specs/semantic-github-publication-and-notification.md index b07b5aba2..d5182ab05 100644 --- a/specs/semantic-github-publication-and-notification.md +++ b/specs/semantic-github-publication-and-notification.md @@ -979,8 +979,8 @@ generic output is left intact. fallback use. The first implementation slices in PRs #367–#372 established semantic -reply/status publication, remove retired generic conversation chrome from the -covered paths, localize branch-sync state, and keep automated reports out of +reply/status publication, removed retired generic conversation chrome from the +covered paths, localized branch-sync state, and kept automated reports out of Bugbot's human-conversation context. The Bugbot slice preserves its feature-owned review/thread model while replacing ad hoc public strings with a single typed catalog per publication operation, proper plural forms, stable @@ -1120,9 +1120,9 @@ count once, and `validate:specifications` fails if a file crosses budgets, an allocation exceeds qualifying declarations since the baseline, or either SDD falls below its numeric minimum. PRs #366–#389 provide controlled English-default GitHub evidence for quiet updates, stable Checks, one edited -Bugbot card, partial -coverage disclosure, and inline-finding resolution; PR #390 closes the -executable concurrency and disjoint-budget evidence. Multilingual, bidi, CJK, +Bugbot card, partial coverage disclosure, and inline-finding resolution. PR #390 +closes the executable concurrency and disjoint-budget evidence. Multilingual, +bidi, CJK, expansion, and narrow-layout states are retained as deterministic renderer and terminal fixtures so validation does not mutate repository-wide locale settings.