diff --git a/build/cli/index.js b/build/cli/index.js index e9a6fef61..da5f09a08 100755 --- a/build/cli/index.js +++ b/build/cli/index.js @@ -36949,7 +36949,7 @@ exports.shouldTrackAgentActivity = shouldTrackAgentActivity; const agent_1 = __nccwpck_require__(89040); const copilot_command_1 = __nccwpck_require__(11771); const branch_sync_command_1 = __nccwpck_require__(51114); -const DETERMINISTIC_COMMENT_COMMANDS = new Set(['help', 'status', 'history', 'dismiss', 'remember']); +const DETERMINISTIC_COMMENT_COMMANDS = new Set(['help', 'status', 'doctor', 'history', 'dismiss', 'remember']); /** Decides whether a route can invoke an agent for its current event. */ function shouldTrackAgentActivity(execution, route) { if (!hasTarget(execution)) @@ -40785,6 +40785,7 @@ const READ_ONLY_COMMANDS = Object.freeze([ ['/copilot recheck', 'interaction.help.command.recheck'], ['/copilot description', 'interaction.help.command.description'], ['/copilot status', 'interaction.help.command.status'], + ['/copilot doctor', 'interaction.help.command.doctor'], ['/copilot history', 'interaction.help.command.history'], ]); const CHANGE_COMMANDS = Object.freeze([ @@ -41744,6 +41745,138 @@ function shortSha(value) { } +/***/ }), + +/***/ 61833: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.doctorWorkflowCondition = doctorWorkflowCondition; +/** Bounded three-valued interpretation; unsupported runtime facts remain unknown. Never eval YAML. */ +function doctorWorkflowCondition(value, event) { + if (value === undefined) + return true; + if (typeof value === 'boolean') + return value; + if (typeof value !== 'string' || value.length > 1000) + return undefined; + const input = value.trim().replace(/^\$\{\{\s*([\s\S]*?)\s*\}\}$/u, '$1'); + const tokens = input.match(/\s+|&&|\|\||==|!=|[!()]|'(?:[^']|'')*'|[A-Za-z_][A-Za-z0-9_.]*|./gu)?.filter(token => !/^\s+$/u.test(token)) ?? []; + let cursor = 0; + let depth = 0; + const truth = (item) => typeof item === 'string' ? item.length > 0 : item; + function atom() { + if (++depth > 32) + throw new Error('Condition too deep.'); + const token = tokens[cursor++]; + let result; + if (token === '(') { + result = or(); + if (tokens[cursor++] !== ')') + throw new Error('Missing bracket.'); + } + else if (token === '!') { + const inner = truth(atom()); + result = inner === undefined ? undefined : !inner; + } + else if (token === 'true' || token === 'false') + result = token === 'true'; + else if (token === 'github.event_name') + result = event; + else if (token?.startsWith("'") && token.endsWith("'")) + result = token.slice(1, -1).replace(/''/gu, "'"); + else if (token && /^[A-Za-z_][A-Za-z0-9_.]*$/u.test(token)) + result = undefined; + else + throw new Error('Unsupported condition.'); + depth--; + return result; + } + function comparison() { + const left = atom(); + if (tokens[cursor] === '==' || tokens[cursor] === '!=') { + const operator = tokens[cursor++]; + const right = atom(); + if (left === undefined || right === undefined || typeof left !== typeof right) + return undefined; + const equal = typeof left === 'string' && typeof right === 'string' ? left.toLowerCase() === right.toLowerCase() : left === right; + return operator === '==' ? equal : !equal; + } + return truth(left); + } + function and() { + let result = comparison(); + while (tokens[cursor] === '&&') { + cursor++; + const right = comparison(); + result = result === false || right === false ? false : result === undefined || right === undefined ? undefined : true; + } + return result; + } + function or() { + let result = and(); + while (tokens[cursor] === '||') { + cursor++; + const right = and(); + result = result === true || right === true ? true : result === undefined || right === undefined ? undefined : false; + } + return result; + } + try { + const result = or(); + return cursor === tokens.length ? result : undefined; + } + catch { + return undefined; + } +} + + +/***/ }), + +/***/ 87989: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.doctorWorkflowMatrix = doctorWorkflowMatrix; +/** Conservative structural evidence only; never evaluates expressions or expands matrices. */ +function doctorWorkflowMatrix(strategy) { + if (strategy === undefined) + return true; + if (!record(strategy)) + return undefined; + if (!Object.prototype.hasOwnProperty.call(strategy, 'matrix')) + return true; + const matrix = strategy.matrix; + if (!record(matrix)) + return undefined; + const axes = Object.entries(matrix); + if (!axes.length || axes.some(([key]) => key === 'include' || key === 'exclude')) + return undefined; + if (axes.some(([, values]) => Array.isArray(values) && values.length === 0)) + return false; + if (!axes.every(([, values]) => Array.isArray(values) && values.every(value => typeof value === 'string' ? !value.includes('${{') + : typeof value === 'boolean' || typeof value === 'number' && Number.isFinite(value)))) + return undefined; + let combinations = 1; + for (const [, values] of axes) { + const length = values.length; + // Bound arithmetic before multiplying; do not expand the Cartesian product. + if (combinations > 256 / length) + return false; + combinations *= length; + } + return true; +} +function record(value) { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} + + /***/ }), /***/ 20542: @@ -41859,6 +41992,292 @@ function neutralizeGithubControls(value) { } +/***/ }), + +/***/ 99566: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.validDoctorConfiguration = validDoctorConfiguration; +exports.doctorRepositoryPrerequisites = doctorRepositoryPrerequisites; +exports.doctorWorkflowContracts = doctorWorkflowContracts; +const doctor_workflow_matrix_policy_1 = __nccwpck_require__(87989); +const github_runtime_doctor_1 = __nccwpck_require__(20413); +const doctor_workflow_condition_policy_1 = __nccwpck_require__(61833); +const js_yaml_1 = __nccwpck_require__(783); +const issue_workflow_profile_1 = __nccwpck_require__(26744); +const issue_start_policy_1 = __nccwpck_require__(90332); +function validDoctorConfiguration(value) { + if (!value || typeof value.preBranchSdd !== 'boolean' || typeof value.issueManagedBranches !== 'boolean' + || !(0, github_runtime_doctor_1.isDoctorBranchReference)(value.dependencyBranch)) + return false; + try { + if ('error' in (0, issue_workflow_profile_1.parseIssueWorkflowProfile)(JSON.stringify(value.profile))) + return false; + (0, issue_start_policy_1.decideIssueStart)({ labels: [], issueManagedBranches: value.issueManagedBranches, preBranchSdd: value.preBranchSdd }); + return true; + } + catch { + return false; + } +} +function doctorRepositoryPrerequisites(read) { + if (read.state !== 'available') + return 'unavailable'; + const paths = ['specs/catalog.json', 'specs/_template.md', 'specs/README.md']; + if (!completeFileSamples(read, paths) || !Array.isArray(read.trackedFiles) || read.trackedFiles.some(path => typeof path !== 'string')) + return 'unavailable'; + if (paths.some(path => read.files[path].state !== 'present')) + return 'blocked'; + const catalogFile = read.files['specs/catalog.json']; + if (catalogFile.state !== 'present') + return 'blocked'; + try { + const catalog = object(JSON.parse(catalogFile.text)); + if (catalog.version !== 1 || !Array.isArray(catalog.capabilities) || catalog.capabilities.length === 0) + return 'blocked'; + const files = new Set(read.trackedFiles); + const ids = new Set(), titles = new Set(), specs = new Set(); + for (const raw of catalog.capabilities) { + const capability = object(raw); + if (['id', 'title', 'status', 'scope', 'owner', 'lastVerified'].some(field => typeof capability[field] !== 'string' || !capability[field].trim())) + return 'blocked'; + if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(String(capability.id)) + || ids.has(String(capability.id)) || titles.has(String(capability.title)) + || !['as-built-baseline', 'implemented', 'proposed', 'deprecated'].includes(String(capability.status)) + || !validCatalogDate(String(capability.lastVerified))) + return 'blocked'; + ids.add(String(capability.id)); + titles.add(String(capability.title)); + for (const field of ['specs', 'workflows', 'entrypoints', 'code', 'tests', 'documentation']) { + const entries = capability[field]; + if (!Array.isArray(entries) || (field !== 'workflows' && entries.length === 0) + || new Set(entries).size !== entries.length) + return 'blocked'; + for (const path of entries) { + if (!validCatalogPath(field, path) || !files.has(path)) + return 'blocked'; + if (field === 'specs') { + if (specs.has(path)) + return 'blocked'; + specs.add(path); + } + } + } + } + const standardDocuments = new Set(['specs/README.md', 'specs/_template.md', 'specs/CATALOG.md']); + if (read.trackedFiles.some(path => /^specs\/.+\.md$/u.test(path) && !standardDocuments.has(path) && !specs.has(path))) + return 'blocked'; + return paths.slice(1).every(path => { + const file = read.files[path]; + return file.state === 'present' && /^#\s+\S/mu.test(file.text); + }) ? 'pass' : 'blocked'; + } + catch { + return 'blocked'; + } +} +/** Structural evidence only. YAML is parsed with the JSON schema and never executed. */ +function doctorWorkflowContracts(read) { + if (read.state !== 'available') + return 'unavailable'; + const expected = [ + ['.github/workflows/copilot_issue.yml', 'issues', 'labeled'], + ['.github/workflows/copilot_issue_comment.yml', 'issue_comment', 'created'], + ['.github/workflows/copilot_pull_request.yml', 'pull_request', 'opened'], + ]; + if (!completeFileSamples(read, expected.map(([path]) => path))) + return 'unavailable'; + let unknown = false; + try { + for (const [path, event, activity] of expected) { + const file = read.files[path]; + if (file?.state !== 'present') + return 'blocked'; + const document = object((0, js_yaml_1.load)(file.text, { schema: js_yaml_1.JSON_SCHEMA })); + const events = document.on; + const admitted = typeof events === 'string' ? events === event : Array.isArray(events) + ? events.includes(event) : Object.prototype.hasOwnProperty.call(object(events), event); + if (!admitted) + return 'blocked'; + const filter = object(object(events)[event]); + if (Object.prototype.hasOwnProperty.call(filter, 'types') + && (!Array.isArray(filter.types) || filter.types.length === 0 + || filter.types.some(type => typeof type !== 'string' || type.length === 0) + || !filter.types.includes(activity))) + return 'blocked'; + let eligible = false; + let possible = false; + const jobs = object(document.jobs); + for (const rawJob of Object.values(jobs)) { + const job = object(rawJob); + const enabled = (0, doctor_workflow_condition_policy_1.doctorWorkflowCondition)(job.if, event); + if (enabled === false || !Array.isArray(job.steps)) + continue; + const matrixEnabled = (0, doctor_workflow_matrix_policy_1.doctorWorkflowMatrix)(job.strategy); + if (matrixEnabled === false) + continue; + const dependenciesEnabled = doctorWorkflowDependencies(job, jobs, event, enabled); + if (dependenciesEnabled === false) + continue; + for (const rawStep of job.steps) { + const step = object(rawStep); + if (!(step.uses === './' || typeof step.uses === 'string' + && /^vypdev\/copilot@[^\s${}<>]+$/iu.test(step.uses))) + continue; + const stepEnabled = (0, doctor_workflow_condition_policy_1.doctorWorkflowCondition)(step.if, event); + if (stepEnabled === false) + continue; + if (enabled === true && stepEnabled === true && matrixEnabled === true && dependenciesEnabled === true) + eligible = true; + else + possible = true; + } + } + if (!eligible && !possible) + return 'blocked'; + if (!eligible) + unknown = true; + } + return unknown ? 'unavailable' : 'pass'; + } + catch { + return 'blocked'; + } +} +function object(value) { + return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : {}; +} +function validCatalogDate(value) { + if (!/^\d{4}-\d{2}-\d{2}$/u.test(value)) + return false; + const [year, month, day] = value.split('-').map(Number), date = new Date(Date.UTC(year, month - 1, day)); + return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day; +} +function validCatalogPath(field, value) { + if (typeof value !== 'string' || value !== value.trim() || value.includes('\\') + || value.split('/').some(part => part === '' || part === '.' || part === '..')) + return false; + const boundaries = { + specs: /^specs\/(?!README\.md$|_template\.md$|CATALOG\.md$).+\.md$/u, + workflows: /^(?:\.github|setup)\/workflows\/.+\.ya?ml$/u, + entrypoints: /^(?:src\/.+|web\/src\/main\.ts|action\.yml|package\.json)$/u, + code: /^(?:(?:src|scripts)\/|web\/src\/.+\.(?:ts|svelte|css)$)/u, + tests: /^src\/.*(?:__tests__\/.*\.test\.ts|\.test\.ts)$/u, + documentation: /^(?:docs\/.*\.(?:md|mdx)|README\.md|CONTRIBUTING\.md)$/u, + }; + return boundaries[field].test(value); +} +/** Only a directly skipped prerequisite is proved; runtime success and override expressions stay unknown. */ +function doctorWorkflowDependencies(job, jobs, event, enabled) { + if (!Object.prototype.hasOwnProperty.call(job, 'needs')) + return true; + if (enabled !== true) + return undefined; + const needs = typeof job.needs === 'string' ? [job.needs] : Array.isArray(job.needs) ? job.needs : undefined; + if (!needs || needs.length === 0 || needs.length > 20 || needs.some(name => typeof name !== 'string' || !/^[A-Za-z_][A-Za-z0-9_-]{0,99}$/u.test(name))) + return undefined; + for (const name of needs) { + if (Object.prototype.hasOwnProperty.call(jobs, name) && (0, doctor_workflow_condition_policy_1.doctorWorkflowCondition)(object(jobs[name]).if, event) === false) + return false; + } + return undefined; +} +/** Explicit missing evidence differs from an incomplete or malformed provider sample. */ +function completeFileSamples(read, paths) { + return read.files !== null && typeof read.files === 'object' && !Array.isArray(read.files) + && paths.every(path => { + const sample = read.files[path]; + return sample?.state === 'missing' || sample?.state === 'present' && typeof sample.text === 'string'; + }); +} + + +/***/ }), + +/***/ 2808: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.isGithubRuntimeDoctorProjection = isGithubRuntimeDoctorProjection; +exports.formatGithubRuntimeDoctor = formatGithubRuntimeDoctor; +const github_runtime_doctor_1 = __nccwpck_require__(20413); +const publication_message_catalog_1 = __nccwpck_require__(34223); +const ACTIONS = ['configuration', 'workflow', 'retry', 'issue', 'specifications', 'branch', 'policy', 'declared']; +const STATES = ['pass', 'warning', 'blocked', 'unavailable', 'not-applicable']; +function isGithubRuntimeDoctorProjection(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) + return false; + const projection = value; + const report = projection.report; + if (['serverOrigin', 'revision', 'owner', 'repository'].some(key => { + const field = projection[key]; + return field !== undefined && (typeof field !== 'string' || field.length > 300); + })) + return false; + if (report === undefined) + return true; + if (!report || typeof report !== 'object' || !report.target || !Number.isSafeInteger(report.target.repositoryId) + || report.target.repositoryId < 1 || (report.target.kind !== 'issue' && report.target.kind !== 'pull-request') + || !Number.isSafeInteger(report.target.kind === 'issue' ? report.target.issueNumber : report.target.pullNumber) + || (report.target.kind === 'issue' ? report.target.issueNumber : report.target.pullNumber) < 1) + return false; + return STATES.includes(report.severity) && Array.isArray(report.checks) && report.checks.length === 7 + && report.checks.every((check, index) => check && check.group === github_runtime_doctor_1.GITHUB_RUNTIME_DOCTOR_GROUPS[index] && STATES.includes(check.state)) + && (report.primaryAction === undefined || ACTIONS.includes(report.primaryAction)); +} +function formatGithubRuntimeDoctor(projection, locale = 'en-US', catalog = (0, publication_message_catalog_1.resolveStaticPublicationCatalog)(locale).catalog) { + if (!isGithubRuntimeDoctorProjection(projection)) + projection = {}; + const message = (suffix) => catalog.render(`interaction.doctor.${suffix}`); + const severity = projection.report?.severity ?? 'unavailable'; + const action = projection.report?.primaryAction; + const lines = [`## 🩺 ${message('heading')}: ${message(`state.${severity}`)}`, '', + message(`summary.${severity}`), '', + `**${message('action')}:** ${action && ACTIONS.includes(action) ? message(`action.${action}`) + : severity === 'pass' || severity === 'not-applicable' ? message('action.none') : message('action.retry')}`, + '', message('readonly'), '', `### ${message('checks')}`, '']; + const source = repositorySource(projection); + const origin = source?.split('/').slice(0, 5).join('/'); + for (const group of github_runtime_doctor_1.GITHUB_RUNTIME_DOCTOR_GROUPS) { + const state = projection.report?.checks.find(check => check.group === group)?.state ?? 'unavailable'; + const evidence = projection.report?.checks.find(check => check.group === group)?.evidence?.source; + const target = projection.report?.target; + const link = origin && target && group === 'pull-request-identity' && target.kind === 'pull-request' + ? `${origin}/pull/${target.pullNumber}` : origin && target && target.kind === 'issue' + && (group === 'issue-readiness' || group === 'linked-branch-readiness') ? `${origin}/issues/${target.issueNumber}` : undefined; + const policySource = group === 'approval-policy' && (evidence === 'repository' || evidence === 'organization' || evidence === 'default-off') + ? message(`source.${evidence}`) : undefined; + const mode = group === 'approval-prerequisites' && (evidence === 'off' || evidence === 'recommend' || evidence === 'guarded') + ? `${message('mode')}: ${evidence}` : undefined; + lines.push(`- **${message(`group.${group}`)}:** ${message(`state.${state}`)}${policySource ? ` · ${policySource}` : ''}${mode ? ` · ${mode}` : ''}${link ? ` · [${message('source')}](${link})` : ''}`); + } + if (source) + lines.push('', `[${message('source')}](${source})`); + return lines.join('\n'); +} +function repositorySource(value) { + if (!value.serverOrigin || !value.revision || !/^[a-f0-9]{40}$/u.test(value.revision) + || !value.owner || !value.repository || !/^[A-Za-z0-9][A-Za-z0-9-]{0,38}$/u.test(value.owner) + || !/^[A-Za-z0-9][A-Za-z0-9_.-]{0,99}$/u.test(value.repository)) + return undefined; + try { + const url = new URL(value.serverOrigin); + if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash + || url.pathname !== '/' || url.origin !== value.serverOrigin.replace(/\/$/u, '')) + return undefined; + return `${url.origin}/${value.owner}/${value.repository}/tree/${value.revision}`; + } + catch { + return undefined; + } +} + + /***/ }), /***/ 30203: @@ -42666,6 +43085,7 @@ const PUBLICATION_SURFACE_MESSAGE_IDS = Object.freeze([ 'interaction.help.command.recheck', 'interaction.help.command.description', 'interaction.help.command.status', + 'interaction.help.command.doctor', 'interaction.help.command.history', 'interaction.history.fixPushed', 'interaction.history.fixNoChanges', @@ -42716,6 +43136,41 @@ const PUBLICATION_SURFACE_MESSAGE_IDS = Object.freeze([ 'interaction.branchSync.merged', 'interaction.branchSync.agentResolution', 'interaction.branchSync.verification', + 'interaction.doctor.source.repository', + 'interaction.doctor.source.organization', + 'interaction.doctor.source.default-off', + 'interaction.doctor.mode', + 'interaction.doctor.heading', + 'interaction.doctor.action', + 'interaction.doctor.checks', + 'interaction.doctor.readonly', + 'interaction.doctor.source', + 'interaction.doctor.group.effective-configuration', + 'interaction.doctor.group.issue-readiness', + 'interaction.doctor.group.linked-branch-readiness', + 'interaction.doctor.group.workflow-contracts', + 'interaction.doctor.group.approval-policy', + 'interaction.doctor.group.approval-prerequisites', + 'interaction.doctor.group.pull-request-identity', + 'interaction.doctor.action.configuration', + 'interaction.doctor.action.workflow', + 'interaction.doctor.action.retry', + 'interaction.doctor.action.issue', + 'interaction.doctor.action.specifications', + 'interaction.doctor.action.branch', + 'interaction.doctor.action.policy', + 'interaction.doctor.action.declared', + 'interaction.doctor.action.none', + 'interaction.doctor.state.pass', + 'interaction.doctor.summary.pass', + 'interaction.doctor.state.blocked', + 'interaction.doctor.summary.blocked', + 'interaction.doctor.state.unavailable', + 'interaction.doctor.summary.unavailable', + 'interaction.doctor.state.warning', + 'interaction.doctor.summary.warning', + 'interaction.doctor.state.not-applicable', + 'interaction.doctor.summary.not-applicable', 'interaction.status.heading', 'interaction.status.repository', 'interaction.status.target', @@ -42809,6 +43264,7 @@ const ENGLISH_MESSAGES = Object.freeze({ 'interaction.help.command.recheck': 're-run the review and reconcile findings.', 'interaction.help.command.description': 'refresh the pull-request description.', 'interaction.help.command.status': 'show the current automation status.', + 'interaction.help.command.doctor': 'check available runtime prerequisites without making repository changes.', 'interaction.help.command.history': 'show up to ten supported operations and their verified receipts.', 'interaction.history.fixPushed': 'Fix changes were pushed. Findings require an independent review of the pushed revision.', 'interaction.history.fixNoChanges': 'The fix produced no changes to push.', @@ -42871,6 +43327,41 @@ const ENGLISH_MESSAGES = Object.freeze({ one: '{count} verification check passed.', other: '{count} verification checks passed.', }), + 'interaction.doctor.source.repository': "Repository variable", + 'interaction.doctor.source.organization': "Organization variable", + 'interaction.doctor.source.default-off': "Verified absence; default off", + 'interaction.doctor.mode': "Mode", + 'interaction.doctor.heading': "Runtime doctor", + 'interaction.doctor.action': "Next action", + 'interaction.doctor.checks': "Checks", + 'interaction.doctor.readonly': "This command reads evidence only. Declared approval settings do not prove producer health or write access.", + 'interaction.doctor.source': "Inspected repository revision", + 'interaction.doctor.group.effective-configuration': "Effective configuration", + 'interaction.doctor.group.issue-readiness': "Issue and SDD prerequisites", + 'interaction.doctor.group.linked-branch-readiness': "Native linked branch", + 'interaction.doctor.group.workflow-contracts': "Installed workflow structure", + 'interaction.doctor.group.approval-policy': "Trusted approval policy read", + 'interaction.doctor.group.approval-prerequisites': "Declared approval prerequisites", + 'interaction.doctor.group.pull-request-identity': "Exact current PR identity", + 'interaction.doctor.action.configuration': "Inspect the effective workflow configuration and correct the invalid setting.", + 'interaction.doctor.action.workflow': "Inspect the linked workflow revision and correct its required structure.", + 'interaction.doctor.action.retry': "Inspect this workflow run and retry /copilot doctor.", + 'interaction.doctor.action.issue': "Inspect the issue form and its routing labels.", + 'interaction.doctor.action.specifications': "Inspect the catalog, template and specification standard at the linked revision.", + 'interaction.doctor.action.branch': "Inspect the issue’s native linked branch and retry after branch readiness.", + 'interaction.doctor.action.policy': "Inspect the trusted Actions Variables approval policy source.", + 'interaction.doctor.action.declared': "Verify the declared producer prerequisites using the approval setup documentation.", + 'interaction.doctor.action.none': "No action required for the inspected prerequisites.", + 'interaction.doctor.state.pass': "Pass", + 'interaction.doctor.summary.pass': "The inspected applicable prerequisites passed.", + 'interaction.doctor.state.blocked': "Blocked", + 'interaction.doctor.summary.blocked': "An authoritative read established an invalid required prerequisite.", + 'interaction.doctor.state.unavailable': "Unavailable", + 'interaction.doctor.summary.unavailable': "Some prerequisites could not be verified; completed checks remain visible.", + 'interaction.doctor.state.warning': "Warning", + 'interaction.doctor.summary.warning': "A prerequisite needs attention or independent verification.", + 'interaction.doctor.state.not-applicable': "Not applicable", + 'interaction.doctor.summary.not-applicable': "The inspected prerequisites do not apply to this target.", 'interaction.status.heading': 'Copilot status', 'interaction.status.repository': 'Repository', 'interaction.status.target': 'Target', @@ -42961,6 +43452,7 @@ const SPANISH_MESSAGES = Object.freeze({ 'interaction.help.command.recheck': 'repite la revisión y reconcilia los hallazgos.', 'interaction.help.command.description': 'actualiza la descripción del pull request.', 'interaction.help.command.status': 'muestra el estado actual de la automatización.', + 'interaction.help.command.doctor': 'comprueba los requisitos disponibles del runtime sin modificar el repositorio.', 'interaction.help.command.history': 'muestra hasta diez operaciones compatibles y sus recibos verificables.', 'interaction.history.fixPushed': 'Se publicaron los cambios de la corrección. Los hallazgos requieren una revisión independiente de esa revisión.', 'interaction.history.fixNoChanges': 'La corrección no produjo cambios que publicar.', @@ -43027,6 +43519,41 @@ const SPANISH_MESSAGES = Object.freeze({ many: 'Se superaron {count} comprobaciones de verificación.', other: 'Se superaron {count} comprobaciones de verificación.', }), + 'interaction.doctor.source.repository': "Variable del repositorio", + 'interaction.doctor.source.organization': "Variable de la organización", + 'interaction.doctor.source.default-off': "Ausencia verificada; off por defecto", + 'interaction.doctor.mode': "Modo", + 'interaction.doctor.heading': "Diagnóstico del runtime", + 'interaction.doctor.action': "Siguiente acción", + 'interaction.doctor.checks': "Comprobaciones", + 'interaction.doctor.readonly': "Este comando solo lee evidencias. La configuración de aprobación declarada no acredita la salud de los productores ni el acceso de escritura.", + 'interaction.doctor.source': "Revisión inspeccionada del repositorio", + 'interaction.doctor.group.effective-configuration': "Configuración efectiva", + 'interaction.doctor.group.issue-readiness': "Requisitos de la issue y el SDD", + 'interaction.doctor.group.linked-branch-readiness': "Rama enlazada nativa", + 'interaction.doctor.group.workflow-contracts': "Estructura de los workflows instalados", + 'interaction.doctor.group.approval-policy': "Lectura confiable de la política de aprobación", + 'interaction.doctor.group.approval-prerequisites': "Requisitos de aprobación declarados", + 'interaction.doctor.group.pull-request-identity': "Identidad actual exacta del PR", + 'interaction.doctor.action.configuration': "Revisa la configuración efectiva del workflow y corrige el ajuste inválido.", + 'interaction.doctor.action.workflow': "Revisa la revisión enlazada de los workflows y corrige la estructura requerida.", + 'interaction.doctor.action.retry': "Revisa esta ejecución del workflow y vuelve a ejecutar /copilot doctor.", + 'interaction.doctor.action.issue': "Revisa el formulario de la issue y sus etiquetas de clasificación.", + 'interaction.doctor.action.specifications': "Revisa el catálogo, la plantilla y el estándar de especificaciones en la revisión enlazada.", + 'interaction.doctor.action.branch': "Revisa la rama enlazada nativa de la issue y reintenta cuando esté preparada.", + 'interaction.doctor.action.policy': "Revisa la fuente confiable de la política de aprobación en Actions Variables.", + 'interaction.doctor.action.declared': "Verifica los requisitos de los productores declarados siguiendo la documentación de aprobación.", + 'interaction.doctor.action.none': "No se requiere ninguna acción para los requisitos inspeccionados.", + 'interaction.doctor.state.pass': "Correcto", + 'interaction.doctor.summary.pass': "Los requisitos aplicables inspeccionados son correctos.", + 'interaction.doctor.state.blocked': "Bloqueado", + 'interaction.doctor.summary.blocked': "Una lectura autoritativa confirmó un requisito obligatorio inválido.", + 'interaction.doctor.state.unavailable': "No disponible", + 'interaction.doctor.summary.unavailable': "Algunos requisitos no se pudieron verificar; las comprobaciones completadas siguen visibles.", + 'interaction.doctor.state.warning': "Advertencia", + 'interaction.doctor.summary.warning': "Un requisito necesita atención o verificación independiente.", + 'interaction.doctor.state.not-applicable': "No aplicable", + 'interaction.doctor.summary.not-applicable': "Los requisitos inspeccionados no se aplican a este destino.", 'interaction.status.heading': 'Estado de Copilot', 'interaction.status.repository': 'Repositorio', 'interaction.status.target': 'Destino', @@ -43313,6 +43840,125 @@ function areOrderedUniquePositiveIntegers(values) { } +/***/ }), + +/***/ 79017: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.renderApprovalAssessment = renderApprovalAssessment; +exports.renderApprovalRunSummary = renderApprovalRunSummary; +/** One bounded, text-first card. No PR prose or provider response is rendered. */ +function renderApprovalAssessment(input) { + const spanish = input.locale.toLowerCase().startsWith('es'); + const { evidence, decision, target } = input; + const url = `https://github.com/${encodeURIComponent(target.owner)}/${encodeURIComponent(target.repository)}/pull/${target.pullNumber}`; + const approved = decision.code === 'approved' || decision.status === 'already-approved'; + const partial = decision.code === 'publication-unknown'; + const status = approved ? (spanish ? 'Aprobada por el bot' : 'Approved by the bot') + : partial ? (spanish ? 'Resultado de publicación incierto' : 'Publication outcome uncertain') + : decision.status === 'pending' ? (spanish ? 'Esperando evidencias' : 'Waiting for evidence') + : decision.status === 'recommend' ? (spanish ? 'Revisión humana necesaria' : 'Human review needed') + : (spanish ? 'Aprobación bloqueada' : 'Approval blocked'); + const action = approved ? (spanish ? 'Ninguna; GitHub seguirá aplicando sus reglas.' : 'None; GitHub still applies its review rules.') + : partial ? (spanish ? 'Reejecuta el observador; comprobará la revisión antes de intentar publicarla otra vez.' + : 'Rerun the observer; it reads the review before any retry.') + : decision.status === 'pending' ? (spanish ? 'Espera al productor pendiente o reejecuta el observador.' + : 'Wait for the pending producer or rerun the observer.') + : decision.status === 'recommend' ? (spanish ? 'Pide una decisión a una persona revisora.' + : 'Ask a human reviewer to decide.') + : (spanish ? 'Corrige el requisito indicado y reejecuta el observador.' + : 'Fix the named prerequisite and rerun the observer.'); + const completed = evidence.checks.filter(check => check.status === 'completed' && check.conclusion === 'success').length; + const selected = evidence.checks.length; + const bugbot = evidence.bugbot?.headSha === evidence.headSha && evidence.bugbot.outcome === 'complete' + && evidence.bugbot.coverage === 'complete'; + const detail = spanish ? spanishDecisionReason(decision.code) : escapeMarkdown(decision.detail); + const lines = [ + '', + `### ${spanish ? 'Aprobación de Copilot' : 'Copilot approval'} · ${status}`, + `**${spanish ? 'Estado' : 'Status'}:** ${detail}`, + `**${spanish ? 'Completado' : 'Completed'}:** ${completed}/${selected} ${spanish ? 'checks observados correctos' : 'observed checks succeeded'}; Bugbot ${bugbot ? (spanish ? 'completo' : 'complete') : (spanish ? 'no verificado' : 'unverified')}.`, + `**${spanish ? 'Acción necesaria' : 'Action required'}:** ${action}`, + `**${spanish ? 'Impacto' : 'Impact'}:** ${approved + ? (spanish ? 'Hay una revisión nativa para esta revisión; no se ha fusionado la PR.' : 'A native review exists for this revision; the PR was not merged.') + : (spanish ? 'No se ha confirmado ninguna aprobación nueva para esta revisión.' : 'No new approval is confirmed for this revision.')}`, + input.reviewId ? `[${spanish ? 'Abrir aprobación' : 'Open approval'}](${url}#pullrequestreview-${input.reviewId}) · [${spanish ? 'Ver checks' : 'See checks'}](${url}/checks)` + : `[${spanish ? 'Ver checks' : 'See checks'}](${url}/checks)`, + '
', + `${spanish ? 'Evidencia técnica' : 'Technical evidence'}`, + '', + `${spanish ? 'Revisión' : 'Revision'}: \`${evidence.headSha.slice(0, 12)}\`; base: \`${evidence.baseSha.slice(0, 12)}\`; ${spanish ? 'motivo' : 'reason'}: ${escapeMarkdown(decision.code)}.`, + '', + '
', + ]; + return lines.join('\n').slice(0, 1800); +} +const SPANISH_DECISION_REASONS = Object.freeze({ + disabled: 'La aprobación automática está desactivada.', + 'unsupported-pr': 'Solo se evalúan PRs abiertas, no borrador, completas y del mismo repositorio.', + scope: 'La PR queda fuera del ámbito de aprobación configurado.', + 'bot-author': 'Una persona debe revisar las PRs abiertas por bots.', + 'protected-path': 'Ha cambiado un archivo protegido de workflow, política o seguridad.', + 'bugbot-ignored-path': 'Bugbot omitió un archivo modificado; su resultado no permite aprobar.', + 'unsafe-rules': 'Las reglas de rama deben ser legibles y descartar aprobaciones antiguas.', + 'producer-unattested': 'Falta la confirmación del productor de CI y del paso obligatorio de cobertura.', + 'reporter-unattested': 'No se ha confirmado la instalación del reporter numérico en CI de confianza.', + 'merge-ref-unavailable': 'Se espera una fusión de prueba verificada para la base y revisión actuales.', + 'approval-check-cycle': 'El check Copilot / Approval no puede exigirse a sí mismo.', + 'bugbot-configuration': 'Bugbot debe usar severidad info y tener desactivado dry-run.', + 'check-producer-unconfigured': 'Falta configurar un productor de confianza para un check obligatorio.', + 'check-missing': 'Se espera un check de la revisión actual.', + 'check-source': 'El origen de un check difiere del productor configurado.', + 'check-ambiguous': 'Hay intentos recientes ambiguos para un check.', + 'check-pending': 'Se espera a que termine un check.', + 'check-failed': 'Un check obligatorio no terminó correctamente.', + 'bugbot-missing': 'Se espera una revisión completa de Bugbot para esta revisión.', + 'bugbot-incomplete': 'La evidencia de Bugbot es parcial, fallida u obsoleta.', + 'bugbot-findings': 'Bugbot tiene hallazgos abiertos, desconocidos o descartados que bloquean.', + 'coverage-evidence': 'Falta evidencia numérica válida para la revisión actual.', + 'coverage-low': 'La cobertura del diff no alcanza el umbral configurado.', + 'reviews-unavailable': 'No se pudo leer todo el historial de revisiones.', + 'existing-approval': 'El bot ya aprobó esta revisión exacta.', + 'approval-dismissed': 'Una persona responsable descartó la aprobación del bot para esta revisión.', + 'changes-requested': 'Una persona revisora ha solicitado cambios.', + 'human-approved': 'Una persona ya aprobó esta revisión.', + 'recommend-mode': 'Las evidencias cumplen; una persona debe decidir.', + eligible: 'Las evidencias y reglas permiten una aprobación nativa.', + approved: 'El bot aprobó esta revisión; GitHub sigue aplicando las reglas de rama.', + 'publication-unknown': 'GitHub podría haber aceptado la revisión; reejecuta para comprobarla.', + 'invalid-policy': 'La política instalada no es válida; ejecuta copilot doctor.', + 'evidence-unavailable': 'No se pudo leer la evidencia de aprobación; reejecuta el observador.', + 'target-mismatch': 'La PR resuelta no coincide con el repositorio y número esperados.', + 'pre-submit-unavailable': 'No se pudo volver a leer la evidencia antes de publicar la aprobación.', + superseded: 'La política o revisión cambió; vuelve a comprobar la revisión actual.', +}); +function spanishDecisionReason(code) { + return SPANISH_DECISION_REASONS[code] ?? 'No se pudo completar la evaluación de aprobación.'; +} +function renderApprovalRunSummary(input) { + const spanish = input.locale.toLowerCase().startsWith('es'); + const checks = input.evidence.checks.slice(0, 8).map(check => `- \`${escapeMarkdown(check.name)}\` · App ${check.sourceAppId} · \`${escapeMarkdown(check.workflowName)}\` · ${escapeMarkdown(check.status)}/${escapeMarkdown(check.conclusion ?? 'unknown')} · run ${check.runId}/${check.attempt}`); + return [ + `### ${spanish ? 'Observación de aprobación' : 'Approval observation'} · PR #${input.target.pullNumber}`, + `- ${spanish ? 'Inicio' : 'Wakeup'}: ${escapeMarkdown(input.wakeup)}`, + `- ${spanish ? 'Modo' : 'Mode'}: \`${escapeMarkdown(input.mode)}\`; ${spanish ? 'resultado' : 'result'}: \`${escapeMarkdown(input.decision.status)}\` (\`${escapeMarkdown(input.decision.code)}\`)`, + `- ${spanish ? 'Revisión/base' : 'Head/base'}: \`${input.evidence.headSha}\` / \`${input.evidence.baseSha}\``, + `- Bugbot: ${escapeMarkdown(input.evidence.bugbot?.outcome ?? 'missing')}/${escapeMarkdown(input.evidence.bugbot?.coverage ?? 'missing')}`, + `- ${spanish ? 'Revisión nativa' : 'Native review'}: ${input.reviewId ?? (spanish ? 'ninguna confirmada' : 'none confirmed')}`, + `- ${spanish ? 'Tarjeta/Check' : 'Card/Check'}: ${escapeMarkdown(input.publication)}`, + ...(checks.length > 0 ? [spanish ? 'Productores observados:' : 'Observed producers:', ...checks] : []), + `- ${spanish ? 'Recuperación' : 'Recovery'}: ${spanish ? 'corrige el bloqueo y reejecuta Copilot - Pull Request Approval con el número de PR' : 'fix the blocker and rerun Copilot - Pull Request Approval with the PR number'}.`, + ].join('\n'); +} +function escapeMarkdown(value) { + return [...value].map(character => character === '@' ? '@\u200b' + : '\\`*_{}[]()#+.!|>~-<>'.includes(character) ? `\\${character}` : character).join(''); +} + + /***/ }), /***/ 43268: @@ -44055,6 +44701,7 @@ exports.hasOwnedPrimaryIssuePublication = hasOwnedPrimaryIssuePublication; exports.selectSemanticReplyIntents = selectSemanticReplyIntents; exports.renderSemanticReply = renderSemanticReply; exports.renderSemanticStatus = renderSemanticStatus; +const github_runtime_doctor_presentation_policy_1 = __nccwpck_require__(2808); const copilot_operation_1 = __nccwpck_require__(40221); const copilot_history_presentation_policy_1 = __nccwpck_require__(55507); const copilot_history_projection_policy_1 = __nccwpck_require__(99186); @@ -44166,6 +44813,11 @@ function selectSemanticReplyIntents(context) { if (kind === 'access-policy') { return [replyIntent(context, correlationId, 'access-policy', Object.freeze({ kind }))]; } + if (result.id.endsWith('.Doctor') && (0, github_runtime_doctor_presentation_policy_1.isGithubRuntimeDoctorProjection)(payload.doctor)) { + return [replyIntent(context, correlationId, 'copilot-doctor', Object.freeze({ + kind: 'doctor-command', snapshot: payload.doctor, + }))]; + } const snapshot = (0, result_1.getResultPayload)(payload.status); if (result.id.endsWith('.Status') && isStatusSnapshot(snapshot)) { const projection = Object.freeze({ @@ -44251,6 +44903,8 @@ function renderReplyBody(intent, catalog) { if (intent.projection.kind === 'branch-sync-result') { return renderBranchSyncResult(intent.projection, catalog); } + if (intent.projection.kind === 'doctor-command') + return (0, github_runtime_doctor_presentation_policy_1.formatGithubRuntimeDoctor)(intent.projection.snapshot, intent.locale, catalog); return (0, status_command_policy_1.formatCopilotStatus)(intent.projection.snapshot, intent.locale, catalog); } function renderBranchSyncResult(projection, catalog) { @@ -49264,6 +49918,21 @@ exports.AGENT_EXECUTION_OUTPUT_DIAGNOSTICS = [ ]; +/***/ }), + +/***/ 64921: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.DOCTOR_REPOSITORY_PATHS = void 0; +exports.DOCTOR_REPOSITORY_PATHS = [ + '.github/workflows/copilot_issue.yml', '.github/workflows/copilot_issue_comment.yml', + '.github/workflows/copilot_pull_request.yml', 'specs/catalog.json', 'specs/_template.md', 'specs/README.md', +]; + + /***/ }), /***/ 6152: @@ -53090,6 +53759,7 @@ async function runDoUserRequestAction(param, options, intentPayload) { Object.defineProperty(exports, "__esModule", ({ value: true })); exports.runExplicitCommentCommand = runExplicitCommentCommand; exports.invalidCommentCommandResult = invalidCommentCommandResult; +const read_copilot_doctor_workflow_1 = __nccwpck_require__(12416); const read_copilot_history_workflow_1 = __nccwpck_require__(8140); const result_1 = __nccwpck_require__(73817); const read_copilot_status_workflow_1 = __nccwpck_require__(57206); @@ -53112,6 +53782,8 @@ async function runExplicitCommentCommand(param, options, command, actorAuthoriza })]; if (command.name === 'help') return runHelpCommand(param, options); + if (command.name === 'doctor') + return [await (0, read_copilot_doctor_workflow_1.readCopilotDoctor)(param, options.taskId, options.doctorReadPorts)]; if (command.name === 'status') return [await (0, read_copilot_status_workflow_1.readCopilotStatus)(param, options.taskId, options.statusReadPorts, options.sddQueryPort, options.nativeStatusReadPort)]; if (command.name === 'dismiss') @@ -53340,6 +54012,22 @@ const copilot_command_1 = __nccwpck_require__(11771); function projectCommentAutomationContext(source, language, userComment) { const review = (0, bugbot_review_operation_context_1.projectBugbotReviewOperationContext)(source); return Object.freeze({ + ...(source.issueWorkflowProfile && typeof source.preBranchSdd === 'boolean' + && typeof source.issue.issueManagedBranches === 'boolean' ? { doctorConfiguration: Object.freeze({ + profile: Object.freeze({ schemaVersion: source.issueWorkflowProfile.schemaVersion, + enabled: Object.freeze([...source.issueWorkflowProfile.enabled]) }), + labels: Object.freeze({ + feature: Object.freeze([source.labels?.feature, source.labels?.enhancement].filter((value) => typeof value === 'string' && value.length > 0)), + bugfix: Object.freeze([source.labels?.bugfix, source.labels?.bug].filter((value) => typeof value === 'string' && value.length > 0)), + documentation: Object.freeze([source.labels?.documentation, source.labels?.docs].filter((value) => typeof value === 'string' && value.length > 0)), + chore: Object.freeze([source.labels?.chore, source.labels?.maintenance].filter((value) => typeof value === 'string' && value.length > 0)), + help: Object.freeze([source.labels?.help, source.labels?.question].filter((value) => typeof value === 'string' && value.length > 0)), + release: Object.freeze([source.labels?.release].filter((value) => typeof value === 'string' && value.length > 0)), + hotfix: Object.freeze([source.labels?.hotfix].filter((value) => typeof value === 'string' && value.length > 0)), + }), + preBranchSdd: source.preBranchSdd, issueManagedBranches: source.issue.issueManagedBranches, + dependencyBranch: source.currentConfiguration.parentBranch ?? source.branches.development ?? '', + }) } : {}), actor: source.actor, trustedBotLogin: source.tokenUser?.trim() ?? '', userComment, @@ -53536,7 +54224,7 @@ async function runCommentAutomation(initialParam, options, actorAuthorizationPor return [(0, comment_automation_command_workflow_1.invalidCommentCommandResult)(options.taskId, command.reason)]; } const isPublicMetadataCommand = command.kind === 'command' - && (command.command.name === 'help' || command.command.name === 'status' || command.command.name === 'history'); + && (command.command.name === 'help' || command.command.name === 'status' || command.command.name === 'doctor' || command.command.name === 'history'); if (!isPublicMetadataCommand && param.membersOnly && !await actorAuthorizationPort.isActorAllowedToUseMemberOnlyAutomation(param.actor)) { (0, logging_ports_1.logInfo)('Skipping agent automation because ai-members-only is enabled and the actor is not authorized.'); @@ -54167,7 +54855,6 @@ function positiveIssueNumberOrUndefined(value) { Object.defineProperty(exports, "__esModule", ({ value: true })); exports.IssueCommentUseCase = void 0; const comment_automation_command_workflow_1 = __nccwpck_require__(63134); -const copilot_command_1 = __nccwpck_require__(11771); const read_copilot_history_workflow_1 = __nccwpck_require__(8140); const result_1 = __nccwpck_require__(73817); const comment_automation_use_case_1 = __nccwpck_require__(9661); @@ -54175,8 +54862,9 @@ const check_issue_comment_language_use_case_1 = __nccwpck_require__(93152); const comment_automation_context_1 = __nccwpck_require__(37055); const pull_request_workflow_context_1 = __nccwpck_require__(73447); const pre_branch_sdd_1 = __nccwpck_require__(34730); +const copilot_command_1 = __nccwpck_require__(11771); class IssueCommentUseCase { - constructor(languageUseCase, intentUseCase, thinkUseCase, autofixUseCase, doUserRequestUseCase, actorAuthorizationPort, bugbotGitMutationPort, dismissBugbotFindingsUseCase, reviewPotentialProblemsUseCase, updatePullRequestDescriptionUseCase, rememberBugbotRuleUseCase, syncBranchUseCase, preBranchSddContinuation, rememberBranchPorts, statusReadPorts, historyReadPort, sddQueryPort, nativeStatusReadPort) { + constructor(languageUseCase, intentUseCase, thinkUseCase, autofixUseCase, doUserRequestUseCase, actorAuthorizationPort, bugbotGitMutationPort, dismissBugbotFindingsUseCase, reviewPotentialProblemsUseCase, updatePullRequestDescriptionUseCase, rememberBugbotRuleUseCase, syncBranchUseCase, preBranchSddContinuation, rememberBranchPorts, statusReadPorts, historyReadPort, sddQueryPort, nativeStatusReadPort, doctorReadPorts) { this.languageUseCase = languageUseCase; this.intentUseCase = intentUseCase; this.thinkUseCase = thinkUseCase; @@ -54195,6 +54883,7 @@ class IssueCommentUseCase { this.historyReadPort = historyReadPort; this.sddQueryPort = sddQueryPort; this.nativeStatusReadPort = nativeStatusReadPort; + this.doctorReadPorts = doctorReadPorts; this.taskId = "IssueCommentUseCase"; } async invoke(param) { @@ -54208,13 +54897,14 @@ class IssueCommentUseCase { generalConversation: param.eventName === 'issue_comment', }, this.historyReadPort)]; } - if (param.preBranchSdd && !param.issue.issueManagedBranches) { + const isDoctor = historyCommand.kind === 'command' && historyCommand.command.name === 'doctor'; + if (!isDoctor && param.preBranchSdd && !param.issue.issueManagedBranches) { return [new result_1.Result({ id: this.taskId, success: false, executed: true, steps: ['pre-branch-sdd requires issue-managed-branches; correct the Action configuration.'], })]; } - if (this.preBranchSddContinuation + if (!isDoctor && this.preBranchSddContinuation && param.issueStartDecision.sddRequired && (0, pre_branch_sdd_1.isSddAnswerComment)(param.issue.commentBody) && param.issue.commentAuthor.toLowerCase() !== param.tokenUser?.toLowerCase()) { @@ -54242,6 +54932,7 @@ class IssueCommentUseCase { rememberBugbotRuleUseCase: this.rememberBugbotRuleUseCase, rememberBranchPorts: this.rememberBranchPorts, statusReadPorts: this.statusReadPorts, + doctorReadPorts: this.doctorReadPorts, nativeStatusReadPort: this.nativeStatusReadPort, sddQueryPort: this.sddQueryPort, syncBranchUseCase: this.syncBranchUseCase @@ -54909,7 +55600,7 @@ const check_pull_request_comment_language_use_case_1 = __nccwpck_require__(21729 const comment_automation_context_1 = __nccwpck_require__(37055); const pull_request_workflow_context_1 = __nccwpck_require__(73447); class PullRequestReviewCommentUseCase { - constructor(languageUseCase, intentUseCase, thinkUseCase, autofixUseCase, doUserRequestUseCase, actorAuthorizationPort, bugbotGitMutationPort, dismissBugbotFindingsUseCase, reviewPotentialProblemsUseCase, updatePullRequestDescriptionUseCase, rememberBugbotRuleUseCase, syncBranchUseCase, rememberBranchPorts, statusReadPorts, historyReadPort, nativeStatusReadPort) { + constructor(languageUseCase, intentUseCase, thinkUseCase, autofixUseCase, doUserRequestUseCase, actorAuthorizationPort, bugbotGitMutationPort, dismissBugbotFindingsUseCase, reviewPotentialProblemsUseCase, updatePullRequestDescriptionUseCase, rememberBugbotRuleUseCase, syncBranchUseCase, rememberBranchPorts, statusReadPorts, historyReadPort, nativeStatusReadPort, doctorReadPorts) { this.languageUseCase = languageUseCase; this.intentUseCase = intentUseCase; this.thinkUseCase = thinkUseCase; @@ -54926,6 +55617,7 @@ class PullRequestReviewCommentUseCase { this.statusReadPorts = statusReadPorts; this.historyReadPort = historyReadPort; this.nativeStatusReadPort = nativeStatusReadPort; + this.doctorReadPorts = doctorReadPorts; this.taskId = "PullRequestReviewCommentUseCase"; } async invoke(param) { @@ -54961,6 +55653,7 @@ class PullRequestReviewCommentUseCase { rememberBugbotRuleUseCase: this.rememberBugbotRuleUseCase, rememberBranchPorts: this.rememberBranchPorts, statusReadPorts: this.statusReadPorts, + doctorReadPorts: this.doctorReadPorts, nativeStatusReadPort: this.nativeStatusReadPort, syncBranchUseCase: this.syncBranchUseCase ? { invoke: (options) => this.syncBranchUseCase.invoke({ context: context.branchSync, options }) } @@ -55448,6 +56141,124 @@ function deepFreezeCopy(value) { } +/***/ }), + +/***/ 12416: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.readCopilotDoctor = readCopilotDoctor; +const result_1 = __nccwpck_require__(73817); +const github_runtime_doctor_1 = __nccwpck_require__(20413); +const issue_workflow_profile_1 = __nccwpck_require__(26744); +const issue_start_policy_1 = __nccwpck_require__(90332); +const positive_integer_policy_1 = __nccwpck_require__(19879); +const context_1 = __nccwpck_require__(14712); +const github_runtime_doctor_policy_1 = __nccwpck_require__(99566); +const github_runtime_doctor_presentation_policy_1 = __nccwpck_require__(2808); +const bugbot_context_request_1 = __nccwpck_require__(98299); +async function readCopilotDoctor(param, taskId, ports) { + const repositoryId = (0, positive_integer_policy_1.parsePositiveSafeInteger)(param.bugbot.review.repository.id); + const isPull = param.status.target === 'pull-request'; + const number = (0, positive_integer_policy_1.parsePositiveSafeInteger)(isPull ? param.status.pullRequestNumber : param.status.issueNumber); + if (!repositoryId || !number) + return doctorResult(taskId, undefined); + const target = isPull ? { kind: 'pull-request', repositoryId, pullNumber: number } + : { kind: 'issue', repositoryId, issueNumber: number }; + const checks = []; + const put = (group, state, action) => checks.push(Object.freeze({ group, state, evidence: { source: group }, ...(action && state !== 'pass' && state !== 'not-applicable' ? { action: state === 'unavailable' ? 'retry' : action } : {}) })); + const configuration = param.doctorConfiguration; + const valid = (0, github_runtime_doctor_policy_1.validDoctorConfiguration)(configuration); + put('effective-configuration', configuration === undefined ? 'unavailable' : valid ? 'pass' : 'blocked', !valid ? 'configuration' : undefined); + const repositoryRead = ports ? await ports.readRepository(repositoryId).catch(() => ({ state: 'unavailable' })) + : { state: 'unavailable' }; + const verifiedRead = repositoryRead.state === 'available' + && (repositoryRead.repositoryId !== repositoryId || !/^[a-f0-9]{40}$/u.test(repositoryRead.revision)) + ? { state: 'unavailable' } : repositoryRead; + put('workflow-contracts', (0, github_runtime_doctor_policy_1.doctorWorkflowContracts)(verifiedRead), 'workflow'); + if (isPull) { + // Optional issue context is not guessed from a PR number or event prose. + put('issue-readiness', 'not-applicable'); + put('linked-branch-readiness', 'not-applicable'); + } + else if (!valid || !ports) { + put('issue-readiness', 'unavailable', 'retry'); + put('linked-branch-readiness', 'unavailable', 'retry'); + } + else { + try { + const issue = await ports.readIssue(number); + if (issue.number !== number) + throw new Error('Wrong issue identity.'); + const kindAdmission = (0, issue_workflow_profile_1.classifyIssueWorkflow)(issue.labels, configuration.profile, configuration.labels, undefined, false); + // Form completeness cannot make branchless help work require an SDD. + // Disabled or conflicting kinds still retain their admission block. + const admission = kindAdmission.status === 'eligible' && kindAdmission.kind === 'help' ? kindAdmission + : (0, issue_workflow_profile_1.classifyIssueWorkflow)(issue.labels, configuration.profile, configuration.labels, issue.body); + if (admission.status !== 'eligible') { + put('issue-readiness', admission.status === 'unmanaged' ? 'unavailable' : 'blocked', 'issue'); + put('linked-branch-readiness', 'not-applicable'); + } + else { + const decision = (0, issue_start_policy_1.decideIssueStart)({ kind: admission.kind, labels: issue.labels, + issueManagedBranches: configuration.issueManagedBranches, preBranchSdd: configuration.preBranchSdd }); + put('issue-readiness', decision.sddRequired ? (0, github_runtime_doctor_policy_1.doctorRepositoryPrerequisites)(verifiedRead) : 'not-applicable', 'specifications'); + if (!decision.branchRequired) + put('linked-branch-readiness', 'not-applicable'); + else { + const branch = await ports.readLinkedBranch(number).catch(() => ({ state: 'unavailable' })); + const state = branch.state === 'available' && branch.repositoryId === repositoryId && /^[a-f0-9]{40}$/u.test(branch.headSha) + ? 'pass' : branch.state === 'absent' ? 'warning' : 'unavailable'; + put('linked-branch-readiness', state, 'branch'); + } + } + } + catch { + put('issue-readiness', 'unavailable', 'retry'); + put('linked-branch-readiness', 'unavailable', 'retry'); + } + } + const policyRead = ports ? await ports.readApprovalPolicy().catch(() => ({ status: 'unavailable' })) + : { status: 'unavailable' }; + const policy = (0, github_runtime_doctor_1.parseTrustedDoctorApprovalPolicy)(policyRead); + put('approval-policy', policy.status === 'valid' ? 'pass' : policy.status === 'invalid' ? 'blocked' : 'unavailable', 'policy'); + if (policy.status === 'valid' || policyRead.status === 'available') + checks[checks.length - 1] = Object.freeze({ ...checks[checks.length - 1], + evidence: { source: policyRead.status === 'available' ? policyRead.source : policy.status === 'valid' ? policy.source : 'unavailable' } }); + put('approval-prerequisites', policy.status !== 'valid' ? 'unavailable' : policy.policy.mode === 'off' ? 'not-applicable' : 'warning', 'declared'); + if (policy.status === 'valid') + checks[checks.length - 1] = Object.freeze({ ...checks[checks.length - 1], evidence: { source: policy.policy.mode } }); + if (!isPull) + put('pull-request-identity', 'not-applicable'); + else { + let state = 'unavailable'; + try { + if (ports) { + const request = (0, bugbot_context_request_1.projectBugbotContextRequest)(param.bugbot.review); + const selection = request.target.pullRequestSelection; + if (selection.kind === 'event' && selection.number === number) { + const canonical = (0, context_1.selectCanonicalBugbotPullRequest)(request.target, [await ports.pullRequests.getPullRequest(number)], 'event'); + if (canonical.kind === 'canonical' && /^[a-f0-9]{40}$/u.test(canonical.pullRequest.headSha) + && await ports.pullRequests.getPullRequestHeadSha(number) === canonical.pullRequest.headSha) + state = 'pass'; + } + } + } + catch { /* Independent facts remain available. */ } + put('pull-request-identity', state, 'retry'); + } + const report = (0, github_runtime_doctor_1.aggregateGithubRuntimeDoctorReport)({ target, checks }); + return doctorResult(taskId, report, ports?.serverOrigin, verifiedRead.state === 'available' ? verifiedRead.revision : undefined, param.status.owner, param.status.repository); +} +function doctorResult(taskId, report, serverOrigin, revision, owner, repository) { + const projection = Object.freeze({ report, serverOrigin, revision, owner, repository }); + return new result_1.Result({ id: `${taskId}.Doctor`, success: true, executed: true, stepFormat: 'markdown', + steps: [(0, github_runtime_doctor_presentation_policy_1.formatGithubRuntimeDoctor)(projection)], payload: { doctor: projection } }); +} + + /***/ }), /***/ 8140: @@ -77419,6 +78230,185 @@ function requireObject(data, operation) { } +/***/ }), + +/***/ 25282: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.RuntimeDoctorRepository = void 0; +const github = __importStar(__nccwpck_require__(78227)); +const github_runtime_doctor_ports_1 = __nccwpck_require__(64921); +const pull_request_approval_repository_1 = __nccwpck_require__(23001); +const SHA = /^[a-f0-9]{40}$/u; +const github_runtime_doctor_1 = __nccwpck_require__(20413); +/** SDK DTOs and diagnostics terminate here; no mutation, provisioning, or filesystem API. */ +class RuntimeDoctorRepository { + constructor(owner, repository, token) { + this.owner = owner; + this.repository = repository; + this.client = github.getOctokit(token); + } + async readRepository(repositoryId) { + try { + const repo = await this.client.rest.repos.get({ owner: this.owner, repo: this.repository }); + if (repo.data.id !== repositoryId || !(0, github_runtime_doctor_1.isDoctorBranchReference)(repo.data.default_branch)) + return { state: 'unavailable' }; + const ref = await this.client.rest.git.getRef({ owner: this.owner, repo: this.repository, ref: `heads/${repo.data.default_branch}` }); + const revision = ref.data.object.sha; + if (!SHA.test(revision) || ref.data.object.type !== 'commit') + return { state: 'unavailable' }; + const trackedPaths = this.readTrackedPaths(revision); + const pathType = (path) => trackedPaths.then(paths => paths.get(path)); + const [values, inventory] = await Promise.all([ + Promise.all(github_runtime_doctor_ports_1.DOCTOR_REPOSITORY_PATHS.map(async (path) => [path, await this.readFile(path, revision, pathType)])), trackedPaths, + ]); + const final = await this.client.rest.git.getRef({ owner: this.owner, repo: this.repository, ref: `heads/${repo.data.default_branch}` }); + if (final.data.object.type !== 'commit' || final.data.object.sha !== revision) + return { state: 'unavailable' }; + return Object.freeze({ state: 'available', repositoryId, revision, + trackedFiles: Object.freeze([...inventory].filter(([, type]) => type === 'file').map(([path]) => path)), + files: Object.freeze(Object.fromEntries(values)) }); + } + catch { + return { state: 'unavailable' }; + } + } + async readFile(path, revision, pathType) { + try { + const response = await this.client.rest.repos.getContent({ owner: this.owner, repo: this.repository, path, ref: revision }); + const data = response.data; + if (Array.isArray(data) || data.type !== 'file' || data.path !== path || !('content' in data) + || data.encoding !== 'base64' || typeof data.content !== 'string' || data.content.length > 300000 + || !/^[A-Za-z0-9+/=\r\n]*$/u.test(data.content)) + throw new Error('Malformed bounded file response.'); + if (await pathType(path) !== 'file') + throw new Error('The pinned path is not a regular repository file.'); + const bytes = Buffer.from(data.content, 'base64'); + const text = bytes.toString('utf8'); + if (bytes.toString('base64') !== data.content.replace(/[\r\n]/gu, '') + || bytes.length > 200000 || !Buffer.from(text, 'utf8').equals(bytes)) + throw new Error('Malformed bounded file content.'); + return Object.freeze({ state: 'present', text }); + } + catch (error) { + if (status(error) === 404 && (await pathType(path)) === undefined) + return Object.freeze({ state: 'missing' }); + throw error; + } + } + /** A content 404 alone is ambiguous. Only a complete immutable tree proves absence. */ + async readTrackedPaths(revision) { + const commit = await this.client.rest.git.getCommit({ owner: this.owner, repo: this.repository, commit_sha: revision }); + if (commit.data.sha !== revision || !SHA.test(commit.data.tree.sha)) + throw new Error('Unverified repository tree identity.'); + const response = await this.client.rest.git.getTree({ owner: this.owner, repo: this.repository, tree_sha: commit.data.tree.sha, recursive: '1' }); + const tree = response.data; + if (tree.sha !== commit.data.tree.sha || tree.truncated !== false || !Array.isArray(tree.tree) || tree.tree.length > 10000) + throw new Error('Incomplete bounded repository tree.'); + const paths = new Map(); + for (const entry of tree.tree) { + const path = entry.path; + if (typeof path !== 'string' || !path || path.length > 1024 || path.startsWith('/') || path.includes('\\') + || Array.from(path).some(character => character.charCodeAt(0) < 32) || path.split('/').includes('..') || paths.has(path) + || !['blob', 'tree', 'commit'].includes(entry.type ?? '') || !SHA.test(entry.sha ?? '')) + throw new Error('Malformed repository tree evidence.'); + paths.set(path, entry.type === 'blob' && ['100644', '100755'].includes(entry.mode ?? '') ? 'file' : 'other'); + } + return paths; + } + async readIssue(issueNumber) { + const response = await this.client.rest.issues.get({ owner: this.owner, repo: this.repository, issue_number: issueNumber }); + const issue = response.data; + if (issue.number !== issueNumber || issue.pull_request || (issue.body !== null && typeof issue.body !== 'string') || (issue.body?.length ?? 0) > 70000 + || typeof issue.title !== 'string' || issue.title.length > 256 || !Array.isArray(issue.labels) || issue.labels.length > 100) + throw new Error('Malformed bounded issue response.'); + const labels = issue.labels.map((label) => typeof label === 'string' ? label : label.name); + if (labels.some((label) => typeof label !== 'string' || label.length > 120)) + throw new Error('Malformed issue labels.'); + return Object.freeze({ number: issue.number, title: issue.title, body: issue.body ?? '', labels: Object.freeze(labels) }); + } + async readLinkedBranch(issueNumber) { + try { + const result = await this.client.graphql(`query RuntimeDoctorLinkedBranch($owner:String!,$repo:String!,$number:Int!){repository(owner:$owner,name:$repo){ + databaseId nameWithOwner issue(number:$number){number linkedBranches(first:100){ + pageInfo{hasNextPage} nodes{ref{name target{oid} repository{databaseId nameWithOwner}}}}}}}`, { owner: this.owner, repo: this.repository, number: issueNumber }); + const repo = result.repository; + const links = repo?.issue?.linkedBranches; + const expected = `${this.owner}/${this.repository}`.toLowerCase(); + if (!repo || !Number.isSafeInteger(repo.databaseId) || (repo.databaseId ?? 0) <= 0 || repo.nameWithOwner?.toLowerCase() !== expected + || repo.issue?.number !== issueNumber || links?.pageInfo?.hasNextPage !== false || !Array.isArray(links.nodes)) + return { state: 'unavailable' }; + const refs = links.nodes.flatMap((node) => node?.ref ? [node.ref] : []); + if (refs.length !== links.nodes.length) + return { state: 'unavailable' }; + if (refs.length === 0) + return { state: 'absent' }; + if (refs.length !== 1) + return { state: 'unavailable' }; + const branch = refs[0]; + if (!branch.name || !(0, github_runtime_doctor_1.isDoctorBranchReference)(branch.name) || !branch.target?.oid || !SHA.test(branch.target.oid) + || branch.repository?.databaseId !== repo.databaseId || branch.repository?.nameWithOwner?.toLowerCase() !== expected) + return { state: 'unavailable' }; + const live = await this.client.rest.git.getRef({ owner: this.owner, repo: this.repository, ref: `heads/${branch.name}` }); + return live.data.object.type === 'commit' && live.data.object.sha === branch.target.oid + ? Object.freeze({ state: 'available', repositoryId: repo.databaseId, name: branch.name, headSha: branch.target.oid }) : { state: 'unavailable' }; + } + catch { + return { state: 'unavailable' }; + } + } + async readApprovalPolicy() { + try { + const value = await (0, pull_request_approval_repository_1.readEffectiveApprovalPolicy)(this.client, { owner: this.owner, repository: this.repository }); + return value ? Object.freeze({ status: 'available', ...value }) : Object.freeze({ status: 'absent' }); + } + catch { + return Object.freeze({ status: 'unavailable' }); + } + } +} +exports.RuntimeDoctorRepository = RuntimeDoctorRepository; +function status(error) { + return error && typeof error === 'object' && 'status' in error && typeof error.status === 'number' ? error.status : undefined; +} + + /***/ }), /***/ 52644: @@ -79458,6 +80448,81 @@ function selectAvailableMembers(members, currentMembers, requested) { } +/***/ }), + +/***/ 96710: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.parseApprovalCoverageZip = parseApprovalCoverageZip; +const node_zlib_1 = __nccwpck_require__(65628); +/** Parse one bounded GitHub artifact as data; never extract paths or execute content. */ +function parseApprovalCoverageZip(value) { + const zip = Buffer.from(value); + if (zip.length > 64 * 1024 || zip.length < 30) + return undefined; + let eocd = -1; + for (let index = zip.length - 22; index >= Math.max(0, zip.length - 65557); index--) { + if (zip.readUInt32LE(index) === 0x06054b50) { + eocd = index; + break; + } + } + if (eocd < 0 || zip.readUInt16LE(eocd + 10) !== 1) + return undefined; + const central = zip.readUInt32LE(eocd + 16); + if (central + 46 > zip.length || zip.readUInt32LE(central) !== 0x02014b50) + return undefined; + const method = zip.readUInt16LE(central + 10); + const compressedSize = zip.readUInt32LE(central + 20); + const plainSize = zip.readUInt32LE(central + 24); + const nameLength = zip.readUInt16LE(central + 28); + const name = zip.subarray(central + 46, central + 46 + nameLength).toString('utf8'); + const local = zip.readUInt32LE(central + 42); + if (name !== 'copilot-diff-coverage.json' || plainSize > 16384 || compressedSize > 64 * 1024 + || ![0, 8].includes(method) || local + 30 > zip.length || zip.readUInt32LE(local) !== 0x04034b50) + return undefined; + const start = local + 30 + zip.readUInt16LE(local + 26) + zip.readUInt16LE(local + 28); + if (start + compressedSize > zip.length) + return undefined; + let raw; + try { + const contents = zip.subarray(start, start + compressedSize); + raw = method === 0 ? contents : (0, node_zlib_1.inflateRawSync)(contents, { maxOutputLength: 16384 }); + } + catch { + return undefined; + } + if (raw.length !== plainSize) + return undefined; + let parsed; + try { + parsed = JSON.parse(raw.toString('utf8')); + } + catch { + return undefined; + } + if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) + return undefined; + const item = parsed; + const keys = [ + 'version', 'repositoryId', 'pullNumber', 'headSha', 'baseSha', 'workflowRunId', + 'workflowRunAttempt', 'coveredChangedLines', 'totalChangedLines', + ]; + if (Object.keys(item).sort().join(',') !== keys.sort().join(',') || item.version !== 1 + || ![item.repositoryId, item.pullNumber, item.workflowRunId, item.workflowRunAttempt, + item.coveredChangedLines, item.totalChangedLines].every(Number.isSafeInteger) + || !/^[a-f0-9]{40}$/iu.test(String(item.headSha)) || !/^[a-f0-9]{40}$/iu.test(String(item.baseSha)) + || Number(item.repositoryId) <= 0 || Number(item.pullNumber) <= 0 || Number(item.workflowRunId) <= 0 + || Number(item.workflowRunAttempt) <= 0 || Number(item.totalChangedLines) < 0 + || Number(item.coveredChangedLines) < 0 || Number(item.coveredChangedLines) > Number(item.totalChangedLines)) + return undefined; + return item; +} + + /***/ }), /***/ 55165: @@ -79489,6 +80554,434 @@ class BugbotPullRequestRepository { exports.BugbotPullRequestRepository = BugbotPullRequestRepository; +/***/ }), + +/***/ 23001: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.PullRequestApprovalRepository = void 0; +exports.readEffectiveApprovalPolicy = readEffectiveApprovalPolicy; +const logging_ports_1 = __nccwpck_require__(6152); +const github = __importStar(__nccwpck_require__(78227)); +const pull_request_approval_policy_1 = __nccwpck_require__(98820); +const approval_coverage_artifact_1 = __nccwpck_require__(96710); +const file_ignore_policy_1 = __nccwpck_require__(20542); +const pull_request_approval_presentation_policy_1 = __nccwpck_require__(79017); +/** GitHub DTOs terminate here. Every partial/forbidden read becomes non-authorizing evidence. */ +class PullRequestApprovalRepository { + constructor(token, settings) { + this.token = token; + this.settings = settings; + this.octokit = github.getOctokit(token); + this.checkOctokit = settings.githubToken ? github.getOctokit(settings.githubToken) : undefined; + } + /** Read-only source evidence; failure never consults an injected policy value. */ + async readPolicyEvidence(target) { + try { + const read = await this.readEffectivePolicy(target); + return Object.freeze(read ? { status: 'available', ...read } : { status: 'absent' }); + } + catch { + return Object.freeze({ status: 'unavailable' }); + } + } + async readPolicy(target) { + return (await this.readEffectivePolicy(target))?.value; + } + async readEffectivePolicy(target) { + return readEffectiveApprovalPolicy(this.octokit, target); + } + async loadEvidence(target) { + const { data: pull } = await this.octokit.rest.pulls.get({ + owner: target.owner, repo: target.repository, pull_number: target.pullNumber, + }); + const headSha = pull.head.sha; + const baseSha = await this.readCurrentBaseSha(target, pull.base.ref); + const policy = (0, pull_request_approval_policy_1.parsePullRequestApprovalPolicy)(await this.readPolicy(target)); + let testedMergeSha; + if (pull.mergeable === true && /^[a-f0-9]{40}$/iu.test(pull.merge_commit_sha ?? '')) { + try { + const merge = await this.octokit.rest.git.getCommit({ + owner: target.owner, repo: target.repository, commit_sha: pull.merge_commit_sha, + }); + if (merge.data.parents[0]?.sha === baseSha && merge.data.parents[1]?.sha === headSha + && merge.data.parents.length === 2) + testedMergeSha = pull.merge_commit_sha; + } + catch { /* Missing or unreadable merge parentage is non-authorizing. */ } + } + const [files, reviews, comments, headChecks, mergeChecks, headRuns, mergeRuns, rules, user] = await Promise.all([ + this.allPages(this.octokit.rest.pulls.listFiles, { owner: target.owner, repo: target.repository, pull_number: target.pullNumber }), + this.allPages(this.octokit.rest.pulls.listReviews, { owner: target.owner, repo: target.repository, pull_number: target.pullNumber }), + this.allPages(this.octokit.rest.issues.listComments, { owner: target.owner, repo: target.repository, issue_number: target.pullNumber }), + this.allPages(this.octokit.rest.checks.listForRef, { owner: target.owner, repo: target.repository, ref: headSha, filter: 'all' }), + testedMergeSha ? this.allPages(this.octokit.rest.checks.listForRef, { owner: target.owner, repo: target.repository, ref: testedMergeSha, filter: 'all' }) : Promise.resolve([]), + this.allPages(this.octokit.rest.actions.listWorkflowRunsForRepo, { owner: target.owner, repo: target.repository, head_sha: headSha }), + testedMergeSha ? this.allPages(this.octokit.rest.actions.listWorkflowRunsForRepo, { owner: target.owner, repo: target.repository, head_sha: testedMergeSha }) : Promise.resolve([]), + this.effectiveRules(target.owner, target.repository, pull.base.ref), + this.octokit.rest.users.getAuthenticated(), + ]); + const branch = Object.entries(this.settings.branchPrefixes) + .find(([, prefix]) => pull.head.ref.startsWith(`${prefix}/`)); + const branchKind = branch?.[0]; + const branchIssue = branch + ? Number(pull.head.ref.slice(branch[1].length + 1).match(/^(\d+)-/u)?.[1]) + : NaN; + const linkedIssue = Number.isSafeInteger(branchIssue) && branchIssue > 0 + && new RegExp(`\\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\\s+#${branchIssue}\\b`, 'iu').test(pull.body ?? ''); + const checkRuns = [...headChecks, ...mergeChecks]; + const workflowRuns = [...headRuns, ...mergeRuns]; + const latestRuns = [...new Set(policy.testChecks.map(producer => producer.workflowName))].flatMap(name => { + const named = workflowRuns.filter(run => run.name === name); + const onMerge = named.filter(run => run.head_sha === testedMergeSha); + const authoritative = onMerge.length > 0 ? onMerge : named.filter(run => run.head_sha === headSha); + const latest = [...authoritative].sort((left, right) => right.id - left.id || right.run_attempt - left.run_attempt)[0]; + return latest ? [latest] : []; + }); + const currentJobs = await Promise.all(latestRuns.map(async (run) => ({ + run, + jobs: await this.allPages(this.octokit.rest.actions.listJobsForWorkflowRunAttempt, { + owner: target.owner, repo: target.repository, run_id: run.id, attempt_number: run.run_attempt, + }), + }))); + const checkFacts = currentJobs.flatMap(({ run, jobs }) => jobs.flatMap(job => { + const id = Number(job.check_run_url?.match(/\/check-runs\/(\d+)$/u)?.[1]); + const check = checkRuns.find(item => item.id === id && item.head_sha === run.head_sha); + if (!check) + return []; + return [{ + name: check.name, + sourceAppId: check.app?.id ?? 0, + workflowName: run.name, + headSha: check.head_sha, + conclusion: run.conclusion === 'success' ? check.conclusion : run.conclusion, + status: run.status === 'completed' ? check.status : run.status, + runId: run.id, + attempt: run.run_attempt, + }]; + })); + const botId = user.data.id; + const botCards = comments.filter((comment) => comment.user?.id === botId && /')); + const body = (0, pull_request_approval_presentation_policy_1.renderApprovalAssessment)({ target, evidence, decision, locale: this.settings.locale ?? 'en-US', + ...(reviewId ? { reviewId } : {}) }); + if (card) { + if (card.body !== body) + await this.octokit.rest.issues.updateComment({ owner: target.owner, repo: target.repository, comment_id: card.id, body }); + } + else { + await this.octokit.rest.issues.createComment({ owner: target.owner, repo: target.repository, issue_number: target.pullNumber, body }); + } + if (this.checkOctokit) { + const prior = await this.checkOctokit.rest.checks.listForRef({ + owner: target.owner, repo: target.repository, ref: headSha, check_name: 'Copilot / Approval', filter: 'all', per_page: 100, + }); + if (prior.data.total_count > 100) + throw new Error('Approval Check Run history exceeds the bounded lookup.'); + const externalId = `copilot:approval:${target.repositoryId}:${target.pullNumber}`; + const matches = prior.data.check_runs.filter((check) => check.name === 'Copilot / Approval' + && check.app?.slug === 'github-actions' && check.external_id === externalId); + if (matches.length > 1) + throw new Error('Approval Check Run identity is ambiguous.'); + const existing = matches[0]; + const fields = { + owner: target.owner, repo: target.repository, name: 'Copilot / Approval', head_sha: headSha, + external_id: externalId, + status: 'completed', + conclusion: decision.code === 'approved' || decision.status === 'already-approved' ? 'success' + : decision.status === 'blocked' ? 'failure' : 'neutral', + output: { title: `PR approval: ${decision.status}`, summary: body.slice(0, 20000) }, + }; + if (existing) { + await this.checkOctokit.rest.checks.update({ + owner: target.owner, repo: target.repository, check_run_id: existing.id, + status: fields.status, conclusion: fields.conclusion, output: fields.output, + }); + } + else { + await this.checkOctokit.rest.checks.create(fields); + } + } + } + /** Pull-request base metadata may lag a parent push; only the native ref is current authority. */ + async readCurrentBaseSha(target, baseRef) { + if (typeof baseRef !== 'string' || baseRef.length === 0 || baseRef.length > 1024 + || baseRef.includes('\u0000') || /[\r\n]/u.test(baseRef)) + throw new Error('The PR base ref is invalid.'); + const { data } = await this.octokit.rest.git.getRef({ + owner: target.owner, repo: target.repository, ref: `heads/${baseRef}`, + }); + if (data?.ref !== `refs/heads/${baseRef}` || data.object?.type !== 'commit' || typeof data.object.sha !== 'string' + || !/^[a-f0-9]{40}$/u.test(data.object.sha)) + throw new Error('The PR base ref cannot be verified.'); + return data.object.sha; + } + async effectiveRules(owner, repo, branch) { + try { + const rulesResponse = await this.octokit.request('GET /repos/{owner}/{repo}/rules/branches/{branch}', { owner, repo, branch }); + if (!Array.isArray(rulesResponse.data) || rulesResponse.data.some((rule) => !record(rule) + || typeof record(rule)?.type !== 'string' || record(rule)?.type === '')) { + throw new Error('GitHub returned an invalid effective rules inventory.'); + } + const rules = rulesResponse.data; + let classic; + try { + const response = await this.octokit.rest.repos.getBranchProtection({ owner, repo, branch }); + classic = response.data; + } + catch (error) { + if (statusOf(error) !== 404) + throw error; + } + const reviewRules = rules.filter(rule => rule.type === 'pull_request'); + const classicReview = record(classic?.required_pull_request_reviews); + const allStale = reviewRules.every(rule => record(rule.parameters)?.dismiss_stale_reviews === true) + && (!classicReview || classicReview.dismiss_stale_reviews === true); + const hasStaleRule = reviewRules.length > 0 || classicReview !== undefined; + const requiredChecks = new Set(); + const classicChecks = record(classic?.required_status_checks); + for (const item of array(classicChecks?.contexts)) + if (typeof item === 'string') + requiredChecks.add(item); + for (const item of array(classicChecks?.checks)) { + const context = record(item)?.context; + if (typeof context === 'string') + requiredChecks.add(context); + } + for (const rule of rules.filter(item => item.type === 'required_status_checks')) { + for (const item of array(record(rule.parameters)?.required_status_checks)) { + const context = record(item)?.context; + if (typeof context === 'string') + requiredChecks.add(context); + } + } + return { readable: true, dismissesStaleReviews: hasStaleRule && allStale, requiredChecks: [...requiredChecks] }; + } + catch { + return { readable: false, dismissesStaleReviews: false, requiredChecks: [] }; + } + } + async allPages(method, params) { + const result = []; + for await (const response of this.octokit.paginate.iterator(method, { ...params, per_page: 100 })) { + const page = Array.isArray(response.data) ? response.data + : record(response.data)?.check_runs ?? record(response.data)?.workflow_runs + ?? record(response.data)?.artifacts ?? record(response.data)?.jobs; + if (!Array.isArray(page)) + throw new Error('GitHub pagination returned an invalid page.'); + result.push(...page); + if (result.length > 3000) + throw new Error('Approval evidence pagination exceeded the safe bound.'); + } + return result; + } +} +exports.PullRequestApprovalRepository = PullRequestApprovalRepository; +function parseBugbotCard(body, headSha, pullNumber) { + const status = body.match(//u); + const evidence = body.match(//u); + if (!status || !evidence || Number(status[1]) !== pullNumber || status[2] !== headSha + || evidence[1] !== headSha || status[3] !== evidence[2]) + return undefined; + const counts = evidence.slice(5).map(Number); + if (counts.some(count => !Number.isSafeInteger(count))) + return undefined; + return { + headSha, outcome: evidence[3], coverage: evidence[4], + open: counts[0], reopened: counts[1], dismissed: counts[2], + verificationRequired: counts[3], unknown: counts[4], + }; +} +/** Fixed metadata only: never interpolate error text, response bodies or credentials. */ +function reportPolicyReadFailure(scope, error) { + const status = statusOf(error); + const code = [400, 401, 403, 409, 422, 429, 500, 502, 503, 504].includes(status) + ? `http-${status}` : 'http-unavailable'; + (0, logging_ports_1.logInfo)(`Approval policy read failed (${scope}, ${code}).`); +} +function statusOf(error) { + return record(error)?.status; +} +function record(value) { + return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined; +} +function array(value) { return Array.isArray(value) ? value : []; } +/** Canonical repository-over-organization read shared with the physically read-only doctor binding. */ +async function readEffectiveApprovalPolicy(octokit, target) { + const admit = (source, value) => { + if (typeof value !== 'string' || value.length > 65536) + throw new Error('Approval policy response is malformed or unbounded.'); + return { source, value }; + }; + try { + const response = await octokit.rest.actions.getRepoVariable({ owner: target.owner, repo: target.repository, name: 'PR_APPROVAL_POLICY' }); + return admit('repository', response.data.value); + } + catch (error) { + if (statusOf(error) !== 404) { + reportPolicyReadFailure('repository-variable', error); + throw error; + } + } + try { + const response = await octokit.rest.actions.getOrgVariable({ org: target.owner, name: 'PR_APPROVAL_POLICY' }); + return admit('organization', response.data.value); + } + catch (error) { + if (statusOf(error) === 404) + return undefined; + reportPolicyReadFailure('organization-variable', error); + throw error; + } +} + + /***/ }), /***/ 71564: @@ -82150,6 +83643,7 @@ exports.COPILOT_COMMAND_NAMES = [ 'estimate', 'test-plan', 'status', + 'doctor', 'history', 'description', 'explain', @@ -82209,6 +83703,9 @@ function parseCopilotCommand(raw) { && name !== 'remember') { return { kind: 'invalid', reason: `Copilot commands accept at most ${MAX_ARGUMENTS} arguments.` }; } + if (name === 'doctor' && argumentsList.length > 0) { + return { kind: 'invalid', reason: '/copilot doctor does not accept arguments.' }; + } if ((name === 'fix' || name === 'dismiss' || name === 'implement' || name === 'remember') && argumentsList.length === 0) { return { kind: 'invalid', reason: `/${name} requires at least one argument.` }; } @@ -83006,6 +84503,85 @@ function publicationTargetToken(target) { } +/***/ }), + +/***/ 20413: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.GITHUB_RUNTIME_DOCTOR_GROUPS = void 0; +exports.parseTrustedDoctorApprovalPolicy = parseTrustedDoctorApprovalPolicy; +exports.aggregateGithubRuntimeDoctorReport = aggregateGithubRuntimeDoctorReport; +exports.isDoctorBranchReference = isDoctorBranchReference; +const pull_request_approval_policy_1 = __nccwpck_require__(98820); +// Domain contracts and trusted policy parsing for the issue/PR runtime doctor. +/** The fixed, ordered evidence groups in the GitHub runtime doctor contract. */ +exports.GITHUB_RUNTIME_DOCTOR_GROUPS = [ + 'effective-configuration', + 'issue-readiness', + 'linked-branch-readiness', + 'workflow-contracts', + 'approval-policy', + 'approval-prerequisites', + 'pull-request-identity', +]; +/** + * Parses only a successful trusted effective read. Verified absence preserves the + * established off default; read failure is unavailable and never consults an injected value. + */ +function parseTrustedDoctorApprovalPolicy(read) { + if (read.status === 'unavailable') + return { status: 'unavailable' }; + if (read.status === 'absent') { + return { status: 'valid', source: 'default-off', policy: pull_request_approval_policy_1.DISABLED_PULL_REQUEST_APPROVAL_POLICY }; + } + if (typeof read.value !== 'string') + return { status: 'unavailable' }; + if (!read.value.trim()) + return { status: 'invalid' }; + try { + return { status: 'valid', source: read.source, policy: (0, pull_request_approval_policy_1.parsePullRequestApprovalPolicy)(read.value) }; + } + catch { + // Do not leak parser/provider text into a report or user-facing surface. + return { status: 'invalid' }; + } +} +/** Aggregate explicit independent evidence. Missing groups are unavailable, never assumed healthy. */ +function aggregateGithubRuntimeDoctorReport(input) { + const byGroup = new Map(); + for (const check of input.checks) { + if (!exports.GITHUB_RUNTIME_DOCTOR_GROUPS.includes(check.group)) + throw new Error('Unknown GitHub runtime doctor group.'); + if (byGroup.has(check.group)) + throw new Error(`Duplicate GitHub runtime doctor group: ${check.group}.`); + byGroup.set(check.group, Object.freeze({ ...check, ...(check.evidence ? { evidence: Object.freeze({ ...check.evidence }) } : {}) })); + } + const checks = exports.GITHUB_RUNTIME_DOCTOR_GROUPS.map(group => byGroup.get(group) + ?? Object.freeze({ group, state: 'unavailable' })); + const priority = ['blocked', 'unavailable', 'warning', 'pass']; + const applicable = checks.filter(check => check.state !== 'not-applicable'); + const severity = applicable.length === 0 ? 'not-applicable' + : priority.find(state => applicable.some(check => check.state === state)) ?? 'unavailable'; + const primaryAction = applicable.find(check => check.state === severity && check.action)?.action; + return Object.freeze({ + target: Object.freeze({ ...input.target }), + checks: Object.freeze(checks), + severity, + ...(primaryAction ? { primaryAction } : {}), + }); +} +/** Bounded literal Git branch names: never revisions, options or reflog expressions. */ +function isDoctorBranchReference(value) { + return typeof value === 'string' + && /^(?!-|.*\.\.)[A-Za-z0-9._/-]{1,200}$/u.test(value) + && value.split('/').every(part => part.length > 0 && !part.startsWith('.') + && !part.endsWith('.') && !part.endsWith('.lock')); +} + + /***/ }), /***/ 84403: @@ -86671,6 +88247,29 @@ const createRepositoryVersionTagsClient = () => new octokit_release_adapters_1.O exports.createRepositoryVersionTagsClient = createRepositoryVersionTagsClient; +/***/ }), + +/***/ 36459: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.createGithubRuntimeDoctorPorts = createGithubRuntimeDoctorPorts; +const runtime_doctor_repository_1 = __nccwpck_require__(25282); +function createGithubRuntimeDoctorPorts(binding, pullRequests) { + const repository = new runtime_doctor_repository_1.RuntimeDoctorRepository(binding.owner, binding.repository, binding.token); + return Object.freeze({ serverOrigin: process.env.GITHUB_SERVER_URL ?? 'https://github.com', + readRepository: (id) => repository.readRepository(id), + readIssue: (number) => repository.readIssue(number), + readLinkedBranch: (number) => repository.readLinkedBranch(number), + readApprovalPolicy: () => repository.readApprovalPolicy(), + pullRequests: Object.freeze({ getPullRequest: (number) => pullRequests.getPullRequest(number), + getPullRequestHeadSha: (number) => pullRequests.getPullRequestHeadSha(number) }), + }); +} + + /***/ }), /***/ 29839: @@ -87145,6 +88744,7 @@ exports.createPullRequestReviewCommentUseCaseCompositionRoot = createPullRequest exports.createCommitUseCaseCompositionRoot = createCommitUseCaseCompositionRoot; exports.createMainRunRouteCompositionRoot = createMainRunRouteCompositionRoot; exports.createClosedIssueRetirementUseCase = createClosedIssueRetirementUseCase; +const github_runtime_doctor_composition_root_1 = __nccwpck_require__(36459); const reply_request_composition_root_1 = __nccwpck_require__(99065); const retire_branch_sync_use_case_1 = __nccwpck_require__(74111); const issue_completion_composition_root_1 = __nccwpck_require__(63719); @@ -87267,7 +88867,7 @@ function createIssueCommentUseCaseCompositionRoot(binding) { contextPorts: bugbot.scm.context, resolutionPorts: bugbot.scm.resolution, catalogResolver: new resolve_message_catalog_use_case_1.ResolveMessageCatalogUseCase(language), - }), new detect_potential_problems_use_case_1.DetectPotentialProblemsUseCase(findings, bugbot.scm, bugbot.telemetry, new resolve_message_catalog_use_case_1.ResolveMessageCatalogUseCase(language)), pullRequestDescription, new remember_bugbot_rule_use_case_1.RememberBugbotRuleUseCase(bugbot.rules), branchSync, (0, issue_use_case_composition_root_1.createIssueUseCaseCompositionRoot)(binding), rememberBranchPorts(binding, bugbot.scm.context), bugbot.scm.context, (0, copilot_history_port_factory_1.createCopilotHistoryReadPort)(bugbot.scm.context, process.env.GITHUB_SERVER_URL ?? 'https://github.com'), (0, sdd_evidence_composition_root_1.createSddEvidenceQueryPort)(binding, bugbot.scm.context), (0, issue_completion_composition_root_1.createNativeStatusReadPort)(binding)); + }), new detect_potential_problems_use_case_1.DetectPotentialProblemsUseCase(findings, bugbot.scm, bugbot.telemetry, new resolve_message_catalog_use_case_1.ResolveMessageCatalogUseCase(language)), pullRequestDescription, new remember_bugbot_rule_use_case_1.RememberBugbotRuleUseCase(bugbot.rules), branchSync, (0, issue_use_case_composition_root_1.createIssueUseCaseCompositionRoot)(binding), rememberBranchPorts(binding, bugbot.scm.context), bugbot.scm.context, (0, copilot_history_port_factory_1.createCopilotHistoryReadPort)(bugbot.scm.context, process.env.GITHUB_SERVER_URL ?? 'https://github.com'), (0, sdd_evidence_composition_root_1.createSddEvidenceQueryPort)(binding, bugbot.scm.context), (0, issue_completion_composition_root_1.createNativeStatusReadPort)(binding), (0, github_runtime_doctor_composition_root_1.createGithubRuntimeDoctorPorts)(binding, bugbot.scm.context)); } function createPullRequestReviewCommentUseCaseCompositionRoot(binding) { const bugbot = (0, bugbot_composition_root_1.createBugbotCompositionRoot)(binding); @@ -87283,7 +88883,7 @@ function createPullRequestReviewCommentUseCaseCompositionRoot(binding) { contextPorts: bugbot.scm.context, resolutionPorts: bugbot.scm.resolution, catalogResolver: new resolve_message_catalog_use_case_1.ResolveMessageCatalogUseCase(language), - }), new detect_potential_problems_use_case_1.DetectPotentialProblemsUseCase(findings, bugbot.scm, bugbot.telemetry, new resolve_message_catalog_use_case_1.ResolveMessageCatalogUseCase(language)), pullRequestDescription, new remember_bugbot_rule_use_case_1.RememberBugbotRuleUseCase(bugbot.rules), branchSync, rememberBranchPorts(binding, bugbot.scm.context), bugbot.scm.context, (0, copilot_history_port_factory_1.createCopilotHistoryReadPort)(bugbot.scm.context, process.env.GITHUB_SERVER_URL ?? 'https://github.com'), (0, issue_completion_composition_root_1.createNativeStatusReadPort)(binding)); + }), new detect_potential_problems_use_case_1.DetectPotentialProblemsUseCase(findings, bugbot.scm, bugbot.telemetry, new resolve_message_catalog_use_case_1.ResolveMessageCatalogUseCase(language)), pullRequestDescription, new remember_bugbot_rule_use_case_1.RememberBugbotRuleUseCase(bugbot.rules), branchSync, rememberBranchPorts(binding, bugbot.scm.context), bugbot.scm.context, (0, copilot_history_port_factory_1.createCopilotHistoryReadPort)(bugbot.scm.context, process.env.GITHUB_SERVER_URL ?? 'https://github.com'), (0, issue_completion_composition_root_1.createNativeStatusReadPort)(binding), (0, github_runtime_doctor_composition_root_1.createGithubRuntimeDoctorPorts)(binding, bugbot.scm.context)); } function createCommitUseCaseCompositionRoot(projectBoardCommandPort, binding) { return new commit_use_case_1.CommitUseCase(new notify_new_commit_on_issue_use_case_1.NotifyNewCommitOnIssueUseCase((0, push_single_action_capability_port_binding_1.bindIssueReopen)((0, issue_interaction_composition_root_1.createIssueNotificationRepository)(), binding)), new check_changes_issue_size_use_case_1.CheckChangesIssueSizeUseCase((0, lifecycle_capability_port_binding_2.bindProjectBoardCommands)(projectBoardCommandPort, binding), (0, lifecycle_capability_port_binding_2.bindIssueLabels)((0, issue_labels_composition_root_1.createIssueLabelRepository)(), binding), (0, push_single_action_capability_port_binding_1.bindPullRequestBranchQuery)(new pull_request_lifecycle_repository_1.PullRequestLifecycleRepository((0, github_pull_request_client_factory_1.createPullRequestLifecycleClient)()), binding), (0, push_single_action_capability_port_binding_1.bindBranchChangeSize)(new branch_compare_repository_1.BranchCompareRepository((0, github_branch_client_factory_1.createBranchComparisonClient)()), binding)), createDetectPotentialProblemsUseCase(binding), (0, check_progress_composition_root_1.createCheckProgressCompositionRoot)(binding), (0, actor_authorization_composition_root_1.createActorAuthorizationRepository)()); diff --git a/build/github_action/index.js b/build/github_action/index.js index cceeb2760..2f08214ba 100644 --- a/build/github_action/index.js +++ b/build/github_action/index.js @@ -39421,7 +39421,7 @@ exports.shouldTrackAgentActivity = shouldTrackAgentActivity; const agent_1 = __nccwpck_require__(89040); const copilot_command_1 = __nccwpck_require__(11771); const branch_sync_command_1 = __nccwpck_require__(51114); -const DETERMINISTIC_COMMENT_COMMANDS = new Set(['help', 'status', 'history', 'dismiss', 'remember']); +const DETERMINISTIC_COMMENT_COMMANDS = new Set(['help', 'status', 'doctor', 'history', 'dismiss', 'remember']); /** Decides whether a route can invoke an agent for its current event. */ function shouldTrackAgentActivity(execution, route) { if (!hasTarget(execution)) @@ -43481,6 +43481,7 @@ const READ_ONLY_COMMANDS = Object.freeze([ ['/copilot recheck', 'interaction.help.command.recheck'], ['/copilot description', 'interaction.help.command.description'], ['/copilot status', 'interaction.help.command.status'], + ['/copilot doctor', 'interaction.help.command.doctor'], ['/copilot history', 'interaction.help.command.history'], ]); const CHANGE_COMMANDS = Object.freeze([ @@ -44440,6 +44441,138 @@ function shortSha(value) { } +/***/ }), + +/***/ 61833: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.doctorWorkflowCondition = doctorWorkflowCondition; +/** Bounded three-valued interpretation; unsupported runtime facts remain unknown. Never eval YAML. */ +function doctorWorkflowCondition(value, event) { + if (value === undefined) + return true; + if (typeof value === 'boolean') + return value; + if (typeof value !== 'string' || value.length > 1000) + return undefined; + const input = value.trim().replace(/^\$\{\{\s*([\s\S]*?)\s*\}\}$/u, '$1'); + const tokens = input.match(/\s+|&&|\|\||==|!=|[!()]|'(?:[^']|'')*'|[A-Za-z_][A-Za-z0-9_.]*|./gu)?.filter(token => !/^\s+$/u.test(token)) ?? []; + let cursor = 0; + let depth = 0; + const truth = (item) => typeof item === 'string' ? item.length > 0 : item; + function atom() { + if (++depth > 32) + throw new Error('Condition too deep.'); + const token = tokens[cursor++]; + let result; + if (token === '(') { + result = or(); + if (tokens[cursor++] !== ')') + throw new Error('Missing bracket.'); + } + else if (token === '!') { + const inner = truth(atom()); + result = inner === undefined ? undefined : !inner; + } + else if (token === 'true' || token === 'false') + result = token === 'true'; + else if (token === 'github.event_name') + result = event; + else if (token?.startsWith("'") && token.endsWith("'")) + result = token.slice(1, -1).replace(/''/gu, "'"); + else if (token && /^[A-Za-z_][A-Za-z0-9_.]*$/u.test(token)) + result = undefined; + else + throw new Error('Unsupported condition.'); + depth--; + return result; + } + function comparison() { + const left = atom(); + if (tokens[cursor] === '==' || tokens[cursor] === '!=') { + const operator = tokens[cursor++]; + const right = atom(); + if (left === undefined || right === undefined || typeof left !== typeof right) + return undefined; + const equal = typeof left === 'string' && typeof right === 'string' ? left.toLowerCase() === right.toLowerCase() : left === right; + return operator === '==' ? equal : !equal; + } + return truth(left); + } + function and() { + let result = comparison(); + while (tokens[cursor] === '&&') { + cursor++; + const right = comparison(); + result = result === false || right === false ? false : result === undefined || right === undefined ? undefined : true; + } + return result; + } + function or() { + let result = and(); + while (tokens[cursor] === '||') { + cursor++; + const right = and(); + result = result === true || right === true ? true : result === undefined || right === undefined ? undefined : false; + } + return result; + } + try { + const result = or(); + return cursor === tokens.length ? result : undefined; + } + catch { + return undefined; + } +} + + +/***/ }), + +/***/ 87989: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.doctorWorkflowMatrix = doctorWorkflowMatrix; +/** Conservative structural evidence only; never evaluates expressions or expands matrices. */ +function doctorWorkflowMatrix(strategy) { + if (strategy === undefined) + return true; + if (!record(strategy)) + return undefined; + if (!Object.prototype.hasOwnProperty.call(strategy, 'matrix')) + return true; + const matrix = strategy.matrix; + if (!record(matrix)) + return undefined; + const axes = Object.entries(matrix); + if (!axes.length || axes.some(([key]) => key === 'include' || key === 'exclude')) + return undefined; + if (axes.some(([, values]) => Array.isArray(values) && values.length === 0)) + return false; + if (!axes.every(([, values]) => Array.isArray(values) && values.every(value => typeof value === 'string' ? !value.includes('${{') + : typeof value === 'boolean' || typeof value === 'number' && Number.isFinite(value)))) + return undefined; + let combinations = 1; + for (const [, values] of axes) { + const length = values.length; + // Bound arithmetic before multiplying; do not expand the Cartesian product. + if (combinations > 256 / length) + return false; + combinations *= length; + } + return true; +} +function record(value) { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} + + /***/ }), /***/ 20542: @@ -44581,6 +44714,292 @@ function resolveGithubExecutionAdmission(input) { } +/***/ }), + +/***/ 99566: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.validDoctorConfiguration = validDoctorConfiguration; +exports.doctorRepositoryPrerequisites = doctorRepositoryPrerequisites; +exports.doctorWorkflowContracts = doctorWorkflowContracts; +const doctor_workflow_matrix_policy_1 = __nccwpck_require__(87989); +const github_runtime_doctor_1 = __nccwpck_require__(20413); +const doctor_workflow_condition_policy_1 = __nccwpck_require__(61833); +const js_yaml_1 = __nccwpck_require__(783); +const issue_workflow_profile_1 = __nccwpck_require__(26744); +const issue_start_policy_1 = __nccwpck_require__(90332); +function validDoctorConfiguration(value) { + if (!value || typeof value.preBranchSdd !== 'boolean' || typeof value.issueManagedBranches !== 'boolean' + || !(0, github_runtime_doctor_1.isDoctorBranchReference)(value.dependencyBranch)) + return false; + try { + if ('error' in (0, issue_workflow_profile_1.parseIssueWorkflowProfile)(JSON.stringify(value.profile))) + return false; + (0, issue_start_policy_1.decideIssueStart)({ labels: [], issueManagedBranches: value.issueManagedBranches, preBranchSdd: value.preBranchSdd }); + return true; + } + catch { + return false; + } +} +function doctorRepositoryPrerequisites(read) { + if (read.state !== 'available') + return 'unavailable'; + const paths = ['specs/catalog.json', 'specs/_template.md', 'specs/README.md']; + if (!completeFileSamples(read, paths) || !Array.isArray(read.trackedFiles) || read.trackedFiles.some(path => typeof path !== 'string')) + return 'unavailable'; + if (paths.some(path => read.files[path].state !== 'present')) + return 'blocked'; + const catalogFile = read.files['specs/catalog.json']; + if (catalogFile.state !== 'present') + return 'blocked'; + try { + const catalog = object(JSON.parse(catalogFile.text)); + if (catalog.version !== 1 || !Array.isArray(catalog.capabilities) || catalog.capabilities.length === 0) + return 'blocked'; + const files = new Set(read.trackedFiles); + const ids = new Set(), titles = new Set(), specs = new Set(); + for (const raw of catalog.capabilities) { + const capability = object(raw); + if (['id', 'title', 'status', 'scope', 'owner', 'lastVerified'].some(field => typeof capability[field] !== 'string' || !capability[field].trim())) + return 'blocked'; + if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(String(capability.id)) + || ids.has(String(capability.id)) || titles.has(String(capability.title)) + || !['as-built-baseline', 'implemented', 'proposed', 'deprecated'].includes(String(capability.status)) + || !validCatalogDate(String(capability.lastVerified))) + return 'blocked'; + ids.add(String(capability.id)); + titles.add(String(capability.title)); + for (const field of ['specs', 'workflows', 'entrypoints', 'code', 'tests', 'documentation']) { + const entries = capability[field]; + if (!Array.isArray(entries) || (field !== 'workflows' && entries.length === 0) + || new Set(entries).size !== entries.length) + return 'blocked'; + for (const path of entries) { + if (!validCatalogPath(field, path) || !files.has(path)) + return 'blocked'; + if (field === 'specs') { + if (specs.has(path)) + return 'blocked'; + specs.add(path); + } + } + } + } + const standardDocuments = new Set(['specs/README.md', 'specs/_template.md', 'specs/CATALOG.md']); + if (read.trackedFiles.some(path => /^specs\/.+\.md$/u.test(path) && !standardDocuments.has(path) && !specs.has(path))) + return 'blocked'; + return paths.slice(1).every(path => { + const file = read.files[path]; + return file.state === 'present' && /^#\s+\S/mu.test(file.text); + }) ? 'pass' : 'blocked'; + } + catch { + return 'blocked'; + } +} +/** Structural evidence only. YAML is parsed with the JSON schema and never executed. */ +function doctorWorkflowContracts(read) { + if (read.state !== 'available') + return 'unavailable'; + const expected = [ + ['.github/workflows/copilot_issue.yml', 'issues', 'labeled'], + ['.github/workflows/copilot_issue_comment.yml', 'issue_comment', 'created'], + ['.github/workflows/copilot_pull_request.yml', 'pull_request', 'opened'], + ]; + if (!completeFileSamples(read, expected.map(([path]) => path))) + return 'unavailable'; + let unknown = false; + try { + for (const [path, event, activity] of expected) { + const file = read.files[path]; + if (file?.state !== 'present') + return 'blocked'; + const document = object((0, js_yaml_1.load)(file.text, { schema: js_yaml_1.JSON_SCHEMA })); + const events = document.on; + const admitted = typeof events === 'string' ? events === event : Array.isArray(events) + ? events.includes(event) : Object.prototype.hasOwnProperty.call(object(events), event); + if (!admitted) + return 'blocked'; + const filter = object(object(events)[event]); + if (Object.prototype.hasOwnProperty.call(filter, 'types') + && (!Array.isArray(filter.types) || filter.types.length === 0 + || filter.types.some(type => typeof type !== 'string' || type.length === 0) + || !filter.types.includes(activity))) + return 'blocked'; + let eligible = false; + let possible = false; + const jobs = object(document.jobs); + for (const rawJob of Object.values(jobs)) { + const job = object(rawJob); + const enabled = (0, doctor_workflow_condition_policy_1.doctorWorkflowCondition)(job.if, event); + if (enabled === false || !Array.isArray(job.steps)) + continue; + const matrixEnabled = (0, doctor_workflow_matrix_policy_1.doctorWorkflowMatrix)(job.strategy); + if (matrixEnabled === false) + continue; + const dependenciesEnabled = doctorWorkflowDependencies(job, jobs, event, enabled); + if (dependenciesEnabled === false) + continue; + for (const rawStep of job.steps) { + const step = object(rawStep); + if (!(step.uses === './' || typeof step.uses === 'string' + && /^vypdev\/copilot@[^\s${}<>]+$/iu.test(step.uses))) + continue; + const stepEnabled = (0, doctor_workflow_condition_policy_1.doctorWorkflowCondition)(step.if, event); + if (stepEnabled === false) + continue; + if (enabled === true && stepEnabled === true && matrixEnabled === true && dependenciesEnabled === true) + eligible = true; + else + possible = true; + } + } + if (!eligible && !possible) + return 'blocked'; + if (!eligible) + unknown = true; + } + return unknown ? 'unavailable' : 'pass'; + } + catch { + return 'blocked'; + } +} +function object(value) { + return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : {}; +} +function validCatalogDate(value) { + if (!/^\d{4}-\d{2}-\d{2}$/u.test(value)) + return false; + const [year, month, day] = value.split('-').map(Number), date = new Date(Date.UTC(year, month - 1, day)); + return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day; +} +function validCatalogPath(field, value) { + if (typeof value !== 'string' || value !== value.trim() || value.includes('\\') + || value.split('/').some(part => part === '' || part === '.' || part === '..')) + return false; + const boundaries = { + specs: /^specs\/(?!README\.md$|_template\.md$|CATALOG\.md$).+\.md$/u, + workflows: /^(?:\.github|setup)\/workflows\/.+\.ya?ml$/u, + entrypoints: /^(?:src\/.+|web\/src\/main\.ts|action\.yml|package\.json)$/u, + code: /^(?:(?:src|scripts)\/|web\/src\/.+\.(?:ts|svelte|css)$)/u, + tests: /^src\/.*(?:__tests__\/.*\.test\.ts|\.test\.ts)$/u, + documentation: /^(?:docs\/.*\.(?:md|mdx)|README\.md|CONTRIBUTING\.md)$/u, + }; + return boundaries[field].test(value); +} +/** Only a directly skipped prerequisite is proved; runtime success and override expressions stay unknown. */ +function doctorWorkflowDependencies(job, jobs, event, enabled) { + if (!Object.prototype.hasOwnProperty.call(job, 'needs')) + return true; + if (enabled !== true) + return undefined; + const needs = typeof job.needs === 'string' ? [job.needs] : Array.isArray(job.needs) ? job.needs : undefined; + if (!needs || needs.length === 0 || needs.length > 20 || needs.some(name => typeof name !== 'string' || !/^[A-Za-z_][A-Za-z0-9_-]{0,99}$/u.test(name))) + return undefined; + for (const name of needs) { + if (Object.prototype.hasOwnProperty.call(jobs, name) && (0, doctor_workflow_condition_policy_1.doctorWorkflowCondition)(object(jobs[name]).if, event) === false) + return false; + } + return undefined; +} +/** Explicit missing evidence differs from an incomplete or malformed provider sample. */ +function completeFileSamples(read, paths) { + return read.files !== null && typeof read.files === 'object' && !Array.isArray(read.files) + && paths.every(path => { + const sample = read.files[path]; + return sample?.state === 'missing' || sample?.state === 'present' && typeof sample.text === 'string'; + }); +} + + +/***/ }), + +/***/ 2808: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.isGithubRuntimeDoctorProjection = isGithubRuntimeDoctorProjection; +exports.formatGithubRuntimeDoctor = formatGithubRuntimeDoctor; +const github_runtime_doctor_1 = __nccwpck_require__(20413); +const publication_message_catalog_1 = __nccwpck_require__(34223); +const ACTIONS = ['configuration', 'workflow', 'retry', 'issue', 'specifications', 'branch', 'policy', 'declared']; +const STATES = ['pass', 'warning', 'blocked', 'unavailable', 'not-applicable']; +function isGithubRuntimeDoctorProjection(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) + return false; + const projection = value; + const report = projection.report; + if (['serverOrigin', 'revision', 'owner', 'repository'].some(key => { + const field = projection[key]; + return field !== undefined && (typeof field !== 'string' || field.length > 300); + })) + return false; + if (report === undefined) + return true; + if (!report || typeof report !== 'object' || !report.target || !Number.isSafeInteger(report.target.repositoryId) + || report.target.repositoryId < 1 || (report.target.kind !== 'issue' && report.target.kind !== 'pull-request') + || !Number.isSafeInteger(report.target.kind === 'issue' ? report.target.issueNumber : report.target.pullNumber) + || (report.target.kind === 'issue' ? report.target.issueNumber : report.target.pullNumber) < 1) + return false; + return STATES.includes(report.severity) && Array.isArray(report.checks) && report.checks.length === 7 + && report.checks.every((check, index) => check && check.group === github_runtime_doctor_1.GITHUB_RUNTIME_DOCTOR_GROUPS[index] && STATES.includes(check.state)) + && (report.primaryAction === undefined || ACTIONS.includes(report.primaryAction)); +} +function formatGithubRuntimeDoctor(projection, locale = 'en-US', catalog = (0, publication_message_catalog_1.resolveStaticPublicationCatalog)(locale).catalog) { + if (!isGithubRuntimeDoctorProjection(projection)) + projection = {}; + const message = (suffix) => catalog.render(`interaction.doctor.${suffix}`); + const severity = projection.report?.severity ?? 'unavailable'; + const action = projection.report?.primaryAction; + const lines = [`## 🩺 ${message('heading')}: ${message(`state.${severity}`)}`, '', + message(`summary.${severity}`), '', + `**${message('action')}:** ${action && ACTIONS.includes(action) ? message(`action.${action}`) + : severity === 'pass' || severity === 'not-applicable' ? message('action.none') : message('action.retry')}`, + '', message('readonly'), '', `### ${message('checks')}`, '']; + const source = repositorySource(projection); + const origin = source?.split('/').slice(0, 5).join('/'); + for (const group of github_runtime_doctor_1.GITHUB_RUNTIME_DOCTOR_GROUPS) { + const state = projection.report?.checks.find(check => check.group === group)?.state ?? 'unavailable'; + const evidence = projection.report?.checks.find(check => check.group === group)?.evidence?.source; + const target = projection.report?.target; + const link = origin && target && group === 'pull-request-identity' && target.kind === 'pull-request' + ? `${origin}/pull/${target.pullNumber}` : origin && target && target.kind === 'issue' + && (group === 'issue-readiness' || group === 'linked-branch-readiness') ? `${origin}/issues/${target.issueNumber}` : undefined; + const policySource = group === 'approval-policy' && (evidence === 'repository' || evidence === 'organization' || evidence === 'default-off') + ? message(`source.${evidence}`) : undefined; + const mode = group === 'approval-prerequisites' && (evidence === 'off' || evidence === 'recommend' || evidence === 'guarded') + ? `${message('mode')}: ${evidence}` : undefined; + lines.push(`- **${message(`group.${group}`)}:** ${message(`state.${state}`)}${policySource ? ` · ${policySource}` : ''}${mode ? ` · ${mode}` : ''}${link ? ` · [${message('source')}](${link})` : ''}`); + } + if (source) + lines.push('', `[${message('source')}](${source})`); + return lines.join('\n'); +} +function repositorySource(value) { + if (!value.serverOrigin || !value.revision || !/^[a-f0-9]{40}$/u.test(value.revision) + || !value.owner || !value.repository || !/^[A-Za-z0-9][A-Za-z0-9-]{0,38}$/u.test(value.owner) + || !/^[A-Za-z0-9][A-Za-z0-9_.-]{0,99}$/u.test(value.repository)) + return undefined; + try { + const url = new URL(value.serverOrigin); + if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash + || url.pathname !== '/' || url.origin !== value.serverOrigin.replace(/\/$/u, '')) + return undefined; + return `${url.origin}/${value.owner}/${value.repository}/tree/${value.revision}`; + } + catch { + return undefined; + } +} + + /***/ }), /***/ 30203: @@ -45521,6 +45940,7 @@ const PUBLICATION_SURFACE_MESSAGE_IDS = Object.freeze([ 'interaction.help.command.recheck', 'interaction.help.command.description', 'interaction.help.command.status', + 'interaction.help.command.doctor', 'interaction.help.command.history', 'interaction.history.fixPushed', 'interaction.history.fixNoChanges', @@ -45571,6 +45991,41 @@ const PUBLICATION_SURFACE_MESSAGE_IDS = Object.freeze([ 'interaction.branchSync.merged', 'interaction.branchSync.agentResolution', 'interaction.branchSync.verification', + 'interaction.doctor.source.repository', + 'interaction.doctor.source.organization', + 'interaction.doctor.source.default-off', + 'interaction.doctor.mode', + 'interaction.doctor.heading', + 'interaction.doctor.action', + 'interaction.doctor.checks', + 'interaction.doctor.readonly', + 'interaction.doctor.source', + 'interaction.doctor.group.effective-configuration', + 'interaction.doctor.group.issue-readiness', + 'interaction.doctor.group.linked-branch-readiness', + 'interaction.doctor.group.workflow-contracts', + 'interaction.doctor.group.approval-policy', + 'interaction.doctor.group.approval-prerequisites', + 'interaction.doctor.group.pull-request-identity', + 'interaction.doctor.action.configuration', + 'interaction.doctor.action.workflow', + 'interaction.doctor.action.retry', + 'interaction.doctor.action.issue', + 'interaction.doctor.action.specifications', + 'interaction.doctor.action.branch', + 'interaction.doctor.action.policy', + 'interaction.doctor.action.declared', + 'interaction.doctor.action.none', + 'interaction.doctor.state.pass', + 'interaction.doctor.summary.pass', + 'interaction.doctor.state.blocked', + 'interaction.doctor.summary.blocked', + 'interaction.doctor.state.unavailable', + 'interaction.doctor.summary.unavailable', + 'interaction.doctor.state.warning', + 'interaction.doctor.summary.warning', + 'interaction.doctor.state.not-applicable', + 'interaction.doctor.summary.not-applicable', 'interaction.status.heading', 'interaction.status.repository', 'interaction.status.target', @@ -45664,6 +46119,7 @@ const ENGLISH_MESSAGES = Object.freeze({ 'interaction.help.command.recheck': 're-run the review and reconcile findings.', 'interaction.help.command.description': 'refresh the pull-request description.', 'interaction.help.command.status': 'show the current automation status.', + 'interaction.help.command.doctor': 'check available runtime prerequisites without making repository changes.', 'interaction.help.command.history': 'show up to ten supported operations and their verified receipts.', 'interaction.history.fixPushed': 'Fix changes were pushed. Findings require an independent review of the pushed revision.', 'interaction.history.fixNoChanges': 'The fix produced no changes to push.', @@ -45726,6 +46182,41 @@ const ENGLISH_MESSAGES = Object.freeze({ one: '{count} verification check passed.', other: '{count} verification checks passed.', }), + 'interaction.doctor.source.repository': "Repository variable", + 'interaction.doctor.source.organization': "Organization variable", + 'interaction.doctor.source.default-off': "Verified absence; default off", + 'interaction.doctor.mode': "Mode", + 'interaction.doctor.heading': "Runtime doctor", + 'interaction.doctor.action': "Next action", + 'interaction.doctor.checks': "Checks", + 'interaction.doctor.readonly': "This command reads evidence only. Declared approval settings do not prove producer health or write access.", + 'interaction.doctor.source': "Inspected repository revision", + 'interaction.doctor.group.effective-configuration': "Effective configuration", + 'interaction.doctor.group.issue-readiness': "Issue and SDD prerequisites", + 'interaction.doctor.group.linked-branch-readiness': "Native linked branch", + 'interaction.doctor.group.workflow-contracts': "Installed workflow structure", + 'interaction.doctor.group.approval-policy': "Trusted approval policy read", + 'interaction.doctor.group.approval-prerequisites': "Declared approval prerequisites", + 'interaction.doctor.group.pull-request-identity': "Exact current PR identity", + 'interaction.doctor.action.configuration': "Inspect the effective workflow configuration and correct the invalid setting.", + 'interaction.doctor.action.workflow': "Inspect the linked workflow revision and correct its required structure.", + 'interaction.doctor.action.retry': "Inspect this workflow run and retry /copilot doctor.", + 'interaction.doctor.action.issue': "Inspect the issue form and its routing labels.", + 'interaction.doctor.action.specifications': "Inspect the catalog, template and specification standard at the linked revision.", + 'interaction.doctor.action.branch': "Inspect the issue’s native linked branch and retry after branch readiness.", + 'interaction.doctor.action.policy': "Inspect the trusted Actions Variables approval policy source.", + 'interaction.doctor.action.declared': "Verify the declared producer prerequisites using the approval setup documentation.", + 'interaction.doctor.action.none': "No action required for the inspected prerequisites.", + 'interaction.doctor.state.pass': "Pass", + 'interaction.doctor.summary.pass': "The inspected applicable prerequisites passed.", + 'interaction.doctor.state.blocked': "Blocked", + 'interaction.doctor.summary.blocked': "An authoritative read established an invalid required prerequisite.", + 'interaction.doctor.state.unavailable': "Unavailable", + 'interaction.doctor.summary.unavailable': "Some prerequisites could not be verified; completed checks remain visible.", + 'interaction.doctor.state.warning': "Warning", + 'interaction.doctor.summary.warning': "A prerequisite needs attention or independent verification.", + 'interaction.doctor.state.not-applicable': "Not applicable", + 'interaction.doctor.summary.not-applicable': "The inspected prerequisites do not apply to this target.", 'interaction.status.heading': 'Copilot status', 'interaction.status.repository': 'Repository', 'interaction.status.target': 'Target', @@ -45816,6 +46307,7 @@ const SPANISH_MESSAGES = Object.freeze({ 'interaction.help.command.recheck': 'repite la revisión y reconcilia los hallazgos.', 'interaction.help.command.description': 'actualiza la descripción del pull request.', 'interaction.help.command.status': 'muestra el estado actual de la automatización.', + 'interaction.help.command.doctor': 'comprueba los requisitos disponibles del runtime sin modificar el repositorio.', 'interaction.help.command.history': 'muestra hasta diez operaciones compatibles y sus recibos verificables.', 'interaction.history.fixPushed': 'Se publicaron los cambios de la corrección. Los hallazgos requieren una revisión independiente de esa revisión.', 'interaction.history.fixNoChanges': 'La corrección no produjo cambios que publicar.', @@ -45882,6 +46374,41 @@ const SPANISH_MESSAGES = Object.freeze({ many: 'Se superaron {count} comprobaciones de verificación.', other: 'Se superaron {count} comprobaciones de verificación.', }), + 'interaction.doctor.source.repository': "Variable del repositorio", + 'interaction.doctor.source.organization': "Variable de la organización", + 'interaction.doctor.source.default-off': "Ausencia verificada; off por defecto", + 'interaction.doctor.mode': "Modo", + 'interaction.doctor.heading': "Diagnóstico del runtime", + 'interaction.doctor.action': "Siguiente acción", + 'interaction.doctor.checks': "Comprobaciones", + 'interaction.doctor.readonly': "Este comando solo lee evidencias. La configuración de aprobación declarada no acredita la salud de los productores ni el acceso de escritura.", + 'interaction.doctor.source': "Revisión inspeccionada del repositorio", + 'interaction.doctor.group.effective-configuration': "Configuración efectiva", + 'interaction.doctor.group.issue-readiness': "Requisitos de la issue y el SDD", + 'interaction.doctor.group.linked-branch-readiness': "Rama enlazada nativa", + 'interaction.doctor.group.workflow-contracts': "Estructura de los workflows instalados", + 'interaction.doctor.group.approval-policy': "Lectura confiable de la política de aprobación", + 'interaction.doctor.group.approval-prerequisites': "Requisitos de aprobación declarados", + 'interaction.doctor.group.pull-request-identity': "Identidad actual exacta del PR", + 'interaction.doctor.action.configuration': "Revisa la configuración efectiva del workflow y corrige el ajuste inválido.", + 'interaction.doctor.action.workflow': "Revisa la revisión enlazada de los workflows y corrige la estructura requerida.", + 'interaction.doctor.action.retry': "Revisa esta ejecución del workflow y vuelve a ejecutar /copilot doctor.", + 'interaction.doctor.action.issue': "Revisa el formulario de la issue y sus etiquetas de clasificación.", + 'interaction.doctor.action.specifications': "Revisa el catálogo, la plantilla y el estándar de especificaciones en la revisión enlazada.", + 'interaction.doctor.action.branch': "Revisa la rama enlazada nativa de la issue y reintenta cuando esté preparada.", + 'interaction.doctor.action.policy': "Revisa la fuente confiable de la política de aprobación en Actions Variables.", + 'interaction.doctor.action.declared': "Verifica los requisitos de los productores declarados siguiendo la documentación de aprobación.", + 'interaction.doctor.action.none': "No se requiere ninguna acción para los requisitos inspeccionados.", + 'interaction.doctor.state.pass': "Correcto", + 'interaction.doctor.summary.pass': "Los requisitos aplicables inspeccionados son correctos.", + 'interaction.doctor.state.blocked': "Bloqueado", + 'interaction.doctor.summary.blocked': "Una lectura autoritativa confirmó un requisito obligatorio inválido.", + 'interaction.doctor.state.unavailable': "No disponible", + 'interaction.doctor.summary.unavailable': "Algunos requisitos no se pudieron verificar; las comprobaciones completadas siguen visibles.", + 'interaction.doctor.state.warning': "Advertencia", + 'interaction.doctor.summary.warning': "Un requisito necesita atención o verificación independiente.", + 'interaction.doctor.state.not-applicable': "No aplicable", + 'interaction.doctor.summary.not-applicable': "Los requisitos inspeccionados no se aplican a este destino.", 'interaction.status.heading': 'Estado de Copilot', 'interaction.status.repository': 'Repositorio', 'interaction.status.target': 'Destino', @@ -47029,6 +47556,7 @@ exports.hasOwnedPrimaryIssuePublication = hasOwnedPrimaryIssuePublication; exports.selectSemanticReplyIntents = selectSemanticReplyIntents; exports.renderSemanticReply = renderSemanticReply; exports.renderSemanticStatus = renderSemanticStatus; +const github_runtime_doctor_presentation_policy_1 = __nccwpck_require__(2808); const copilot_operation_1 = __nccwpck_require__(40221); const copilot_history_presentation_policy_1 = __nccwpck_require__(55507); const copilot_history_projection_policy_1 = __nccwpck_require__(99186); @@ -47140,6 +47668,11 @@ function selectSemanticReplyIntents(context) { if (kind === 'access-policy') { return [replyIntent(context, correlationId, 'access-policy', Object.freeze({ kind }))]; } + if (result.id.endsWith('.Doctor') && (0, github_runtime_doctor_presentation_policy_1.isGithubRuntimeDoctorProjection)(payload.doctor)) { + return [replyIntent(context, correlationId, 'copilot-doctor', Object.freeze({ + kind: 'doctor-command', snapshot: payload.doctor, + }))]; + } const snapshot = (0, result_1.getResultPayload)(payload.status); if (result.id.endsWith('.Status') && isStatusSnapshot(snapshot)) { const projection = Object.freeze({ @@ -47225,6 +47758,8 @@ function renderReplyBody(intent, catalog) { if (intent.projection.kind === 'branch-sync-result') { return renderBranchSyncResult(intent.projection, catalog); } + if (intent.projection.kind === 'doctor-command') + return (0, github_runtime_doctor_presentation_policy_1.formatGithubRuntimeDoctor)(intent.projection.snapshot, intent.locale, catalog); return (0, status_command_policy_1.formatCopilotStatus)(intent.projection.snapshot, intent.locale, catalog); } function renderBranchSyncResult(projection, catalog) { @@ -49057,6 +49592,21 @@ exports.AGENT_EXECUTION_OUTPUT_DIAGNOSTICS = [ ]; +/***/ }), + +/***/ 64921: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.DOCTOR_REPOSITORY_PATHS = void 0; +exports.DOCTOR_REPOSITORY_PATHS = [ + '.github/workflows/copilot_issue.yml', '.github/workflows/copilot_issue_comment.yml', + '.github/workflows/copilot_pull_request.yml', 'specs/catalog.json', 'specs/_template.md', 'specs/README.md', +]; + + /***/ }), /***/ 6152: @@ -52990,6 +53540,7 @@ async function runDoUserRequestAction(param, options, intentPayload) { Object.defineProperty(exports, "__esModule", ({ value: true })); exports.runExplicitCommentCommand = runExplicitCommentCommand; exports.invalidCommentCommandResult = invalidCommentCommandResult; +const read_copilot_doctor_workflow_1 = __nccwpck_require__(12416); const read_copilot_history_workflow_1 = __nccwpck_require__(8140); const result_1 = __nccwpck_require__(73817); const read_copilot_status_workflow_1 = __nccwpck_require__(57206); @@ -53012,6 +53563,8 @@ async function runExplicitCommentCommand(param, options, command, actorAuthoriza })]; if (command.name === 'help') return runHelpCommand(param, options); + if (command.name === 'doctor') + return [await (0, read_copilot_doctor_workflow_1.readCopilotDoctor)(param, options.taskId, options.doctorReadPorts)]; if (command.name === 'status') return [await (0, read_copilot_status_workflow_1.readCopilotStatus)(param, options.taskId, options.statusReadPorts, options.sddQueryPort, options.nativeStatusReadPort)]; if (command.name === 'dismiss') @@ -53240,6 +53793,22 @@ const copilot_command_1 = __nccwpck_require__(11771); function projectCommentAutomationContext(source, language, userComment) { const review = (0, bugbot_review_operation_context_1.projectBugbotReviewOperationContext)(source); return Object.freeze({ + ...(source.issueWorkflowProfile && typeof source.preBranchSdd === 'boolean' + && typeof source.issue.issueManagedBranches === 'boolean' ? { doctorConfiguration: Object.freeze({ + profile: Object.freeze({ schemaVersion: source.issueWorkflowProfile.schemaVersion, + enabled: Object.freeze([...source.issueWorkflowProfile.enabled]) }), + labels: Object.freeze({ + feature: Object.freeze([source.labels?.feature, source.labels?.enhancement].filter((value) => typeof value === 'string' && value.length > 0)), + bugfix: Object.freeze([source.labels?.bugfix, source.labels?.bug].filter((value) => typeof value === 'string' && value.length > 0)), + documentation: Object.freeze([source.labels?.documentation, source.labels?.docs].filter((value) => typeof value === 'string' && value.length > 0)), + chore: Object.freeze([source.labels?.chore, source.labels?.maintenance].filter((value) => typeof value === 'string' && value.length > 0)), + help: Object.freeze([source.labels?.help, source.labels?.question].filter((value) => typeof value === 'string' && value.length > 0)), + release: Object.freeze([source.labels?.release].filter((value) => typeof value === 'string' && value.length > 0)), + hotfix: Object.freeze([source.labels?.hotfix].filter((value) => typeof value === 'string' && value.length > 0)), + }), + preBranchSdd: source.preBranchSdd, issueManagedBranches: source.issue.issueManagedBranches, + dependencyBranch: source.currentConfiguration.parentBranch ?? source.branches.development ?? '', + }) } : {}), actor: source.actor, trustedBotLogin: source.tokenUser?.trim() ?? '', userComment, @@ -53436,7 +54005,7 @@ async function runCommentAutomation(initialParam, options, actorAuthorizationPor return [(0, comment_automation_command_workflow_1.invalidCommentCommandResult)(options.taskId, command.reason)]; } const isPublicMetadataCommand = command.kind === 'command' - && (command.command.name === 'help' || command.command.name === 'status' || command.command.name === 'history'); + && (command.command.name === 'help' || command.command.name === 'status' || command.command.name === 'doctor' || command.command.name === 'history'); if (!isPublicMetadataCommand && param.membersOnly && !await actorAuthorizationPort.isActorAllowedToUseMemberOnlyAutomation(param.actor)) { (0, logging_ports_1.logInfo)('Skipping agent automation because ai-members-only is enabled and the actor is not authorized.'); @@ -54102,7 +54671,6 @@ function positiveIssueNumberOrUndefined(value) { Object.defineProperty(exports, "__esModule", ({ value: true })); exports.IssueCommentUseCase = void 0; const comment_automation_command_workflow_1 = __nccwpck_require__(63134); -const copilot_command_1 = __nccwpck_require__(11771); const read_copilot_history_workflow_1 = __nccwpck_require__(8140); const result_1 = __nccwpck_require__(73817); const comment_automation_use_case_1 = __nccwpck_require__(9661); @@ -54110,8 +54678,9 @@ const check_issue_comment_language_use_case_1 = __nccwpck_require__(93152); const comment_automation_context_1 = __nccwpck_require__(37055); const pull_request_workflow_context_1 = __nccwpck_require__(73447); const pre_branch_sdd_1 = __nccwpck_require__(34730); +const copilot_command_1 = __nccwpck_require__(11771); class IssueCommentUseCase { - constructor(languageUseCase, intentUseCase, thinkUseCase, autofixUseCase, doUserRequestUseCase, actorAuthorizationPort, bugbotGitMutationPort, dismissBugbotFindingsUseCase, reviewPotentialProblemsUseCase, updatePullRequestDescriptionUseCase, rememberBugbotRuleUseCase, syncBranchUseCase, preBranchSddContinuation, rememberBranchPorts, statusReadPorts, historyReadPort, sddQueryPort, nativeStatusReadPort) { + constructor(languageUseCase, intentUseCase, thinkUseCase, autofixUseCase, doUserRequestUseCase, actorAuthorizationPort, bugbotGitMutationPort, dismissBugbotFindingsUseCase, reviewPotentialProblemsUseCase, updatePullRequestDescriptionUseCase, rememberBugbotRuleUseCase, syncBranchUseCase, preBranchSddContinuation, rememberBranchPorts, statusReadPorts, historyReadPort, sddQueryPort, nativeStatusReadPort, doctorReadPorts) { this.languageUseCase = languageUseCase; this.intentUseCase = intentUseCase; this.thinkUseCase = thinkUseCase; @@ -54130,6 +54699,7 @@ class IssueCommentUseCase { this.historyReadPort = historyReadPort; this.sddQueryPort = sddQueryPort; this.nativeStatusReadPort = nativeStatusReadPort; + this.doctorReadPorts = doctorReadPorts; this.taskId = "IssueCommentUseCase"; } async invoke(param) { @@ -54143,13 +54713,14 @@ class IssueCommentUseCase { generalConversation: param.eventName === 'issue_comment', }, this.historyReadPort)]; } - if (param.preBranchSdd && !param.issue.issueManagedBranches) { + const isDoctor = historyCommand.kind === 'command' && historyCommand.command.name === 'doctor'; + if (!isDoctor && param.preBranchSdd && !param.issue.issueManagedBranches) { return [new result_1.Result({ id: this.taskId, success: false, executed: true, steps: ['pre-branch-sdd requires issue-managed-branches; correct the Action configuration.'], })]; } - if (this.preBranchSddContinuation + if (!isDoctor && this.preBranchSddContinuation && param.issueStartDecision.sddRequired && (0, pre_branch_sdd_1.isSddAnswerComment)(param.issue.commentBody) && param.issue.commentAuthor.toLowerCase() !== param.tokenUser?.toLowerCase()) { @@ -54177,6 +54748,7 @@ class IssueCommentUseCase { rememberBugbotRuleUseCase: this.rememberBugbotRuleUseCase, rememberBranchPorts: this.rememberBranchPorts, statusReadPorts: this.statusReadPorts, + doctorReadPorts: this.doctorReadPorts, nativeStatusReadPort: this.nativeStatusReadPort, sddQueryPort: this.sddQueryPort, syncBranchUseCase: this.syncBranchUseCase @@ -54972,7 +55544,7 @@ const check_pull_request_comment_language_use_case_1 = __nccwpck_require__(21729 const comment_automation_context_1 = __nccwpck_require__(37055); const pull_request_workflow_context_1 = __nccwpck_require__(73447); class PullRequestReviewCommentUseCase { - constructor(languageUseCase, intentUseCase, thinkUseCase, autofixUseCase, doUserRequestUseCase, actorAuthorizationPort, bugbotGitMutationPort, dismissBugbotFindingsUseCase, reviewPotentialProblemsUseCase, updatePullRequestDescriptionUseCase, rememberBugbotRuleUseCase, syncBranchUseCase, rememberBranchPorts, statusReadPorts, historyReadPort, nativeStatusReadPort) { + constructor(languageUseCase, intentUseCase, thinkUseCase, autofixUseCase, doUserRequestUseCase, actorAuthorizationPort, bugbotGitMutationPort, dismissBugbotFindingsUseCase, reviewPotentialProblemsUseCase, updatePullRequestDescriptionUseCase, rememberBugbotRuleUseCase, syncBranchUseCase, rememberBranchPorts, statusReadPorts, historyReadPort, nativeStatusReadPort, doctorReadPorts) { this.languageUseCase = languageUseCase; this.intentUseCase = intentUseCase; this.thinkUseCase = thinkUseCase; @@ -54989,6 +55561,7 @@ class PullRequestReviewCommentUseCase { this.statusReadPorts = statusReadPorts; this.historyReadPort = historyReadPort; this.nativeStatusReadPort = nativeStatusReadPort; + this.doctorReadPorts = doctorReadPorts; this.taskId = "PullRequestReviewCommentUseCase"; } async invoke(param) { @@ -55024,6 +55597,7 @@ class PullRequestReviewCommentUseCase { rememberBugbotRuleUseCase: this.rememberBugbotRuleUseCase, rememberBranchPorts: this.rememberBranchPorts, statusReadPorts: this.statusReadPorts, + doctorReadPorts: this.doctorReadPorts, nativeStatusReadPort: this.nativeStatusReadPort, syncBranchUseCase: this.syncBranchUseCase ? { invoke: (options) => this.syncBranchUseCase.invoke({ context: context.branchSync, options }) } @@ -55511,6 +56085,124 @@ function deepFreezeCopy(value) { } +/***/ }), + +/***/ 12416: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.readCopilotDoctor = readCopilotDoctor; +const result_1 = __nccwpck_require__(73817); +const github_runtime_doctor_1 = __nccwpck_require__(20413); +const issue_workflow_profile_1 = __nccwpck_require__(26744); +const issue_start_policy_1 = __nccwpck_require__(90332); +const positive_integer_policy_1 = __nccwpck_require__(19879); +const context_1 = __nccwpck_require__(14712); +const github_runtime_doctor_policy_1 = __nccwpck_require__(99566); +const github_runtime_doctor_presentation_policy_1 = __nccwpck_require__(2808); +const bugbot_context_request_1 = __nccwpck_require__(98299); +async function readCopilotDoctor(param, taskId, ports) { + const repositoryId = (0, positive_integer_policy_1.parsePositiveSafeInteger)(param.bugbot.review.repository.id); + const isPull = param.status.target === 'pull-request'; + const number = (0, positive_integer_policy_1.parsePositiveSafeInteger)(isPull ? param.status.pullRequestNumber : param.status.issueNumber); + if (!repositoryId || !number) + return doctorResult(taskId, undefined); + const target = isPull ? { kind: 'pull-request', repositoryId, pullNumber: number } + : { kind: 'issue', repositoryId, issueNumber: number }; + const checks = []; + const put = (group, state, action) => checks.push(Object.freeze({ group, state, evidence: { source: group }, ...(action && state !== 'pass' && state !== 'not-applicable' ? { action: state === 'unavailable' ? 'retry' : action } : {}) })); + const configuration = param.doctorConfiguration; + const valid = (0, github_runtime_doctor_policy_1.validDoctorConfiguration)(configuration); + put('effective-configuration', configuration === undefined ? 'unavailable' : valid ? 'pass' : 'blocked', !valid ? 'configuration' : undefined); + const repositoryRead = ports ? await ports.readRepository(repositoryId).catch(() => ({ state: 'unavailable' })) + : { state: 'unavailable' }; + const verifiedRead = repositoryRead.state === 'available' + && (repositoryRead.repositoryId !== repositoryId || !/^[a-f0-9]{40}$/u.test(repositoryRead.revision)) + ? { state: 'unavailable' } : repositoryRead; + put('workflow-contracts', (0, github_runtime_doctor_policy_1.doctorWorkflowContracts)(verifiedRead), 'workflow'); + if (isPull) { + // Optional issue context is not guessed from a PR number or event prose. + put('issue-readiness', 'not-applicable'); + put('linked-branch-readiness', 'not-applicable'); + } + else if (!valid || !ports) { + put('issue-readiness', 'unavailable', 'retry'); + put('linked-branch-readiness', 'unavailable', 'retry'); + } + else { + try { + const issue = await ports.readIssue(number); + if (issue.number !== number) + throw new Error('Wrong issue identity.'); + const kindAdmission = (0, issue_workflow_profile_1.classifyIssueWorkflow)(issue.labels, configuration.profile, configuration.labels, undefined, false); + // Form completeness cannot make branchless help work require an SDD. + // Disabled or conflicting kinds still retain their admission block. + const admission = kindAdmission.status === 'eligible' && kindAdmission.kind === 'help' ? kindAdmission + : (0, issue_workflow_profile_1.classifyIssueWorkflow)(issue.labels, configuration.profile, configuration.labels, issue.body); + if (admission.status !== 'eligible') { + put('issue-readiness', admission.status === 'unmanaged' ? 'unavailable' : 'blocked', 'issue'); + put('linked-branch-readiness', 'not-applicable'); + } + else { + const decision = (0, issue_start_policy_1.decideIssueStart)({ kind: admission.kind, labels: issue.labels, + issueManagedBranches: configuration.issueManagedBranches, preBranchSdd: configuration.preBranchSdd }); + put('issue-readiness', decision.sddRequired ? (0, github_runtime_doctor_policy_1.doctorRepositoryPrerequisites)(verifiedRead) : 'not-applicable', 'specifications'); + if (!decision.branchRequired) + put('linked-branch-readiness', 'not-applicable'); + else { + const branch = await ports.readLinkedBranch(number).catch(() => ({ state: 'unavailable' })); + const state = branch.state === 'available' && branch.repositoryId === repositoryId && /^[a-f0-9]{40}$/u.test(branch.headSha) + ? 'pass' : branch.state === 'absent' ? 'warning' : 'unavailable'; + put('linked-branch-readiness', state, 'branch'); + } + } + } + catch { + put('issue-readiness', 'unavailable', 'retry'); + put('linked-branch-readiness', 'unavailable', 'retry'); + } + } + const policyRead = ports ? await ports.readApprovalPolicy().catch(() => ({ status: 'unavailable' })) + : { status: 'unavailable' }; + const policy = (0, github_runtime_doctor_1.parseTrustedDoctorApprovalPolicy)(policyRead); + put('approval-policy', policy.status === 'valid' ? 'pass' : policy.status === 'invalid' ? 'blocked' : 'unavailable', 'policy'); + if (policy.status === 'valid' || policyRead.status === 'available') + checks[checks.length - 1] = Object.freeze({ ...checks[checks.length - 1], + evidence: { source: policyRead.status === 'available' ? policyRead.source : policy.status === 'valid' ? policy.source : 'unavailable' } }); + put('approval-prerequisites', policy.status !== 'valid' ? 'unavailable' : policy.policy.mode === 'off' ? 'not-applicable' : 'warning', 'declared'); + if (policy.status === 'valid') + checks[checks.length - 1] = Object.freeze({ ...checks[checks.length - 1], evidence: { source: policy.policy.mode } }); + if (!isPull) + put('pull-request-identity', 'not-applicable'); + else { + let state = 'unavailable'; + try { + if (ports) { + const request = (0, bugbot_context_request_1.projectBugbotContextRequest)(param.bugbot.review); + const selection = request.target.pullRequestSelection; + if (selection.kind === 'event' && selection.number === number) { + const canonical = (0, context_1.selectCanonicalBugbotPullRequest)(request.target, [await ports.pullRequests.getPullRequest(number)], 'event'); + if (canonical.kind === 'canonical' && /^[a-f0-9]{40}$/u.test(canonical.pullRequest.headSha) + && await ports.pullRequests.getPullRequestHeadSha(number) === canonical.pullRequest.headSha) + state = 'pass'; + } + } + } + catch { /* Independent facts remain available. */ } + put('pull-request-identity', state, 'retry'); + } + const report = (0, github_runtime_doctor_1.aggregateGithubRuntimeDoctorReport)({ target, checks }); + return doctorResult(taskId, report, ports?.serverOrigin, verifiedRead.state === 'available' ? verifiedRead.revision : undefined, param.status.owner, param.status.repository); +} +function doctorResult(taskId, report, serverOrigin, revision, owner, repository) { + const projection = Object.freeze({ report, serverOrigin, revision, owner, repository }); + return new result_1.Result({ id: `${taskId}.Doctor`, success: true, executed: true, stepFormat: 'markdown', + steps: [(0, github_runtime_doctor_presentation_policy_1.formatGithubRuntimeDoctor)(projection)], payload: { doctor: projection } }); +} + + /***/ }), /***/ 8140: @@ -71158,6 +71850,185 @@ function requireObject(data, operation) { } +/***/ }), + +/***/ 25282: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.RuntimeDoctorRepository = void 0; +const github = __importStar(__nccwpck_require__(78227)); +const github_runtime_doctor_ports_1 = __nccwpck_require__(64921); +const pull_request_approval_repository_1 = __nccwpck_require__(23001); +const SHA = /^[a-f0-9]{40}$/u; +const github_runtime_doctor_1 = __nccwpck_require__(20413); +/** SDK DTOs and diagnostics terminate here; no mutation, provisioning, or filesystem API. */ +class RuntimeDoctorRepository { + constructor(owner, repository, token) { + this.owner = owner; + this.repository = repository; + this.client = github.getOctokit(token); + } + async readRepository(repositoryId) { + try { + const repo = await this.client.rest.repos.get({ owner: this.owner, repo: this.repository }); + if (repo.data.id !== repositoryId || !(0, github_runtime_doctor_1.isDoctorBranchReference)(repo.data.default_branch)) + return { state: 'unavailable' }; + const ref = await this.client.rest.git.getRef({ owner: this.owner, repo: this.repository, ref: `heads/${repo.data.default_branch}` }); + const revision = ref.data.object.sha; + if (!SHA.test(revision) || ref.data.object.type !== 'commit') + return { state: 'unavailable' }; + const trackedPaths = this.readTrackedPaths(revision); + const pathType = (path) => trackedPaths.then(paths => paths.get(path)); + const [values, inventory] = await Promise.all([ + Promise.all(github_runtime_doctor_ports_1.DOCTOR_REPOSITORY_PATHS.map(async (path) => [path, await this.readFile(path, revision, pathType)])), trackedPaths, + ]); + const final = await this.client.rest.git.getRef({ owner: this.owner, repo: this.repository, ref: `heads/${repo.data.default_branch}` }); + if (final.data.object.type !== 'commit' || final.data.object.sha !== revision) + return { state: 'unavailable' }; + return Object.freeze({ state: 'available', repositoryId, revision, + trackedFiles: Object.freeze([...inventory].filter(([, type]) => type === 'file').map(([path]) => path)), + files: Object.freeze(Object.fromEntries(values)) }); + } + catch { + return { state: 'unavailable' }; + } + } + async readFile(path, revision, pathType) { + try { + const response = await this.client.rest.repos.getContent({ owner: this.owner, repo: this.repository, path, ref: revision }); + const data = response.data; + if (Array.isArray(data) || data.type !== 'file' || data.path !== path || !('content' in data) + || data.encoding !== 'base64' || typeof data.content !== 'string' || data.content.length > 300000 + || !/^[A-Za-z0-9+/=\r\n]*$/u.test(data.content)) + throw new Error('Malformed bounded file response.'); + if (await pathType(path) !== 'file') + throw new Error('The pinned path is not a regular repository file.'); + const bytes = Buffer.from(data.content, 'base64'); + const text = bytes.toString('utf8'); + if (bytes.toString('base64') !== data.content.replace(/[\r\n]/gu, '') + || bytes.length > 200000 || !Buffer.from(text, 'utf8').equals(bytes)) + throw new Error('Malformed bounded file content.'); + return Object.freeze({ state: 'present', text }); + } + catch (error) { + if (status(error) === 404 && (await pathType(path)) === undefined) + return Object.freeze({ state: 'missing' }); + throw error; + } + } + /** A content 404 alone is ambiguous. Only a complete immutable tree proves absence. */ + async readTrackedPaths(revision) { + const commit = await this.client.rest.git.getCommit({ owner: this.owner, repo: this.repository, commit_sha: revision }); + if (commit.data.sha !== revision || !SHA.test(commit.data.tree.sha)) + throw new Error('Unverified repository tree identity.'); + const response = await this.client.rest.git.getTree({ owner: this.owner, repo: this.repository, tree_sha: commit.data.tree.sha, recursive: '1' }); + const tree = response.data; + if (tree.sha !== commit.data.tree.sha || tree.truncated !== false || !Array.isArray(tree.tree) || tree.tree.length > 10000) + throw new Error('Incomplete bounded repository tree.'); + const paths = new Map(); + for (const entry of tree.tree) { + const path = entry.path; + if (typeof path !== 'string' || !path || path.length > 1024 || path.startsWith('/') || path.includes('\\') + || Array.from(path).some(character => character.charCodeAt(0) < 32) || path.split('/').includes('..') || paths.has(path) + || !['blob', 'tree', 'commit'].includes(entry.type ?? '') || !SHA.test(entry.sha ?? '')) + throw new Error('Malformed repository tree evidence.'); + paths.set(path, entry.type === 'blob' && ['100644', '100755'].includes(entry.mode ?? '') ? 'file' : 'other'); + } + return paths; + } + async readIssue(issueNumber) { + const response = await this.client.rest.issues.get({ owner: this.owner, repo: this.repository, issue_number: issueNumber }); + const issue = response.data; + if (issue.number !== issueNumber || issue.pull_request || (issue.body !== null && typeof issue.body !== 'string') || (issue.body?.length ?? 0) > 70000 + || typeof issue.title !== 'string' || issue.title.length > 256 || !Array.isArray(issue.labels) || issue.labels.length > 100) + throw new Error('Malformed bounded issue response.'); + const labels = issue.labels.map((label) => typeof label === 'string' ? label : label.name); + if (labels.some((label) => typeof label !== 'string' || label.length > 120)) + throw new Error('Malformed issue labels.'); + return Object.freeze({ number: issue.number, title: issue.title, body: issue.body ?? '', labels: Object.freeze(labels) }); + } + async readLinkedBranch(issueNumber) { + try { + const result = await this.client.graphql(`query RuntimeDoctorLinkedBranch($owner:String!,$repo:String!,$number:Int!){repository(owner:$owner,name:$repo){ + databaseId nameWithOwner issue(number:$number){number linkedBranches(first:100){ + pageInfo{hasNextPage} nodes{ref{name target{oid} repository{databaseId nameWithOwner}}}}}}}`, { owner: this.owner, repo: this.repository, number: issueNumber }); + const repo = result.repository; + const links = repo?.issue?.linkedBranches; + const expected = `${this.owner}/${this.repository}`.toLowerCase(); + if (!repo || !Number.isSafeInteger(repo.databaseId) || (repo.databaseId ?? 0) <= 0 || repo.nameWithOwner?.toLowerCase() !== expected + || repo.issue?.number !== issueNumber || links?.pageInfo?.hasNextPage !== false || !Array.isArray(links.nodes)) + return { state: 'unavailable' }; + const refs = links.nodes.flatMap((node) => node?.ref ? [node.ref] : []); + if (refs.length !== links.nodes.length) + return { state: 'unavailable' }; + if (refs.length === 0) + return { state: 'absent' }; + if (refs.length !== 1) + return { state: 'unavailable' }; + const branch = refs[0]; + if (!branch.name || !(0, github_runtime_doctor_1.isDoctorBranchReference)(branch.name) || !branch.target?.oid || !SHA.test(branch.target.oid) + || branch.repository?.databaseId !== repo.databaseId || branch.repository?.nameWithOwner?.toLowerCase() !== expected) + return { state: 'unavailable' }; + const live = await this.client.rest.git.getRef({ owner: this.owner, repo: this.repository, ref: `heads/${branch.name}` }); + return live.data.object.type === 'commit' && live.data.object.sha === branch.target.oid + ? Object.freeze({ state: 'available', repositoryId: repo.databaseId, name: branch.name, headSha: branch.target.oid }) : { state: 'unavailable' }; + } + catch { + return { state: 'unavailable' }; + } + } + async readApprovalPolicy() { + try { + const value = await (0, pull_request_approval_repository_1.readEffectiveApprovalPolicy)(this.client, { owner: this.owner, repository: this.repository }); + return value ? Object.freeze({ status: 'available', ...value }) : Object.freeze({ status: 'absent' }); + } + catch { + return Object.freeze({ status: 'unavailable' }); + } + } +} +exports.RuntimeDoctorRepository = RuntimeDoctorRepository; +function status(error) { + return error && typeof error === 'object' && 'status' in error && typeof error.status === 'number' ? error.status : undefined; +} + + /***/ }), /***/ 52644: @@ -73345,6 +74216,7 @@ var __importStar = (this && this.__importStar) || (function () { })(); Object.defineProperty(exports, "__esModule", ({ value: true })); exports.PullRequestApprovalRepository = void 0; +exports.readEffectiveApprovalPolicy = readEffectiveApprovalPolicy; const logging_ports_1 = __nccwpck_require__(6152); const github = __importStar(__nccwpck_require__(78227)); const pull_request_approval_policy_1 = __nccwpck_require__(98820); @@ -73359,33 +74231,22 @@ class PullRequestApprovalRepository { this.octokit = github.getOctokit(token); this.checkOctokit = settings.githubToken ? github.getOctokit(settings.githubToken) : undefined; } - async readPolicy(target) { + /** Read-only source evidence; failure never consults an injected policy value. */ + async readPolicyEvidence(target) { try { - const response = await this.octokit.rest.actions.getRepoVariable({ - owner: target.owner, repo: target.repository, name: 'PR_APPROVAL_POLICY', - }); - return response.data.value; + const read = await this.readEffectivePolicy(target); + return Object.freeze(read ? { status: 'available', ...read } : { status: 'absent' }); } - catch (error) { - if (statusOf(error) !== 404) { - reportPolicyReadFailure('repository-variable', error); - throw error; - } - } - // A repository Variable takes precedence over an accessible organization Variable. - try { - const response = await this.octokit.rest.actions.getOrgVariable({ - org: target.owner, name: 'PR_APPROVAL_POLICY', - }); - return response.data.value; - } - catch (error) { - if (statusOf(error) === 404) - return undefined; - reportPolicyReadFailure('organization-variable', error); - throw error; + catch { + return Object.freeze({ status: 'unavailable' }); } } + async readPolicy(target) { + return (await this.readEffectivePolicy(target))?.value; + } + async readEffectivePolicy(target) { + return readEffectiveApprovalPolicy(this.octokit, target); + } async loadEvidence(target) { const { data: pull } = await this.octokit.rest.pulls.get({ owner: target.owner, repo: target.repository, pull_number: target.pullNumber, @@ -73711,6 +74572,34 @@ function record(value) { return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined; } function array(value) { return Array.isArray(value) ? value : []; } +/** Canonical repository-over-organization read shared with the physically read-only doctor binding. */ +async function readEffectiveApprovalPolicy(octokit, target) { + const admit = (source, value) => { + if (typeof value !== 'string' || value.length > 65536) + throw new Error('Approval policy response is malformed or unbounded.'); + return { source, value }; + }; + try { + const response = await octokit.rest.actions.getRepoVariable({ owner: target.owner, repo: target.repository, name: 'PR_APPROVAL_POLICY' }); + return admit('repository', response.data.value); + } + catch (error) { + if (statusOf(error) !== 404) { + reportPolicyReadFailure('repository-variable', error); + throw error; + } + } + try { + const response = await octokit.rest.actions.getOrgVariable({ org: target.owner, name: 'PR_APPROVAL_POLICY' }); + return admit('organization', response.data.value); + } + catch (error) { + if (statusOf(error) === 404) + return undefined; + reportPolicyReadFailure('organization-variable', error); + throw error; + } +} /***/ }), @@ -76306,6 +77195,7 @@ exports.COPILOT_COMMAND_NAMES = [ 'estimate', 'test-plan', 'status', + 'doctor', 'history', 'description', 'explain', @@ -76365,6 +77255,9 @@ function parseCopilotCommand(raw) { && name !== 'remember') { return { kind: 'invalid', reason: `Copilot commands accept at most ${MAX_ARGUMENTS} arguments.` }; } + if (name === 'doctor' && argumentsList.length > 0) { + return { kind: 'invalid', reason: '/copilot doctor does not accept arguments.' }; + } if ((name === 'fix' || name === 'dismiss' || name === 'implement' || name === 'remember') && argumentsList.length === 0) { return { kind: 'invalid', reason: `/${name} requires at least one argument.` }; } @@ -77162,6 +78055,85 @@ function publicationTargetToken(target) { } +/***/ }), + +/***/ 20413: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.GITHUB_RUNTIME_DOCTOR_GROUPS = void 0; +exports.parseTrustedDoctorApprovalPolicy = parseTrustedDoctorApprovalPolicy; +exports.aggregateGithubRuntimeDoctorReport = aggregateGithubRuntimeDoctorReport; +exports.isDoctorBranchReference = isDoctorBranchReference; +const pull_request_approval_policy_1 = __nccwpck_require__(98820); +// Domain contracts and trusted policy parsing for the issue/PR runtime doctor. +/** The fixed, ordered evidence groups in the GitHub runtime doctor contract. */ +exports.GITHUB_RUNTIME_DOCTOR_GROUPS = [ + 'effective-configuration', + 'issue-readiness', + 'linked-branch-readiness', + 'workflow-contracts', + 'approval-policy', + 'approval-prerequisites', + 'pull-request-identity', +]; +/** + * Parses only a successful trusted effective read. Verified absence preserves the + * established off default; read failure is unavailable and never consults an injected value. + */ +function parseTrustedDoctorApprovalPolicy(read) { + if (read.status === 'unavailable') + return { status: 'unavailable' }; + if (read.status === 'absent') { + return { status: 'valid', source: 'default-off', policy: pull_request_approval_policy_1.DISABLED_PULL_REQUEST_APPROVAL_POLICY }; + } + if (typeof read.value !== 'string') + return { status: 'unavailable' }; + if (!read.value.trim()) + return { status: 'invalid' }; + try { + return { status: 'valid', source: read.source, policy: (0, pull_request_approval_policy_1.parsePullRequestApprovalPolicy)(read.value) }; + } + catch { + // Do not leak parser/provider text into a report or user-facing surface. + return { status: 'invalid' }; + } +} +/** Aggregate explicit independent evidence. Missing groups are unavailable, never assumed healthy. */ +function aggregateGithubRuntimeDoctorReport(input) { + const byGroup = new Map(); + for (const check of input.checks) { + if (!exports.GITHUB_RUNTIME_DOCTOR_GROUPS.includes(check.group)) + throw new Error('Unknown GitHub runtime doctor group.'); + if (byGroup.has(check.group)) + throw new Error(`Duplicate GitHub runtime doctor group: ${check.group}.`); + byGroup.set(check.group, Object.freeze({ ...check, ...(check.evidence ? { evidence: Object.freeze({ ...check.evidence }) } : {}) })); + } + const checks = exports.GITHUB_RUNTIME_DOCTOR_GROUPS.map(group => byGroup.get(group) + ?? Object.freeze({ group, state: 'unavailable' })); + const priority = ['blocked', 'unavailable', 'warning', 'pass']; + const applicable = checks.filter(check => check.state !== 'not-applicable'); + const severity = applicable.length === 0 ? 'not-applicable' + : priority.find(state => applicable.some(check => check.state === state)) ?? 'unavailable'; + const primaryAction = applicable.find(check => check.state === severity && check.action)?.action; + return Object.freeze({ + target: Object.freeze({ ...input.target }), + checks: Object.freeze(checks), + severity, + ...(primaryAction ? { primaryAction } : {}), + }); +} +/** Bounded literal Git branch names: never revisions, options or reflog expressions. */ +function isDoctorBranchReference(value) { + return typeof value === 'string' + && /^(?!-|.*\.\.)[A-Za-z0-9._/-]{1,200}$/u.test(value) + && value.split('/').every(part => part.length > 0 && !part.startsWith('.') + && !part.endsWith('.') && !part.endsWith('.lock')); +} + + /***/ }), /***/ 84403: @@ -81207,6 +82179,29 @@ const createRepositoryVersionTagsClient = () => new octokit_release_adapters_1.O exports.createRepositoryVersionTagsClient = createRepositoryVersionTagsClient; +/***/ }), + +/***/ 36459: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.createGithubRuntimeDoctorPorts = createGithubRuntimeDoctorPorts; +const runtime_doctor_repository_1 = __nccwpck_require__(25282); +function createGithubRuntimeDoctorPorts(binding, pullRequests) { + const repository = new runtime_doctor_repository_1.RuntimeDoctorRepository(binding.owner, binding.repository, binding.token); + return Object.freeze({ serverOrigin: process.env.GITHUB_SERVER_URL ?? 'https://github.com', + readRepository: (id) => repository.readRepository(id), + readIssue: (number) => repository.readIssue(number), + readLinkedBranch: (number) => repository.readLinkedBranch(number), + readApprovalPolicy: () => repository.readApprovalPolicy(), + pullRequests: Object.freeze({ getPullRequest: (number) => pullRequests.getPullRequest(number), + getPullRequestHeadSha: (number) => pullRequests.getPullRequestHeadSha(number) }), + }); +} + + /***/ }), /***/ 29839: @@ -81672,6 +82667,7 @@ exports.createPullRequestReviewCommentUseCaseCompositionRoot = createPullRequest exports.createCommitUseCaseCompositionRoot = createCommitUseCaseCompositionRoot; exports.createMainRunRouteCompositionRoot = createMainRunRouteCompositionRoot; exports.createClosedIssueRetirementUseCase = createClosedIssueRetirementUseCase; +const github_runtime_doctor_composition_root_1 = __nccwpck_require__(36459); const reply_request_composition_root_1 = __nccwpck_require__(99065); const retire_branch_sync_use_case_1 = __nccwpck_require__(74111); const issue_completion_composition_root_1 = __nccwpck_require__(63719); @@ -81794,7 +82790,7 @@ function createIssueCommentUseCaseCompositionRoot(binding) { contextPorts: bugbot.scm.context, resolutionPorts: bugbot.scm.resolution, catalogResolver: new resolve_message_catalog_use_case_1.ResolveMessageCatalogUseCase(language), - }), new detect_potential_problems_use_case_1.DetectPotentialProblemsUseCase(findings, bugbot.scm, bugbot.telemetry, new resolve_message_catalog_use_case_1.ResolveMessageCatalogUseCase(language)), pullRequestDescription, new remember_bugbot_rule_use_case_1.RememberBugbotRuleUseCase(bugbot.rules), branchSync, (0, issue_use_case_composition_root_1.createIssueUseCaseCompositionRoot)(binding), rememberBranchPorts(binding, bugbot.scm.context), bugbot.scm.context, (0, copilot_history_port_factory_1.createCopilotHistoryReadPort)(bugbot.scm.context, process.env.GITHUB_SERVER_URL ?? 'https://github.com'), (0, sdd_evidence_composition_root_1.createSddEvidenceQueryPort)(binding, bugbot.scm.context), (0, issue_completion_composition_root_1.createNativeStatusReadPort)(binding)); + }), new detect_potential_problems_use_case_1.DetectPotentialProblemsUseCase(findings, bugbot.scm, bugbot.telemetry, new resolve_message_catalog_use_case_1.ResolveMessageCatalogUseCase(language)), pullRequestDescription, new remember_bugbot_rule_use_case_1.RememberBugbotRuleUseCase(bugbot.rules), branchSync, (0, issue_use_case_composition_root_1.createIssueUseCaseCompositionRoot)(binding), rememberBranchPorts(binding, bugbot.scm.context), bugbot.scm.context, (0, copilot_history_port_factory_1.createCopilotHistoryReadPort)(bugbot.scm.context, process.env.GITHUB_SERVER_URL ?? 'https://github.com'), (0, sdd_evidence_composition_root_1.createSddEvidenceQueryPort)(binding, bugbot.scm.context), (0, issue_completion_composition_root_1.createNativeStatusReadPort)(binding), (0, github_runtime_doctor_composition_root_1.createGithubRuntimeDoctorPorts)(binding, bugbot.scm.context)); } function createPullRequestReviewCommentUseCaseCompositionRoot(binding) { const bugbot = (0, bugbot_composition_root_1.createBugbotCompositionRoot)(binding); @@ -81810,7 +82806,7 @@ function createPullRequestReviewCommentUseCaseCompositionRoot(binding) { contextPorts: bugbot.scm.context, resolutionPorts: bugbot.scm.resolution, catalogResolver: new resolve_message_catalog_use_case_1.ResolveMessageCatalogUseCase(language), - }), new detect_potential_problems_use_case_1.DetectPotentialProblemsUseCase(findings, bugbot.scm, bugbot.telemetry, new resolve_message_catalog_use_case_1.ResolveMessageCatalogUseCase(language)), pullRequestDescription, new remember_bugbot_rule_use_case_1.RememberBugbotRuleUseCase(bugbot.rules), branchSync, rememberBranchPorts(binding, bugbot.scm.context), bugbot.scm.context, (0, copilot_history_port_factory_1.createCopilotHistoryReadPort)(bugbot.scm.context, process.env.GITHUB_SERVER_URL ?? 'https://github.com'), (0, issue_completion_composition_root_1.createNativeStatusReadPort)(binding)); + }), new detect_potential_problems_use_case_1.DetectPotentialProblemsUseCase(findings, bugbot.scm, bugbot.telemetry, new resolve_message_catalog_use_case_1.ResolveMessageCatalogUseCase(language)), pullRequestDescription, new remember_bugbot_rule_use_case_1.RememberBugbotRuleUseCase(bugbot.rules), branchSync, rememberBranchPorts(binding, bugbot.scm.context), bugbot.scm.context, (0, copilot_history_port_factory_1.createCopilotHistoryReadPort)(bugbot.scm.context, process.env.GITHUB_SERVER_URL ?? 'https://github.com'), (0, issue_completion_composition_root_1.createNativeStatusReadPort)(binding), (0, github_runtime_doctor_composition_root_1.createGithubRuntimeDoctorPorts)(binding, bugbot.scm.context)); } function createCommitUseCaseCompositionRoot(projectBoardCommandPort, binding) { return new commit_use_case_1.CommitUseCase(new notify_new_commit_on_issue_use_case_1.NotifyNewCommitOnIssueUseCase((0, push_single_action_capability_port_binding_1.bindIssueReopen)((0, issue_interaction_composition_root_1.createIssueNotificationRepository)(), binding)), new check_changes_issue_size_use_case_1.CheckChangesIssueSizeUseCase((0, lifecycle_capability_port_binding_2.bindProjectBoardCommands)(projectBoardCommandPort, binding), (0, lifecycle_capability_port_binding_2.bindIssueLabels)((0, issue_labels_composition_root_1.createIssueLabelRepository)(), binding), (0, push_single_action_capability_port_binding_1.bindPullRequestBranchQuery)(new pull_request_lifecycle_repository_1.PullRequestLifecycleRepository((0, github_pull_request_client_factory_1.createPullRequestLifecycleClient)()), binding), (0, push_single_action_capability_port_binding_1.bindBranchChangeSize)(new branch_compare_repository_1.BranchCompareRepository((0, github_branch_client_factory_1.createBranchComparisonClient)()), binding)), createDetectPotentialProblemsUseCase(binding), (0, check_progress_composition_root_1.createCheckProgressCompositionRoot)(binding), (0, actor_authorization_composition_root_1.createActorAuthorizationRepository)()); diff --git a/docs/development/testing.mdx b/docs/development/testing.mdx index a783cdabb..a5cfa52fc 100644 --- a/docs/development/testing.mdx +++ b/docs/development/testing.mdx @@ -136,6 +136,10 @@ The reproducible `metrics:architecture` collector is the full evidence workflow; it requires a clean working tree and writes reports outside the repository. +Architecture fixtures that resolve TypeScript symbols should share one compiler +program containing production sources and isolated fixture modules. Retain the +real bindings and every assertion; rebuilding the whole project for each fixture +can exhaust a Jest worker during full platform verification. The raw-error boundary suite shares one real TypeScript compiler graph across the repository scan and its six fixture modules. It keeps all seven assertions and checks the real logger/sanitizer declarations. This reduces repeated diff --git a/docs/issues/comment-commands.mdx b/docs/issues/comment-commands.mdx index 767cbc878..1d57b03ae 100644 --- a/docs/issues/comment-commands.mdx +++ b/docs/issues/comment-commands.mdx @@ -45,6 +45,7 @@ Commands are recognized at the beginning of a comment, case-insensitively. The c | `/copilot findings` | Reconcile and publish current findings. | No | | `/copilot recheck` | Re-run the review against the current branch. | No | | `/copilot status` | Show the current Copilot status. | No | +| `/copilot doctor` | Check runtime prerequisites using bounded read-only evidence. | No | | `/copilot description` | Refresh the PR description when that action is configured. | No | | `/copilot fix [finding-id ...]` | Fix one or more existing Bugbot findings. | Yes, if authorized | | `/copilot dismiss [finding-id ...]` | Dismiss one or more findings. | No file changes | @@ -60,7 +61,7 @@ whitespace-separated, and commands are limited to 2,000 characters and 20 arguments. Missing or malformed required arguments produce a validation reply; they never mean “all” or authorize a broad change. -`help`, `status`, `dismiss`, `remember` and rejected commands do not run a +`help`, `status`, `doctor`, `dismiss`, `remember` and rejected commands do not run a model or change the temporary agent activity label. `sync-branch --dry-run` and `--no-agent` also avoid model activity. Ordinary sync may use the conflict fixer, and `findings` runs a fresh review. These distinctions preserve each @@ -113,7 +114,6 @@ You can combine plain and quoted IDs. Use JSON escapes for a literal double quote or backslash; malformed, empty or multiline quoted IDs are rejected. The command still accepts at most 20 IDs and 2,000 characters. - `/copilot status` reports lifecycle, waiting state, branch, labels, and the canonical Bugbot counts: open, reopened, verification-required, unknown, and resolved. In a PR conversation, it reads one trusted complete Bugbot status @@ -125,6 +125,47 @@ separate from an issue with the same number. Status and description queries preserve existing lifecycle and waiting labels when they make no lifecycle decision. +`/copilot doctor` reads seven bounded groups: effective workflow and dependency +settings, eligible issue/SDD prerequisites, the issue's native linked branch, +installed workflow structure, the trusted approval policy, declared approval +prerequisites, and the exact current PR identity. It has no arguments and +never invokes a model or repairs the repository. Participants can request this +metadata report even when model automation is restricted to organization members. + +Its structural workflow check requires issue `labeled`, issue-comment `created`, +and pull-request `opened` entry activities when an explicit `types` filter is +present. A valid but irrelevant filter such as issue-comment `deleted` is +blocked. Unknown dynamic job conditions remain unavailable; this does not prove +a workflow is disabled, and retrying alone cannot certify those conditions. + +The reply distinguishes **pass**, **warning**, **blocked**, **unavailable**, and +**not applicable**. A successfully read invalid policy is blocked; a denied or +failed read is unavailable. Completed independent checks remain visible. Only +verified absence of both repository and organization policy values selects the +existing off default. Active approval settings are declared prerequisites, +which need independent producer verification and do not certify write access. + +Workflow files and specification prerequisites are read at one verified immutable +default-branch revision, linked in the report. A moved default head makes that +sample unavailable. Native linked branches require complete same-repository +association and a matching live ref. A null or missing linked ref remains +unavailable; only a complete empty page proves that no branch is linked. +Specification prerequisites require canonical catalog metadata and evidence +arrays, supported status/date values, unique ownership and regular referenced +files in the same complete immutable tree (at most 10,000 entries). Doctor keeps +six bounded content reads; full as-built document contents and uncatalogued +specification discovery remain CI checks. Denied or incomplete tree evidence is +unavailable. Effective dependency, default and linked branches must be valid +bounded literal Git branch names, including every path component. A PR request checks only its exact event +PR and fences its current head; optional issue context is not inferred from +an equal conversation number. Reports use the repository locale in English or +Spanish with atomic fallback and one correlated reply per source comment. + +Inspect the displayed primary action and the workflow run, correct any proven +invalid configuration through the usual maintainer flow, then retry +`/copilot doctor`. The command does not read secret values, test write access, +provision setup, change labels or approvals, or infer grants from workflow text. + For issue types covered by `pre-branch-sdd`, status also appends an SDD readiness section. It reports when an SDD is not required and marks required SDD evidence unavailable until the reader verifies the current owned card, @@ -306,6 +347,17 @@ correlated replies; the welcome text embeds no separate marker. See [Bugbot detection](/bugbot/detection), [Autofix](/bugbot/autofix), and [Do user request](/bugbot/do-user-request) for the execution details. +The runtime doctor distinguishes proven missing files from inaccessible evidence. +A contents API 404 needs an independent complete tree at the same verified +commit before it can be reported as missing. Denied or incomplete tree reads +remain unavailable. The proof adds at most two shared read requests and never +changes repository files or permissions. + +The doctor workflow group checks that Copilot is in an eligible job and step for +the required event. Disabled jobs or steps cannot make it pass. Runtime-dependent +or unsupported conditions are reported as unavailable because a repository +snapshot cannot establish their result. Inspect those conditions and the actual +workflow run; a structural pass still does not prove runner availability. ### Recent operation history `/copilot history` shows at most ten supported requests in this issue or PR's @@ -340,10 +392,27 @@ SDD status keeps an unknown requirement visible when the issue has no admitted workflow kind. Evidence links use the configured GitHub server origin and the same issue identity; an untrusted or mismatched URL is shown without navigation. +Doctor treats a valid native issue with no description as an empty body, so +independent checks can still complete. Unknown evidence groups are rejected; +installed workflow activity filters must contain a non-empty array of text +values. A malformed filter is blocked rather than reported as a valid trigger. + +Doctor's bounded workflow interpreter leaves mixed-type comparisons unavailable +rather than applying JavaScript equality to GitHub expressions. Inspect the +actual condition when that prerequisite is unavailable; the report does not +execute the expression or infer that the job is disabled. When the detected request language and writing script already match the target, Copilot keeps the original wording and omits a redundant translation disclosure. Mixed-language requests and different writing scripts can still be translated. +Doctor verifies that every inspected configuration/workflow path is a regular file in the same immutable Git tree. Contents returned through a symlink do not establish valid installed prerequisites. + + +Doctor checks whether an Action job has usable structural scheduling evidence. +A static empty matrix blocks that job. Dynamic matrices and include/exclude +transformations are reported as unavailable because doctor does not simulate +the workflow. Ordinary nonmatrix and supported static jobs remain eligible; +this check does not certify a runner or real tool execution. History requests are deterministic on issue comments and PR review comments. Their activity-admission policy neither inspects agent role availability nor activates the temporary agent activity label; unavailable model configuration does not turn a history read @@ -412,3 +481,30 @@ help answers from non-comment events retain their ordinary immutable reply. that exact PR, even when its linked issue has a different number. The query uses the PR locale and does not infer an issue from the PR number. Review threads continue to direct you to the main conversation; arguments are not accepted. + +Doctor treats a unique enabled help issue as branchless: incomplete help-form +headings do not create a branch or SDD requirement. Disabled workflow kinds and +conflicting kind labels still show their admission block. For simple literal +workflow matrices, Doctor checks the product against GitHub's 256-job matrix +limit; it does not expand or execute jobs. Dynamic axes and include/exclude +transformations remain unavailable rather than receiving a guessed Pass. + +Doctor keeps an explicitly absent approval policy separate from a present empty +value: verified absence retains the off default, while a present empty or +whitespace value is invalid. A malformed provider value is unavailable. SDD +catalog prerequisites require every tracked product specification, including +nested Markdown files, to have a catalog owner; the three top-level standard +documents are excluded. Independent successful checks remain visible when a +policy or specification check is blocked. + +A workflow Action job cannot receive Pass when a directly required job is +statically skipped. Other `needs` requirements remain unavailable when runtime +success, dependency chains, cycles or unsupported override conditions cannot +be established by the bounded structural inspection. Another independent +eligible Action job can still satisfy the workflow contract. Doctor does not +execute jobs or predict their runtime success. + +An explicit missing-file result is blocked; an omitted or malformed provider +entry is unavailable evidence. Doctor never treats an incomplete sample as proof +that a file or permission is missing. Successful independent checks remain +visible, and the report points to the run and read-access guidance for retry. diff --git a/scripts/coverage-budgets.json b/scripts/coverage-budgets.json index 8ffa64f2c..9988e6c8b 100644 --- a/scripts/coverage-budgets.json +++ b/scripts/coverage-budgets.json @@ -557,6 +557,20 @@ ], "successMessage": "Git hook verification environment coverage: PASS (100% lines/statements/branches/functions)" }, + { + "name": "Doctor matrix eligibility", + "missingEntryLabel": "doctor matrix eligibility", + "rules": [ + { + "files": [ + "src/application/policies/doctor_workflow_matrix_policy.ts" + ], + "mode": "each", + "thresholdProfile": "exhaustive" + } + ], + "successMessage": "doctor matrix eligibility coverage: PASS (100% all metrics)" + }, { "name": "Reply request freshness", "missingEntryLabel": "reply request freshness", diff --git a/specs/CATALOG.md b/specs/CATALOG.md index f0431d906..8b366c8a4 100644 --- a/specs/CATALOG.md +++ b/specs/CATALOG.md @@ -10,13 +10,13 @@ debt or convert unknown historic intent into a design decision. | Capability ID | Status | Scope | Primary SDD | Evidence | |---|---|---|---|---| -| `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) + 2 companion | 246 paths · 2026-10-13 | +| `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) + 2 companion | 247 paths · 2026-10-13 | | `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-16 | | `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) | 59 paths · 2026-10-11 | | `execution-lifecycle` | Implemented | Shared GitHub Action lifecycle from event admission through durable user-facing results | [Execution admission, queueing, routing, and result publication](./execution-admission-queue-and-publication.md) + 3 companion | 86 paths · 2026-10-13 | | `architecture-quality-hardening` | Implemented | Close verified concurrency, error-contract, context-coupling, fan-out, setup/doctor, and provider-policy risks in dependency order | [Architecture quality and scalability hardening](./architecture-quality-and-scalability-hardening.md) + 1 companion | 72 paths · 2026-10-13 | -| `setup-and-doctor` | Implemented | Plan, validate, provision, and audit a repository installation without exposing credentials | [Setup, configuration, credentials, and doctor](./setup-configuration-credentials-and-doctor.md) + 2 companion | 165 paths · 2026-10-11 | +| `setup-and-doctor` | Implemented | Plan, validate, provision, and audit a repository installation without exposing credentials | [Setup, configuration, credentials, and doctor](./setup-configuration-credentials-and-doctor.md) + 3 companion | 196 paths · 2026-10-13 | | `local-web-setup-assistant` | Proposed | Offer a packaged, loopback-only Svelte setup interface over the existing engine with four-language guidance, English CLI help, basic/custom first-run paths, editable question and plan review, source-labelled GitHub facts, assisted CI and Project evidence, role-separated PATs, and truthful itemized outcomes; inspect existing configuration and apply independent, scope-aware runtime adjustments with permission, drift and read-back verification | [Local web setup assistant](./local-web-setup-assistant.md) + 1 companion | 202 paths · 2026-10-13 | | `guided-bot-pat-onboarding` | Proposed | Guide creation of the persistent workflow PAT, verify bot identity and grants, recover explicitly from wrong-account input within the session, and install the approved Actions Secret | [Guided bot PAT onboarding](./guided-bot-pat-onboarding.md) | 39 paths · 2026-10-07 | | `temporary-setup-operator-authorization` | Proposed | Guide creation of the one-run operator PAT through GitHub's official form, verify final setup access, and report user-owned deletion accurately | [Assisted setup PAT creation](./temporary-setup-operator-authorization.md) | 34 paths · 2026-10-07 | @@ -41,7 +41,7 @@ debt or convert unknown historic intent into a design decision. - 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) · [`specs/copilot-history.md`](./copilot-history.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) · [`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/copilot_interaction_policy.ts`](../src/application/policies/copilot_interaction_policy.ts) · [`src/application/policies/recommendation_policy.ts`](../src/application/policies/recommendation_policy.ts) · [`src/application/ports/actor_authorization_ports.ts`](../src/application/ports/actor_authorization_ports.ts) · [`src/data/repository/actor_modification_policy.ts`](../src/data/repository/actor_modification_policy.ts) · [`src/data/repository/organization/actor_authorization_repository.ts`](../src/data/repository/organization/actor_authorization_repository.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/application/usecases/actions/publish_issue_comment_workflow.ts`](../src/application/usecases/actions/publish_issue_comment_workflow.ts) · [`src/data/model/recommendation_state.ts`](../src/data/model/recommendation_state.ts) · [`src/data/model/config.ts`](../src/data/model/config.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/resolve_issue_finding.ts`](../src/application/usecases/steps/commit/bugbot/resolve_issue_finding.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) · [`src/application/usecases/comment_automation_command_workflow.ts`](../src/application/usecases/comment_automation_command_workflow.ts) · [`src/application/usecases/steps/commit/bugbot/dismiss_bugbot_findings_use_case.ts`](../src/application/usecases/steps/commit/bugbot/dismiss_bugbot_findings_use_case.ts) · [`src/application/policies/inactivity_result_counts_projection_policy.ts`](../src/application/policies/inactivity_result_counts_projection_policy.ts) · [`src/domain/copilot_operation.ts`](../src/domain/copilot_operation.ts) · [`src/application/policies/copilot_history_policy.ts`](../src/application/policies/copilot_history_policy.ts) · [`src/application/policies/copilot_history_projection_policy.ts`](../src/application/policies/copilot_history_projection_policy.ts) · [`src/application/policies/copilot_history_presentation_policy.ts`](../src/application/policies/copilot_history_presentation_policy.ts) · [`src/application/ports/copilot_history_ports.ts`](../src/application/ports/copilot_history_ports.ts) · [`src/application/usecases/read_copilot_history_workflow.ts`](../src/application/usecases/read_copilot_history_workflow.ts) · [`src/infrastructure/composition/copilot_history_port_factory.ts`](../src/infrastructure/composition/copilot_history_port_factory.ts) · [`src/application/ports/progress_diff_query_ports.ts`](../src/application/ports/progress_diff_query_ports.ts) · [`src/data/repository/progress_diff_repository.ts`](../src/data/repository/progress_diff_repository.ts) · [`src/application/policies/agent_activity_policy.ts`](../src/application/policies/agent_activity_policy.ts) · [`src/domain/reply_request_digest.ts`](../src/domain/reply_request_digest.ts) · [`src/application/ports/reply_request_read_ports.ts`](../src/application/ports/reply_request_read_ports.ts) · [`src/data/repository/reply_request_repository.ts`](../src/data/repository/reply_request_repository.ts) · [`src/infrastructure/composition/reply_request_composition_root.ts`](../src/infrastructure/composition/reply_request_composition_root.ts) · [`src/infrastructure/github/ports/github_reply_request_client.ts`](../src/infrastructure/github/ports/github_reply_request_client.ts) +- 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/copilot_interaction_policy.ts`](../src/application/policies/copilot_interaction_policy.ts) · [`src/application/policies/recommendation_policy.ts`](../src/application/policies/recommendation_policy.ts) · [`src/application/ports/actor_authorization_ports.ts`](../src/application/ports/actor_authorization_ports.ts) · [`src/data/repository/actor_modification_policy.ts`](../src/data/repository/actor_modification_policy.ts) · [`src/data/repository/organization/actor_authorization_repository.ts`](../src/data/repository/organization/actor_authorization_repository.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/application/usecases/actions/publish_issue_comment_workflow.ts`](../src/application/usecases/actions/publish_issue_comment_workflow.ts) · [`src/data/model/recommendation_state.ts`](../src/data/model/recommendation_state.ts) · [`src/data/model/config.ts`](../src/data/model/config.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/resolve_issue_finding.ts`](../src/application/usecases/steps/commit/bugbot/resolve_issue_finding.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) · [`src/application/usecases/comment_automation_command_workflow.ts`](../src/application/usecases/comment_automation_command_workflow.ts) · [`src/application/usecases/steps/commit/bugbot/dismiss_bugbot_findings_use_case.ts`](../src/application/usecases/steps/commit/bugbot/dismiss_bugbot_findings_use_case.ts) · [`src/application/policies/inactivity_result_counts_projection_policy.ts`](../src/application/policies/inactivity_result_counts_projection_policy.ts) · [`src/domain/copilot_command.ts`](../src/domain/copilot_command.ts) · [`src/domain/copilot_operation.ts`](../src/domain/copilot_operation.ts) · [`src/application/policies/copilot_history_policy.ts`](../src/application/policies/copilot_history_policy.ts) · [`src/application/policies/copilot_history_projection_policy.ts`](../src/application/policies/copilot_history_projection_policy.ts) · [`src/application/policies/copilot_history_presentation_policy.ts`](../src/application/policies/copilot_history_presentation_policy.ts) · [`src/application/ports/copilot_history_ports.ts`](../src/application/ports/copilot_history_ports.ts) · [`src/application/usecases/read_copilot_history_workflow.ts`](../src/application/usecases/read_copilot_history_workflow.ts) · [`src/infrastructure/composition/copilot_history_port_factory.ts`](../src/infrastructure/composition/copilot_history_port_factory.ts) · [`src/application/ports/progress_diff_query_ports.ts`](../src/application/ports/progress_diff_query_ports.ts) · [`src/data/repository/progress_diff_repository.ts`](../src/data/repository/progress_diff_repository.ts) · [`src/application/policies/agent_activity_policy.ts`](../src/application/policies/agent_activity_policy.ts) · [`src/domain/reply_request_digest.ts`](../src/domain/reply_request_digest.ts) · [`src/application/ports/reply_request_read_ports.ts`](../src/application/ports/reply_request_read_ports.ts) · [`src/data/repository/reply_request_repository.ts`](../src/data/repository/reply_request_repository.ts) · [`src/infrastructure/composition/reply_request_composition_root.ts`](../src/infrastructure/composition/reply_request_composition_root.ts) · [`src/infrastructure/github/ports/github_reply_request_client.ts`](../src/infrastructure/github/ports/github_reply_request_client.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/actions/__tests__/github_action.test.ts`](../src/actions/__tests__/github_action.test.ts) · [`src/data/repository/__tests__/actor_modification_policy.test.ts`](../src/data/repository/__tests__/actor_modification_policy.test.ts) · [`src/data/repository/organization/__tests__/actor_authorization_repository.test.ts`](../src/data/repository/organization/__tests__/actor_authorization_repository.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) · [`src/domain/__tests__/copilot_operation.test.ts`](../src/domain/__tests__/copilot_operation.test.ts) · [`src/application/policies/__tests__/copilot_history_policy.test.ts`](../src/application/policies/__tests__/copilot_history_policy.test.ts) · [`src/application/policies/__tests__/copilot_history_presentation_policy.test.ts`](../src/application/policies/__tests__/copilot_history_presentation_policy.test.ts) · [`src/application/policies/__tests__/copilot_operation_publication.test.ts`](../src/application/policies/__tests__/copilot_operation_publication.test.ts) · [`src/application/usecases/__tests__/copilot_history_route.test.ts`](../src/application/usecases/__tests__/copilot_history_route.test.ts) · [`src/application/usecases/__tests__/copilot_history_workflow.test.ts`](../src/application/usecases/__tests__/copilot_history_workflow.test.ts) · [`src/infrastructure/composition/__tests__/copilot_history_port_factory.test.ts`](../src/infrastructure/composition/__tests__/copilot_history_port_factory.test.ts) · [`src/data/repository/__tests__/progress_diff_repository.test.ts`](../src/data/repository/__tests__/progress_diff_repository.test.ts) · [`src/application/policies/__tests__/agent_activity_policy.test.ts`](../src/application/policies/__tests__/agent_activity_policy.test.ts) · [`src/data/repository/__tests__/reply_request_repository.test.ts`](../src/data/repository/__tests__/reply_request_repository.test.ts) · [`src/application/usecases/steps/common/__tests__/reply_request_publication.test.ts`](../src/application/usecases/steps/common/__tests__/reply_request_publication.test.ts) · [`src/infrastructure/composition/__tests__/reply_request_composition_root.test.ts`](../src/infrastructure/composition/__tests__/reply_request_composition_root.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) · [`docs/issues/branch-management.mdx`](../docs/issues/branch-management.mdx) @@ -103,13 +103,13 @@ debt or convert unknown historic intent into a design decision. ### `setup-and-doctor` — Setup, configuration, credentials, and doctor - Owner: Copilot maintainers -- Last verified: 2026-10-11 -- Specifications: [`specs/setup-configuration-credentials-and-doctor.md`](./setup-configuration-credentials-and-doctor.md) · [`specs/setup-doctor-architecture-hardening.md`](./setup-doctor-architecture-hardening.md) · [`specs/setup-pat-permission-guidance-and-verification.md`](./setup-pat-permission-guidance-and-verification.md) -- Workflows: [`setup/workflows/agent-cli-provisioning.yml`](../setup/workflows/agent-cli-provisioning.yml) · [`setup/workflows/copilot_credential_health.yml`](../setup/workflows/copilot_credential_health.yml) -- Entrypoints: [`src/cli/commands/setup.ts`](../src/cli/commands/setup.ts) · [`src/cli/commands/doctor.ts`](../src/cli/commands/doctor.ts) -- Core code: [`src/domain/setup.ts`](../src/domain/setup.ts) · [`src/domain/setup_questionnaire.ts`](../src/domain/setup_questionnaire.ts) · [`src/domain/setup_token_permissions.ts`](../src/domain/setup_token_permissions.ts) · [`src/application/ports/setup_terminal_ports.ts`](../src/application/ports/setup_terminal_ports.ts) · [`src/application/ports/setup_wizard_ports.ts`](../src/application/ports/setup_wizard_ports.ts) · [`src/application/ports/branch_tag_ports.ts`](../src/application/ports/branch_tag_ports.ts) · [`src/application/policies/setup_issue_workflow_policy.ts`](../src/application/policies/setup_issue_workflow_policy.ts) · [`src/data/repository/release/repository_version_tags_query_repository.ts`](../src/data/repository/release/repository_version_tags_query_repository.ts) · [`src/application/ports/setup_token_permission_ports.ts`](../src/application/ports/setup_token_permission_ports.ts) · [`src/application/policies/setup_token_permission_evidence_policy.ts`](../src/application/policies/setup_token_permission_evidence_policy.ts) · [`src/application/policies/setup_token_permission_policy.ts`](../src/application/policies/setup_token_permission_policy.ts) · [`src/application/policies/setup_questionnaire_policy.ts`](../src/application/policies/setup_questionnaire_policy.ts) · [`src/application/policies/setup_configuration_plan.ts`](../src/application/policies/setup_configuration_plan.ts) · [`src/application/policies/merge_setup_overrides_policy.ts`](../src/application/policies/merge_setup_overrides_policy.ts) · [`src/application/policies/setup_configuration_storage_policy.ts`](../src/application/policies/setup_configuration_storage_policy.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/policies/setup_journey_policy.ts`](../src/application/policies/setup_journey_policy.ts) · [`src/application/policies/setup_permission_summary_policy.ts`](../src/application/policies/setup_permission_summary_policy.ts) · [`src/application/usecases/setup/setup_wizard_use_case.ts`](../src/application/usecases/setup/setup_wizard_use_case.ts) · [`src/application/usecases/setup/setup_journey_use_case.ts`](../src/application/usecases/setup/setup_journey_use_case.ts) · [`src/application/usecases/setup/setup_session_coordinator.ts`](../src/application/usecases/setup/setup_session_coordinator.ts) · [`src/application/usecases/setup/setup_questionnaire_controller.ts`](../src/application/usecases/setup/setup_questionnaire_controller.ts) · [`src/application/usecases/setup/setup_credentials_use_case.ts`](../src/application/usecases/setup/setup_credentials_use_case.ts) · [`src/application/usecases/setup/setup_token_permissions_use_case.ts`](../src/application/usecases/setup/setup_token_permissions_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/actions/initial_setup_workflow.ts`](../src/application/usecases/actions/initial_setup_workflow.ts) · [`src/application/usecases/actions/setup_resource_provisioning.ts`](../src/application/usecases/actions/setup_resource_provisioning.ts) · [`src/application/ports/message_catalog_ports.ts`](../src/application/ports/message_catalog_ports.ts) · [`src/application/usecases/localization/resolve_message_catalog_use_case.ts`](../src/application/usecases/localization/resolve_message_catalog_use_case.ts) · [`src/application/policies/setup_configuration_validation.ts`](../src/application/policies/setup_configuration_validation.ts) · [`src/infrastructure/setup_workspace_adapter.ts`](../src/infrastructure/setup_workspace_adapter.ts) · [`src/data/repository/repository_variables_repository.ts`](../src/data/repository/repository_variables_repository.ts) · [`src/infrastructure/github/ports/github_repository_variables_protocol.ts`](../src/infrastructure/github/ports/github_repository_variables_protocol.ts) · [`src/infrastructure/setup_remote_credential_health_adapter.ts`](../src/infrastructure/setup_remote_credential_health_adapter.ts) · [`src/infrastructure/setup_credential_validation_adapter.ts`](../src/infrastructure/setup_credential_validation_adapter.ts) · [`src/infrastructure/setup_token_permission_query_adapter.ts`](../src/infrastructure/setup_token_permission_query_adapter.ts) · [`src/infrastructure/setup_permission_actions_probe.ts`](../src/infrastructure/setup_permission_actions_probe.ts) · [`src/infrastructure/setup_permission_issue_probe.ts`](../src/infrastructure/setup_permission_issue_probe.ts) · [`src/infrastructure/setup_permission_issue_cleanup.ts`](../src/infrastructure/setup_permission_issue_cleanup.ts) · [`src/infrastructure/setup_permission_probe_journal.ts`](../src/infrastructure/setup_permission_probe_journal.ts) · [`src/infrastructure/setup_permission_probe_transaction.ts`](../src/infrastructure/setup_permission_probe_transaction.ts) · [`src/infrastructure/setup_permission_resource_probes.ts`](../src/infrastructure/setup_permission_resource_probes.ts) · [`src/infrastructure/setup_projects_probe_page_policy.ts`](../src/infrastructure/setup_projects_probe_page_policy.ts) · [`src/cli/setup_terminal_driver.ts`](../src/cli/setup_terminal_driver.ts) · [`src/cli/setup_question_renderer.ts`](../src/cli/setup_question_renderer.ts) · [`src/cli/setup_journey_presenter.ts`](../src/cli/setup_journey_presenter.ts) · [`src/cli/setup_plan_presenter.ts`](../src/cli/setup_plan_presenter.ts) · [`src/cli/setup_doctor_presenter.ts`](../src/cli/setup_doctor_presenter.ts) · [`src/cli/setup_prompt_rendering.ts`](../src/cli/setup_prompt_rendering.ts) · [`src/cli/setup_credential_prompt_adapter.ts`](../src/cli/setup_credential_prompt_adapter.ts) · [`src/cli/setup_credential_collection.ts`](../src/cli/setup_credential_collection.ts) · [`src/cli/commands/setup_execution.ts`](../src/cli/commands/setup_execution.ts) · [`src/cli/setup_token_permission_presenter.ts`](../src/cli/setup_token_permission_presenter.ts) · [`src/infrastructure/composition/setup_credentials_composition_root.ts`](../src/infrastructure/composition/setup_credentials_composition_root.ts) · [`src/infrastructure/composition/setup_token_permissions_composition_root.ts`](../src/infrastructure/composition/setup_token_permissions_composition_root.ts) · [`src/infrastructure/composition/setup_doctor_composition_root.ts`](../src/infrastructure/composition/setup_doctor_composition_root.ts) · [`scripts/coverage-budgets.json`](../scripts/coverage-budgets.json) · [`scripts/documentation_pat_exception_policy.cjs`](../scripts/documentation_pat_exception_policy.cjs) · [`scripts/validate-documentation-contract.cjs`](../scripts/validate-documentation-contract.cjs) · [`src/infrastructure/http_deadline.ts`](../src/infrastructure/http_deadline.ts) · [`src/infrastructure/setup_permission_denial.ts`](../src/infrastructure/setup_permission_denial.ts) · [`src/infrastructure/setup_permission_read_probe.ts`](../src/infrastructure/setup_permission_read_probe.ts) · [`src/infrastructure/setup_permission_read_evidence.ts`](../src/infrastructure/setup_permission_read_evidence.ts) · [`src/infrastructure/setup_permission_read_shape.ts`](../src/infrastructure/setup_permission_read_shape.ts) · [`src/infrastructure/setup_permission_projects_read.ts`](../src/infrastructure/setup_permission_projects_read.ts) · [`src/infrastructure/setup_permission_projects_access.ts`](../src/infrastructure/setup_permission_projects_access.ts) · [`src/infrastructure/setup_permission_workflow_cleanup.ts`](../src/infrastructure/setup_permission_workflow_cleanup.ts) · [`src/infrastructure/setup_permission_project_cleanup.ts`](../src/infrastructure/setup_permission_project_cleanup.ts) · [`src/infrastructure/setup_permission_probe_http.ts`](../src/infrastructure/setup_permission_probe_http.ts) · [`src/infrastructure/setup_permission_pull_request_probe.ts`](../src/infrastructure/setup_permission_pull_request_probe.ts) · [`src/infrastructure/git_authentication_environment.ts`](../src/infrastructure/git_authentication_environment.ts) · [`src/actions/github_action_input.ts`](../src/actions/github_action_input.ts) · [`src/infrastructure/setup_github_identity_query_adapter.ts`](../src/infrastructure/setup_github_identity_query_adapter.ts) · [`src/infrastructure/github_secret_encryption.ts`](../src/infrastructure/github_secret_encryption.ts) · [`src/infrastructure/setup_permission_organization_owner.ts`](../src/infrastructure/setup_permission_organization_owner.ts) · [`src/data/repository/github/github_actions_resource_inspector.ts`](../src/data/repository/github/github_actions_resource_inspector.ts) · [`src/data/repository/github/github_actions_resource_commands.ts`](../src/data/repository/github/github_actions_resource_commands.ts) · [`src/data/repository/github/github_actions_resource_collection.ts`](../src/data/repository/github/github_actions_resource_collection.ts) · [`src/application/usecases/setup/resolve_setup_workflow_pat_conflict_use_case.ts`](../src/application/usecases/setup/resolve_setup_workflow_pat_conflict_use_case.ts) · [`src/cli/setup_workflow_pat_storage_copy.ts`](../src/cli/setup_workflow_pat_storage_copy.ts) · [`src/cli/commands/setup_policy.ts`](../src/cli/commands/setup_policy.ts) · [`src/application/usecases/execution/execution_issue_number_policy.ts`](../src/application/usecases/execution/execution_issue_number_policy.ts) · [`src/application/usecases/execution/setup_execution_workflow.ts`](../src/application/usecases/execution/setup_execution_workflow.ts) -- Tests: [`src/data/repository/__tests__/repository_version_tags_query_repository.test.ts`](../src/data/repository/__tests__/repository_version_tags_query_repository.test.ts) · [`src/cli/__tests__/setup_command_options.test.ts`](../src/cli/__tests__/setup_command_options.test.ts) · [`src/data/repository/__tests__/setup_variable_write_failures.test.ts`](../src/data/repository/__tests__/setup_variable_write_failures.test.ts) · [`src/infrastructure/composition/__tests__/initial_setup_composition_root.test.ts`](../src/infrastructure/composition/__tests__/initial_setup_composition_root.test.ts) · [`src/application/policies/__tests__/setup_questionnaire_policy.test.ts`](../src/application/policies/__tests__/setup_questionnaire_policy.test.ts) · [`src/application/policies/__tests__/setup_configuration_policy.test.ts`](../src/application/policies/__tests__/setup_configuration_policy.test.ts) · [`src/application/policies/__tests__/setup_token_permission_policy.test.ts`](../src/application/policies/__tests__/setup_token_permission_policy.test.ts) · [`src/application/policies/__tests__/setup_remote_facts_policy.test.ts`](../src/application/policies/__tests__/setup_remote_facts_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/setup/__tests__/setup_questionnaire_controller.test.ts`](../src/application/usecases/setup/__tests__/setup_questionnaire_controller.test.ts) · [`src/application/usecases/setup/__tests__/setup_wizard_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_wizard_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_journey_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_journey_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_session_coordinator.test.ts`](../src/application/usecases/setup/__tests__/setup_session_coordinator.test.ts) · [`src/application/usecases/setup/__tests__/setup_credentials_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_credentials_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_token_permissions_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_token_permissions_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/usecases/actions/__tests__/initial_setup_use_case.test.ts`](../src/application/usecases/actions/__tests__/initial_setup_use_case.test.ts) · [`src/application/usecases/actions/__tests__/setup_resource_provisioning.test.ts`](../src/application/usecases/actions/__tests__/setup_resource_provisioning.test.ts) · [`src/infrastructure/__tests__/setup_workspace_adapter.test.ts`](../src/infrastructure/__tests__/setup_workspace_adapter.test.ts) · [`src/infrastructure/__tests__/setup_remote_credential_health_adapter.test.ts`](../src/infrastructure/__tests__/setup_remote_credential_health_adapter.test.ts) · [`src/infrastructure/__tests__/setup_token_permission_query_adapter.test.ts`](../src/infrastructure/__tests__/setup_token_permission_query_adapter.test.ts) · [`src/infrastructure/__tests__/setup_permission_resource_probes.test.ts`](../src/infrastructure/__tests__/setup_permission_resource_probes.test.ts) · [`src/infrastructure/__tests__/setup_projects_probe_page_policy.test.ts`](../src/infrastructure/__tests__/setup_projects_probe_page_policy.test.ts) · [`src/infrastructure/composition/__tests__/setup_token_permissions_composition_root.test.ts`](../src/infrastructure/composition/__tests__/setup_token_permissions_composition_root.test.ts) · [`src/data/repository/__tests__/repository_variables_repository.test.ts`](../src/data/repository/__tests__/repository_variables_repository.test.ts) · [`src/cli/__tests__/setup_presenters.test.ts`](../src/cli/__tests__/setup_presenters.test.ts) · [`src/cli/__tests__/setup_journey_presenter.test.ts`](../src/cli/__tests__/setup_journey_presenter.test.ts) · [`src/cli/__tests__/setup_prompt_rendering.test.ts`](../src/cli/__tests__/setup_prompt_rendering.test.ts) · [`src/cli/__tests__/setup_token_permission_presenter.test.ts`](../src/cli/__tests__/setup_token_permission_presenter.test.ts) · [`src/__tests__/cli.test.ts`](../src/__tests__/cli.test.ts) · [`src/cli/__tests__/setup_terminal_driver.test.ts`](../src/cli/__tests__/setup_terminal_driver.test.ts) · [`src/architecture/__tests__/setup_doctor_boundaries.test.ts`](../src/architecture/__tests__/setup_doctor_boundaries.test.ts) · [`src/tooling/__tests__/documentation_pat_exception_policy.test.ts`](../src/tooling/__tests__/documentation_pat_exception_policy.test.ts) · [`src/infrastructure/__tests__/setup_pat_audit.test.ts`](../src/infrastructure/__tests__/setup_pat_audit.test.ts) · [`src/infrastructure/__tests__/setup_permission_projects_access.test.ts`](../src/infrastructure/__tests__/setup_permission_projects_access.test.ts) · [`src/infrastructure/__tests__/setup_credential_validation_adapter.test.ts`](../src/infrastructure/__tests__/setup_credential_validation_adapter.test.ts) · [`src/infrastructure/__tests__/setup_github_identity_query_adapter.test.ts`](../src/infrastructure/__tests__/setup_github_identity_query_adapter.test.ts) · [`src/infrastructure/__tests__/git_commit_adapter.test.ts`](../src/infrastructure/__tests__/git_commit_adapter.test.ts) · [`src/actions/__tests__/github_action_input.test.ts`](../src/actions/__tests__/github_action_input.test.ts) · [`src/infrastructure/__tests__/setup_permission_cleanup_contract.test.ts`](../src/infrastructure/__tests__/setup_permission_cleanup_contract.test.ts) · [`src/infrastructure/__tests__/setup_permission_actions_cleanup_timing.test.ts`](../src/infrastructure/__tests__/setup_permission_actions_cleanup_timing.test.ts) · [`src/infrastructure/__tests__/setup_permission_operator_actions.test.ts`](../src/infrastructure/__tests__/setup_permission_operator_actions.test.ts) · [`src/infrastructure/__tests__/setup_permission_journal_contract.test.ts`](../src/infrastructure/__tests__/setup_permission_journal_contract.test.ts) · [`src/infrastructure/__tests__/setup_permission_audit_transactions.test.ts`](../src/infrastructure/__tests__/setup_permission_audit_transactions.test.ts) · [`src/infrastructure/__tests__/setup_permission_write_failures.test.ts`](../src/infrastructure/__tests__/setup_permission_write_failures.test.ts) · [`src/infrastructure/__tests__/setup_permission_resource_failures.test.ts`](../src/infrastructure/__tests__/setup_permission_resource_failures.test.ts) · [`src/infrastructure/__tests__/setup_permission_boundary_failures.test.ts`](../src/infrastructure/__tests__/setup_permission_boundary_failures.test.ts) · [`src/infrastructure/__tests__/github_secret_encryption.test.ts`](../src/infrastructure/__tests__/github_secret_encryption.test.ts) · [`src/infrastructure/__tests__/setup_permission_organization_owner.test.ts`](../src/infrastructure/__tests__/setup_permission_organization_owner.test.ts) · [`src/infrastructure/__tests__/setup_pat_bootstrap_transactions.test.ts`](../src/infrastructure/__tests__/setup_pat_bootstrap_transactions.test.ts) · [`src/data/repository/__tests__/repository_actions_sdk_contract.integration.test.ts`](../src/data/repository/__tests__/repository_actions_sdk_contract.integration.test.ts) · [`src/data/repository/__tests__/repository_actions_resource_edge_cases.test.ts`](../src/data/repository/__tests__/repository_actions_resource_edge_cases.test.ts) · [`src/data/repository/__tests__/repository_selected_resource_grants.test.ts`](../src/data/repository/__tests__/repository_selected_resource_grants.test.ts) · [`src/infrastructure/__tests__/setup_permission_read_contract.test.ts`](../src/infrastructure/__tests__/setup_permission_read_contract.test.ts) · [`src/application/usecases/setup/__tests__/setup_workflow_pat_conflict.test.ts`](../src/application/usecases/setup/__tests__/setup_workflow_pat_conflict.test.ts) · [`src/application/usecases/setup/__tests__/setup_workflow_pat_credential_rotation.test.ts`](../src/application/usecases/setup/__tests__/setup_workflow_pat_credential_rotation.test.ts) · [`src/data/repository/__tests__/setup_organization_pat_inventory.test.ts`](../src/data/repository/__tests__/setup_organization_pat_inventory.test.ts) · [`src/cli/__tests__/web_setup_bot_pat_scope_conflict.test.ts`](../src/cli/__tests__/web_setup_bot_pat_scope_conflict.test.ts) · [`src/actions/__tests__/local_setup_apply_contract.test.ts`](../src/actions/__tests__/local_setup_apply_contract.test.ts) · [`src/cli/commands/__tests__/setup_policy.test.ts`](../src/cli/commands/__tests__/setup_policy.test.ts) · [`src/application/usecases/execution/__tests__/execution_issue_number_policy.test.ts`](../src/application/usecases/execution/__tests__/execution_issue_number_policy.test.ts) · [`src/application/usecases/execution/__tests__/setup_execution_workflow.test.ts`](../src/application/usecases/execution/__tests__/setup_execution_workflow.test.ts) -- User documentation: [`README.md`](../README.md) · [`docs/how-to-use.mdx`](../docs/how-to-use.mdx) · [`docs/configuration.mdx`](../docs/configuration.mdx) · [`docs/configuration-checklist.mdx`](../docs/configuration-checklist.mdx) · [`docs/authentication.mdx`](../docs/authentication.mdx) · [`docs/development/architecture.mdx`](../docs/development/architecture.mdx) · [`docs/security-operations/operations/provisioning.mdx`](../docs/security-operations/operations/provisioning.mdx) · [`docs/security-operations/operations/troubleshooting.mdx`](../docs/security-operations/operations/troubleshooting.mdx) · [`docs/security-operations/security/credentials.mdx`](../docs/security-operations/security/credentials.mdx) · [`docs/single-actions/workflow-and-cli.mdx`](../docs/single-actions/workflow-and-cli.mdx) · [`docs/security-operations/operations/verification.mdx`](../docs/security-operations/operations/verification.mdx) · [`docs/development/setup-pat-provider-audit-2026-10-06.md`](../docs/development/setup-pat-provider-audit-2026-10-06.md) +- Last verified: 2026-10-13 +- Specifications: [`specs/setup-configuration-credentials-and-doctor.md`](./setup-configuration-credentials-and-doctor.md) · [`specs/setup-doctor-architecture-hardening.md`](./setup-doctor-architecture-hardening.md) · [`specs/setup-pat-permission-guidance-and-verification.md`](./setup-pat-permission-guidance-and-verification.md) · [`specs/github-runtime-doctor-command.md`](./github-runtime-doctor-command.md) +- Workflows: [`setup/workflows/agent-cli-provisioning.yml`](../setup/workflows/agent-cli-provisioning.yml) · [`setup/workflows/copilot_credential_health.yml`](../setup/workflows/copilot_credential_health.yml) · [`.github/workflows/copilot_issue_comment.yml`](../.github/workflows/copilot_issue_comment.yml) · [`.github/workflows/copilot_pull_request_comment.yml`](../.github/workflows/copilot_pull_request_comment.yml) +- Entrypoints: [`src/cli/commands/setup.ts`](../src/cli/commands/setup.ts) · [`src/cli/commands/doctor.ts`](../src/cli/commands/doctor.ts) · [`src/application/usecases/issue_comment_use_case.ts`](../src/application/usecases/issue_comment_use_case.ts) · [`src/application/usecases/pull_request_review_comment_use_case.ts`](../src/application/usecases/pull_request_review_comment_use_case.ts) +- Core code: [`src/domain/setup.ts`](../src/domain/setup.ts) · [`src/domain/setup_questionnaire.ts`](../src/domain/setup_questionnaire.ts) · [`src/domain/setup_token_permissions.ts`](../src/domain/setup_token_permissions.ts) · [`src/application/ports/setup_terminal_ports.ts`](../src/application/ports/setup_terminal_ports.ts) · [`src/application/ports/setup_wizard_ports.ts`](../src/application/ports/setup_wizard_ports.ts) · [`src/application/ports/branch_tag_ports.ts`](../src/application/ports/branch_tag_ports.ts) · [`src/application/policies/setup_issue_workflow_policy.ts`](../src/application/policies/setup_issue_workflow_policy.ts) · [`src/data/repository/release/repository_version_tags_query_repository.ts`](../src/data/repository/release/repository_version_tags_query_repository.ts) · [`src/application/ports/setup_token_permission_ports.ts`](../src/application/ports/setup_token_permission_ports.ts) · [`src/application/policies/setup_token_permission_evidence_policy.ts`](../src/application/policies/setup_token_permission_evidence_policy.ts) · [`src/application/policies/setup_token_permission_policy.ts`](../src/application/policies/setup_token_permission_policy.ts) · [`src/application/policies/setup_questionnaire_policy.ts`](../src/application/policies/setup_questionnaire_policy.ts) · [`src/application/policies/setup_configuration_plan.ts`](../src/application/policies/setup_configuration_plan.ts) · [`src/application/policies/merge_setup_overrides_policy.ts`](../src/application/policies/merge_setup_overrides_policy.ts) · [`src/application/policies/setup_configuration_storage_policy.ts`](../src/application/policies/setup_configuration_storage_policy.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/policies/setup_journey_policy.ts`](../src/application/policies/setup_journey_policy.ts) · [`src/application/policies/setup_permission_summary_policy.ts`](../src/application/policies/setup_permission_summary_policy.ts) · [`src/application/usecases/setup/setup_wizard_use_case.ts`](../src/application/usecases/setup/setup_wizard_use_case.ts) · [`src/application/usecases/setup/setup_journey_use_case.ts`](../src/application/usecases/setup/setup_journey_use_case.ts) · [`src/application/usecases/setup/setup_session_coordinator.ts`](../src/application/usecases/setup/setup_session_coordinator.ts) · [`src/application/usecases/setup/setup_questionnaire_controller.ts`](../src/application/usecases/setup/setup_questionnaire_controller.ts) · [`src/application/usecases/setup/setup_credentials_use_case.ts`](../src/application/usecases/setup/setup_credentials_use_case.ts) · [`src/application/usecases/setup/setup_token_permissions_use_case.ts`](../src/application/usecases/setup/setup_token_permissions_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/actions/initial_setup_workflow.ts`](../src/application/usecases/actions/initial_setup_workflow.ts) · [`src/application/usecases/actions/setup_resource_provisioning.ts`](../src/application/usecases/actions/setup_resource_provisioning.ts) · [`src/application/ports/message_catalog_ports.ts`](../src/application/ports/message_catalog_ports.ts) · [`src/application/usecases/localization/resolve_message_catalog_use_case.ts`](../src/application/usecases/localization/resolve_message_catalog_use_case.ts) · [`src/application/policies/setup_configuration_validation.ts`](../src/application/policies/setup_configuration_validation.ts) · [`src/infrastructure/setup_workspace_adapter.ts`](../src/infrastructure/setup_workspace_adapter.ts) · [`src/data/repository/repository_variables_repository.ts`](../src/data/repository/repository_variables_repository.ts) · [`src/infrastructure/github/ports/github_repository_variables_protocol.ts`](../src/infrastructure/github/ports/github_repository_variables_protocol.ts) · [`src/infrastructure/setup_remote_credential_health_adapter.ts`](../src/infrastructure/setup_remote_credential_health_adapter.ts) · [`src/infrastructure/setup_credential_validation_adapter.ts`](../src/infrastructure/setup_credential_validation_adapter.ts) · [`src/infrastructure/setup_token_permission_query_adapter.ts`](../src/infrastructure/setup_token_permission_query_adapter.ts) · [`src/infrastructure/setup_permission_actions_probe.ts`](../src/infrastructure/setup_permission_actions_probe.ts) · [`src/infrastructure/setup_permission_issue_probe.ts`](../src/infrastructure/setup_permission_issue_probe.ts) · [`src/infrastructure/setup_permission_issue_cleanup.ts`](../src/infrastructure/setup_permission_issue_cleanup.ts) · [`src/infrastructure/setup_permission_probe_journal.ts`](../src/infrastructure/setup_permission_probe_journal.ts) · [`src/infrastructure/setup_permission_probe_transaction.ts`](../src/infrastructure/setup_permission_probe_transaction.ts) · [`src/infrastructure/setup_permission_resource_probes.ts`](../src/infrastructure/setup_permission_resource_probes.ts) · [`src/infrastructure/setup_projects_probe_page_policy.ts`](../src/infrastructure/setup_projects_probe_page_policy.ts) · [`src/cli/setup_terminal_driver.ts`](../src/cli/setup_terminal_driver.ts) · [`src/cli/setup_question_renderer.ts`](../src/cli/setup_question_renderer.ts) · [`src/cli/setup_journey_presenter.ts`](../src/cli/setup_journey_presenter.ts) · [`src/cli/setup_plan_presenter.ts`](../src/cli/setup_plan_presenter.ts) · [`src/cli/setup_doctor_presenter.ts`](../src/cli/setup_doctor_presenter.ts) · [`src/cli/setup_prompt_rendering.ts`](../src/cli/setup_prompt_rendering.ts) · [`src/cli/setup_credential_prompt_adapter.ts`](../src/cli/setup_credential_prompt_adapter.ts) · [`src/cli/setup_credential_collection.ts`](../src/cli/setup_credential_collection.ts) · [`src/cli/commands/setup_execution.ts`](../src/cli/commands/setup_execution.ts) · [`src/cli/setup_token_permission_presenter.ts`](../src/cli/setup_token_permission_presenter.ts) · [`src/infrastructure/composition/setup_credentials_composition_root.ts`](../src/infrastructure/composition/setup_credentials_composition_root.ts) · [`src/infrastructure/composition/setup_token_permissions_composition_root.ts`](../src/infrastructure/composition/setup_token_permissions_composition_root.ts) · [`src/infrastructure/composition/setup_doctor_composition_root.ts`](../src/infrastructure/composition/setup_doctor_composition_root.ts) · [`scripts/coverage-budgets.json`](../scripts/coverage-budgets.json) · [`scripts/documentation_pat_exception_policy.cjs`](../scripts/documentation_pat_exception_policy.cjs) · [`scripts/validate-documentation-contract.cjs`](../scripts/validate-documentation-contract.cjs) · [`src/infrastructure/http_deadline.ts`](../src/infrastructure/http_deadline.ts) · [`src/infrastructure/setup_permission_denial.ts`](../src/infrastructure/setup_permission_denial.ts) · [`src/infrastructure/setup_permission_read_probe.ts`](../src/infrastructure/setup_permission_read_probe.ts) · [`src/infrastructure/setup_permission_read_evidence.ts`](../src/infrastructure/setup_permission_read_evidence.ts) · [`src/infrastructure/setup_permission_read_shape.ts`](../src/infrastructure/setup_permission_read_shape.ts) · [`src/infrastructure/setup_permission_projects_read.ts`](../src/infrastructure/setup_permission_projects_read.ts) · [`src/infrastructure/setup_permission_projects_access.ts`](../src/infrastructure/setup_permission_projects_access.ts) · [`src/infrastructure/setup_permission_workflow_cleanup.ts`](../src/infrastructure/setup_permission_workflow_cleanup.ts) · [`src/infrastructure/setup_permission_project_cleanup.ts`](../src/infrastructure/setup_permission_project_cleanup.ts) · [`src/infrastructure/setup_permission_probe_http.ts`](../src/infrastructure/setup_permission_probe_http.ts) · [`src/infrastructure/setup_permission_pull_request_probe.ts`](../src/infrastructure/setup_permission_pull_request_probe.ts) · [`src/infrastructure/git_authentication_environment.ts`](../src/infrastructure/git_authentication_environment.ts) · [`src/actions/github_action_input.ts`](../src/actions/github_action_input.ts) · [`src/infrastructure/setup_github_identity_query_adapter.ts`](../src/infrastructure/setup_github_identity_query_adapter.ts) · [`src/infrastructure/github_secret_encryption.ts`](../src/infrastructure/github_secret_encryption.ts) · [`src/infrastructure/setup_permission_organization_owner.ts`](../src/infrastructure/setup_permission_organization_owner.ts) · [`src/data/repository/github/github_actions_resource_inspector.ts`](../src/data/repository/github/github_actions_resource_inspector.ts) · [`src/data/repository/github/github_actions_resource_commands.ts`](../src/data/repository/github/github_actions_resource_commands.ts) · [`src/data/repository/github/github_actions_resource_collection.ts`](../src/data/repository/github/github_actions_resource_collection.ts) · [`src/application/usecases/setup/resolve_setup_workflow_pat_conflict_use_case.ts`](../src/application/usecases/setup/resolve_setup_workflow_pat_conflict_use_case.ts) · [`src/cli/setup_workflow_pat_storage_copy.ts`](../src/cli/setup_workflow_pat_storage_copy.ts) · [`src/cli/commands/setup_policy.ts`](../src/cli/commands/setup_policy.ts) · [`src/application/usecases/execution/execution_issue_number_policy.ts`](../src/application/usecases/execution/execution_issue_number_policy.ts) · [`src/application/usecases/execution/setup_execution_workflow.ts`](../src/application/usecases/execution/setup_execution_workflow.ts) · [`src/domain/github_runtime_doctor.ts`](../src/domain/github_runtime_doctor.ts) · [`src/domain/copilot_command.ts`](../src/domain/copilot_command.ts) · [`src/application/usecases/comment_automation_command_workflow.ts`](../src/application/usecases/comment_automation_command_workflow.ts) · [`src/application/policies/copilot_interaction_policy.ts`](../src/application/policies/copilot_interaction_policy.ts) · [`src/application/policies/publication_message_catalog.ts`](../src/application/policies/publication_message_catalog.ts) · [`src/application/ports/github_runtime_doctor_ports.ts`](../src/application/ports/github_runtime_doctor_ports.ts) · [`src/application/policies/github_runtime_doctor_policy.ts`](../src/application/policies/github_runtime_doctor_policy.ts) · [`src/application/policies/doctor_workflow_condition_policy.ts`](../src/application/policies/doctor_workflow_condition_policy.ts) · [`src/application/policies/github_runtime_doctor_presentation_policy.ts`](../src/application/policies/github_runtime_doctor_presentation_policy.ts) · [`src/application/usecases/read_copilot_doctor_workflow.ts`](../src/application/usecases/read_copilot_doctor_workflow.ts) · [`src/data/repository/github/runtime_doctor_repository.ts`](../src/data/repository/github/runtime_doctor_repository.ts) · [`src/infrastructure/composition/github_runtime_doctor_composition_root.ts`](../src/infrastructure/composition/github_runtime_doctor_composition_root.ts) · [`src/application/policies/agent_activity_policy.ts`](../src/application/policies/agent_activity_policy.ts) · [`src/application/policies/doctor_workflow_matrix_policy.ts`](../src/application/policies/doctor_workflow_matrix_policy.ts) +- Tests: [`src/data/repository/__tests__/repository_version_tags_query_repository.test.ts`](../src/data/repository/__tests__/repository_version_tags_query_repository.test.ts) · [`src/cli/__tests__/setup_command_options.test.ts`](../src/cli/__tests__/setup_command_options.test.ts) · [`src/data/repository/__tests__/setup_variable_write_failures.test.ts`](../src/data/repository/__tests__/setup_variable_write_failures.test.ts) · [`src/infrastructure/composition/__tests__/initial_setup_composition_root.test.ts`](../src/infrastructure/composition/__tests__/initial_setup_composition_root.test.ts) · [`src/application/policies/__tests__/setup_questionnaire_policy.test.ts`](../src/application/policies/__tests__/setup_questionnaire_policy.test.ts) · [`src/application/policies/__tests__/setup_configuration_policy.test.ts`](../src/application/policies/__tests__/setup_configuration_policy.test.ts) · [`src/application/policies/__tests__/setup_token_permission_policy.test.ts`](../src/application/policies/__tests__/setup_token_permission_policy.test.ts) · [`src/application/policies/__tests__/setup_remote_facts_policy.test.ts`](../src/application/policies/__tests__/setup_remote_facts_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/setup/__tests__/setup_questionnaire_controller.test.ts`](../src/application/usecases/setup/__tests__/setup_questionnaire_controller.test.ts) · [`src/application/usecases/setup/__tests__/setup_wizard_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_wizard_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_journey_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_journey_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_session_coordinator.test.ts`](../src/application/usecases/setup/__tests__/setup_session_coordinator.test.ts) · [`src/application/usecases/setup/__tests__/setup_credentials_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_credentials_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_token_permissions_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_token_permissions_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/usecases/actions/__tests__/initial_setup_use_case.test.ts`](../src/application/usecases/actions/__tests__/initial_setup_use_case.test.ts) · [`src/application/usecases/actions/__tests__/setup_resource_provisioning.test.ts`](../src/application/usecases/actions/__tests__/setup_resource_provisioning.test.ts) · [`src/infrastructure/__tests__/setup_workspace_adapter.test.ts`](../src/infrastructure/__tests__/setup_workspace_adapter.test.ts) · [`src/infrastructure/__tests__/setup_remote_credential_health_adapter.test.ts`](../src/infrastructure/__tests__/setup_remote_credential_health_adapter.test.ts) · [`src/infrastructure/__tests__/setup_token_permission_query_adapter.test.ts`](../src/infrastructure/__tests__/setup_token_permission_query_adapter.test.ts) · [`src/infrastructure/__tests__/setup_permission_resource_probes.test.ts`](../src/infrastructure/__tests__/setup_permission_resource_probes.test.ts) · [`src/infrastructure/__tests__/setup_projects_probe_page_policy.test.ts`](../src/infrastructure/__tests__/setup_projects_probe_page_policy.test.ts) · [`src/infrastructure/composition/__tests__/setup_token_permissions_composition_root.test.ts`](../src/infrastructure/composition/__tests__/setup_token_permissions_composition_root.test.ts) · [`src/data/repository/__tests__/repository_variables_repository.test.ts`](../src/data/repository/__tests__/repository_variables_repository.test.ts) · [`src/cli/__tests__/setup_presenters.test.ts`](../src/cli/__tests__/setup_presenters.test.ts) · [`src/cli/__tests__/setup_journey_presenter.test.ts`](../src/cli/__tests__/setup_journey_presenter.test.ts) · [`src/cli/__tests__/setup_prompt_rendering.test.ts`](../src/cli/__tests__/setup_prompt_rendering.test.ts) · [`src/cli/__tests__/setup_token_permission_presenter.test.ts`](../src/cli/__tests__/setup_token_permission_presenter.test.ts) · [`src/__tests__/cli.test.ts`](../src/__tests__/cli.test.ts) · [`src/cli/__tests__/setup_terminal_driver.test.ts`](../src/cli/__tests__/setup_terminal_driver.test.ts) · [`src/architecture/__tests__/setup_doctor_boundaries.test.ts`](../src/architecture/__tests__/setup_doctor_boundaries.test.ts) · [`src/tooling/__tests__/documentation_pat_exception_policy.test.ts`](../src/tooling/__tests__/documentation_pat_exception_policy.test.ts) · [`src/infrastructure/__tests__/setup_pat_audit.test.ts`](../src/infrastructure/__tests__/setup_pat_audit.test.ts) · [`src/infrastructure/__tests__/setup_permission_projects_access.test.ts`](../src/infrastructure/__tests__/setup_permission_projects_access.test.ts) · [`src/infrastructure/__tests__/setup_credential_validation_adapter.test.ts`](../src/infrastructure/__tests__/setup_credential_validation_adapter.test.ts) · [`src/infrastructure/__tests__/setup_github_identity_query_adapter.test.ts`](../src/infrastructure/__tests__/setup_github_identity_query_adapter.test.ts) · [`src/infrastructure/__tests__/git_commit_adapter.test.ts`](../src/infrastructure/__tests__/git_commit_adapter.test.ts) · [`src/actions/__tests__/github_action_input.test.ts`](../src/actions/__tests__/github_action_input.test.ts) · [`src/infrastructure/__tests__/setup_permission_cleanup_contract.test.ts`](../src/infrastructure/__tests__/setup_permission_cleanup_contract.test.ts) · [`src/infrastructure/__tests__/setup_permission_actions_cleanup_timing.test.ts`](../src/infrastructure/__tests__/setup_permission_actions_cleanup_timing.test.ts) · [`src/infrastructure/__tests__/setup_permission_operator_actions.test.ts`](../src/infrastructure/__tests__/setup_permission_operator_actions.test.ts) · [`src/infrastructure/__tests__/setup_permission_journal_contract.test.ts`](../src/infrastructure/__tests__/setup_permission_journal_contract.test.ts) · [`src/infrastructure/__tests__/setup_permission_audit_transactions.test.ts`](../src/infrastructure/__tests__/setup_permission_audit_transactions.test.ts) · [`src/infrastructure/__tests__/setup_permission_write_failures.test.ts`](../src/infrastructure/__tests__/setup_permission_write_failures.test.ts) · [`src/infrastructure/__tests__/setup_permission_resource_failures.test.ts`](../src/infrastructure/__tests__/setup_permission_resource_failures.test.ts) · [`src/infrastructure/__tests__/setup_permission_boundary_failures.test.ts`](../src/infrastructure/__tests__/setup_permission_boundary_failures.test.ts) · [`src/infrastructure/__tests__/github_secret_encryption.test.ts`](../src/infrastructure/__tests__/github_secret_encryption.test.ts) · [`src/infrastructure/__tests__/setup_permission_organization_owner.test.ts`](../src/infrastructure/__tests__/setup_permission_organization_owner.test.ts) · [`src/infrastructure/__tests__/setup_pat_bootstrap_transactions.test.ts`](../src/infrastructure/__tests__/setup_pat_bootstrap_transactions.test.ts) · [`src/data/repository/__tests__/repository_actions_sdk_contract.integration.test.ts`](../src/data/repository/__tests__/repository_actions_sdk_contract.integration.test.ts) · [`src/data/repository/__tests__/repository_actions_resource_edge_cases.test.ts`](../src/data/repository/__tests__/repository_actions_resource_edge_cases.test.ts) · [`src/data/repository/__tests__/repository_selected_resource_grants.test.ts`](../src/data/repository/__tests__/repository_selected_resource_grants.test.ts) · [`src/infrastructure/__tests__/setup_permission_read_contract.test.ts`](../src/infrastructure/__tests__/setup_permission_read_contract.test.ts) · [`src/application/usecases/setup/__tests__/setup_workflow_pat_conflict.test.ts`](../src/application/usecases/setup/__tests__/setup_workflow_pat_conflict.test.ts) · [`src/application/usecases/setup/__tests__/setup_workflow_pat_credential_rotation.test.ts`](../src/application/usecases/setup/__tests__/setup_workflow_pat_credential_rotation.test.ts) · [`src/data/repository/__tests__/setup_organization_pat_inventory.test.ts`](../src/data/repository/__tests__/setup_organization_pat_inventory.test.ts) · [`src/cli/__tests__/web_setup_bot_pat_scope_conflict.test.ts`](../src/cli/__tests__/web_setup_bot_pat_scope_conflict.test.ts) · [`src/actions/__tests__/local_setup_apply_contract.test.ts`](../src/actions/__tests__/local_setup_apply_contract.test.ts) · [`src/cli/commands/__tests__/setup_policy.test.ts`](../src/cli/commands/__tests__/setup_policy.test.ts) · [`src/application/usecases/execution/__tests__/execution_issue_number_policy.test.ts`](../src/application/usecases/execution/__tests__/execution_issue_number_policy.test.ts) · [`src/application/usecases/execution/__tests__/setup_execution_workflow.test.ts`](../src/application/usecases/execution/__tests__/setup_execution_workflow.test.ts) · [`src/domain/__tests__/github_runtime_doctor.test.ts`](../src/domain/__tests__/github_runtime_doctor.test.ts) · [`src/domain/__tests__/copilot_command.test.ts`](../src/domain/__tests__/copilot_command.test.ts) · [`src/application/usecases/__tests__/comment_automation_use_case.test.ts`](../src/application/usecases/__tests__/comment_automation_use_case.test.ts) · [`src/application/policies/__tests__/github_runtime_doctor_policy.test.ts`](../src/application/policies/__tests__/github_runtime_doctor_policy.test.ts) · [`src/application/policies/__tests__/doctor_workflow_condition_policy.test.ts`](../src/application/policies/__tests__/doctor_workflow_condition_policy.test.ts) · [`src/application/policies/__tests__/github_runtime_doctor_presentation_policy.test.ts`](../src/application/policies/__tests__/github_runtime_doctor_presentation_policy.test.ts) · [`src/application/usecases/__tests__/read_copilot_doctor_workflow.test.ts`](../src/application/usecases/__tests__/read_copilot_doctor_workflow.test.ts) · [`src/application/usecases/__tests__/runtime_doctor_security_integration.test.ts`](../src/application/usecases/__tests__/runtime_doctor_security_integration.test.ts) · [`src/data/repository/github/__tests__/runtime_doctor_repository.test.ts`](../src/data/repository/github/__tests__/runtime_doctor_repository.test.ts) · [`src/infrastructure/composition/__tests__/github_runtime_doctor_composition_root.test.ts`](../src/infrastructure/composition/__tests__/github_runtime_doctor_composition_root.test.ts) · [`src/application/policies/__tests__/runtime_doctor_agent_activity.test.ts`](../src/application/policies/__tests__/runtime_doctor_agent_activity.test.ts) · [`src/application/policies/__tests__/doctor_workflow_matrix_policy.test.ts`](../src/application/policies/__tests__/doctor_workflow_matrix_policy.test.ts) +- User documentation: [`README.md`](../README.md) · [`docs/how-to-use.mdx`](../docs/how-to-use.mdx) · [`docs/configuration.mdx`](../docs/configuration.mdx) · [`docs/configuration-checklist.mdx`](../docs/configuration-checklist.mdx) · [`docs/authentication.mdx`](../docs/authentication.mdx) · [`docs/development/architecture.mdx`](../docs/development/architecture.mdx) · [`docs/security-operations/operations/provisioning.mdx`](../docs/security-operations/operations/provisioning.mdx) · [`docs/security-operations/operations/troubleshooting.mdx`](../docs/security-operations/operations/troubleshooting.mdx) · [`docs/security-operations/security/credentials.mdx`](../docs/security-operations/security/credentials.mdx) · [`docs/single-actions/workflow-and-cli.mdx`](../docs/single-actions/workflow-and-cli.mdx) · [`docs/security-operations/operations/verification.mdx`](../docs/security-operations/operations/verification.mdx) · [`docs/development/setup-pat-provider-audit-2026-10-06.md`](../docs/development/setup-pat-provider-audit-2026-10-06.md) · [`docs/issues/comment-commands.mdx`](../docs/issues/comment-commands.mdx) ### `local-web-setup-assistant` — Local web setup assistant diff --git a/specs/architecture-quality-and-scalability-hardening.md b/specs/architecture-quality-and-scalability-hardening.md index e58d6d6f4..77d511fa2 100644 --- a/specs/architecture-quality-and-scalability-hardening.md +++ b/specs/architecture-quality-and-scalability-hardening.md @@ -880,6 +880,7 @@ package validation when bundles change, and `graphify update .` after code chang - Decision: quality is satisfied by observable invariants and evidence. Static scores guide review but are never optimized independently of cohesion and risk. +Native doctor parent integration 38277723562 exhausted a Jest worker in the raw-error logging architecture suite. Its unchanged seven assertions now share one real TypeScript program with all production roots and six isolated fixture modules, instead of rebuilding the project per fixture. Real logger/sanitizer symbol resolution and the complete source scan remain mandatory. No new acceptance case is claimed and no threshold, platform or check is reduced. ### Share the real compiler graph in raw-error boundary checks (2026-10-12) diff --git a/specs/catalog.json b/specs/catalog.json index 141e70392..4b9d938d8 100644 --- a/specs/catalog.json +++ b/specs/catalog.json @@ -129,6 +129,7 @@ "src/application/usecases/comment_automation_command_workflow.ts", "src/application/usecases/steps/commit/bugbot/dismiss_bugbot_findings_use_case.ts", "src/application/policies/inactivity_result_counts_projection_policy.ts", + "src/domain/copilot_command.ts", "src/domain/copilot_operation.ts", "src/application/policies/copilot_history_policy.ts", "src/application/policies/copilot_history_projection_policy.ts", @@ -680,19 +681,24 @@ "status": "implemented", "scope": "Plan, validate, provision, and audit a repository installation without exposing credentials", "owner": "Copilot maintainers", - "lastVerified": "2026-10-11", + "lastVerified": "2026-10-13", "specs": [ "specs/setup-configuration-credentials-and-doctor.md", "specs/setup-doctor-architecture-hardening.md", - "specs/setup-pat-permission-guidance-and-verification.md" + "specs/setup-pat-permission-guidance-and-verification.md", + "specs/github-runtime-doctor-command.md" ], "workflows": [ "setup/workflows/agent-cli-provisioning.yml", - "setup/workflows/copilot_credential_health.yml" + "setup/workflows/copilot_credential_health.yml", + ".github/workflows/copilot_issue_comment.yml", + ".github/workflows/copilot_pull_request_comment.yml" ], "entrypoints": [ "src/cli/commands/setup.ts", - "src/cli/commands/doctor.ts" + "src/cli/commands/doctor.ts", + "src/application/usecases/issue_comment_use_case.ts", + "src/application/usecases/pull_request_review_comment_use_case.ts" ], "code": [ "src/domain/setup.ts", @@ -779,7 +785,21 @@ "src/cli/setup_workflow_pat_storage_copy.ts", "src/cli/commands/setup_policy.ts", "src/application/usecases/execution/execution_issue_number_policy.ts", - "src/application/usecases/execution/setup_execution_workflow.ts" + "src/application/usecases/execution/setup_execution_workflow.ts", + "src/domain/github_runtime_doctor.ts", + "src/domain/copilot_command.ts", + "src/application/usecases/comment_automation_command_workflow.ts", + "src/application/policies/copilot_interaction_policy.ts", + "src/application/policies/publication_message_catalog.ts", + "src/application/ports/github_runtime_doctor_ports.ts", + "src/application/policies/github_runtime_doctor_policy.ts", + "src/application/policies/doctor_workflow_condition_policy.ts", + "src/application/policies/github_runtime_doctor_presentation_policy.ts", + "src/application/usecases/read_copilot_doctor_workflow.ts", + "src/data/repository/github/runtime_doctor_repository.ts", + "src/infrastructure/composition/github_runtime_doctor_composition_root.ts", + "src/application/policies/agent_activity_policy.ts", + "src/application/policies/doctor_workflow_matrix_policy.ts" ], "tests": [ "src/data/repository/__tests__/repository_version_tags_query_repository.test.ts", @@ -845,7 +865,19 @@ "src/actions/__tests__/local_setup_apply_contract.test.ts", "src/cli/commands/__tests__/setup_policy.test.ts", "src/application/usecases/execution/__tests__/execution_issue_number_policy.test.ts", - "src/application/usecases/execution/__tests__/setup_execution_workflow.test.ts" + "src/application/usecases/execution/__tests__/setup_execution_workflow.test.ts", + "src/domain/__tests__/github_runtime_doctor.test.ts", + "src/domain/__tests__/copilot_command.test.ts", + "src/application/usecases/__tests__/comment_automation_use_case.test.ts", + "src/application/policies/__tests__/github_runtime_doctor_policy.test.ts", + "src/application/policies/__tests__/doctor_workflow_condition_policy.test.ts", + "src/application/policies/__tests__/github_runtime_doctor_presentation_policy.test.ts", + "src/application/usecases/__tests__/read_copilot_doctor_workflow.test.ts", + "src/application/usecases/__tests__/runtime_doctor_security_integration.test.ts", + "src/data/repository/github/__tests__/runtime_doctor_repository.test.ts", + "src/infrastructure/composition/__tests__/github_runtime_doctor_composition_root.test.ts", + "src/application/policies/__tests__/runtime_doctor_agent_activity.test.ts", + "src/application/policies/__tests__/doctor_workflow_matrix_policy.test.ts" ], "documentation": [ "README.md", @@ -859,7 +891,8 @@ "docs/security-operations/security/credentials.mdx", "docs/single-actions/workflow-and-cli.mdx", "docs/security-operations/operations/verification.mdx", - "docs/development/setup-pat-provider-audit-2026-10-06.md" + "docs/development/setup-pat-provider-audit-2026-10-06.md", + "docs/issues/comment-commands.mdx" ] }, { diff --git a/specs/github-runtime-doctor-command.md b/specs/github-runtime-doctor-command.md new file mode 100644 index 000000000..7264912b1 --- /dev/null +++ b/specs/github-runtime-doctor-command.md @@ -0,0 +1,691 @@ +# GitHub Runtime Doctor Command + +- Status: Implemented and locally validated on the Action-managed #461 branch; live command acceptance pending +- Date: 2026-10-11 +- Catalog capability ID: `setup-and-doctor` +- Last verified: 2026-10-11 (572 full suites, 7,544 passing tests; live acceptance pending) +- Owners: setup-and-doctor capability owner; implementation owners to be confirmed in review +- Scope: Add deterministic `/copilot doctor` replies to issue and pull request conversations using bounded read-only evidence about runtime prerequisites. +- Related issues/PRs: Issue #461 +- Required review gates: product UX, architecture, testing, documentation, security/operations +- Open decisions blocking readiness: None identified in the supplied issue and answers. + +## 1. Executive summary + +A repository participant who can comment may invoke `/copilot doctor` in an issue or pull request. The command checks a fixed set of runtime and workflow prerequisites, reports the status and source of each check, and gives one primary next action. It performs no model call and makes no repair or other repository mutation. Each successful read is evidence only for that read; unavailable evidence is never presented as proof of a missing permission or unhealthy credential. + +```text +/c​​opilot doctor -> bounded independent reads -> one correlated, localized reply with check states, evidence links, and one primary action +``` + +## 2. Problem, current behavior, and evidence + +### 2.1 Problem + +Maintainers investigating a policy-unavailable recommendation result may need to inspect workflow logs and run a local doctor. `/copilot diagnose` is an AI investigation and does not provide a deterministic runtime configuration audit. Participants need safe, inspectable evidence in the conversation without sharing tokens or installing a local CLI. + +### 2.2 Current behavior + +Facts supported by the issue: the prior live round produced a policy-unavailable assessment even though the policy was valid; diagnosis required workflow logs and local doctor; `/copilot diagnose` is AI-assisted. The issue does not establish the exact current command admission implementation or the existing adapter method names. Those must be verified against repository code during implementation. + +### 2.3 Evidence + +- Code/workflow/documentation references: issue #461 identifies the existing setup/doctor policies, comment-automation, semantic GitHub communication, issue-start-and-sdd-readiness, `specs/catalog.json`, and the feature workflow as the relevant owners and sources. Exact file paths and contract details require repository verification. +- Verified implementation baseline on 2026-10-11: `src/domain/github_runtime_doctor.ts` contains the fixed seven-group contract, trusted approval-policy parsing, and deterministic aggregate priority. `/copilot doctor` is recognized and listed in English/Spanish help, but the comment route intentionally returns a sanitized unavailable error until evidence adapters and correlated rendering are composed. No runtime evidence group is currently complete. +- Incidents or concrete examples: a live recommendation assessment reported policy unavailable although the policy was valid; investigation required workflow logs and local doctor. +- External primary sources: not required to define this repository behavior. +- Unknowns: current concrete port APIs, workflow contract shape, existing test commands and thresholds, and documentation route names must be confirmed before implementation. + +### 2.4 Retrospective classification (as-built baselines only) + +Not applicable: this is a prospective specification and does not reconstruct an implemented baseline. + +## 3. Actors, surfaces, and terminology + +| Actor | Goal | Entry point | Visible surfaces | +|---|---|---|---| +| Repository participant | Understand actionable runtime prerequisites | Explicit `/copilot doctor` comment | Issue or PR reply and linked evidence | +| Maintainer/operator | Fix a proven prerequisite or investigate unavailable evidence | Reply links and workflow run | Repository settings/policy source and run logs | +| Workflow | Gather bounded evidence and publish one response | Existing comment event workflow | One reconciled issue/PR comment | + +“Evidence group” means one bounded prerequisite assessment in the fixed order in §6. “Unavailable” means the command could not establish a fact because a read or sample was absent or failed. “Blocked” means a successful, authoritative read or pure policy established an invalid required prerequisite. “Current PR identity” means the exact PR identified by the event and verified through a canonical current PR read, including a final head freshness check. + +## 4. Goals, non-goals, and fixed invariants + +### 4.1 Goals + +1. Provide a deterministic, read-only runtime audit in issue and PR conversations. +2. Preserve independent successful results when another evidence group is unavailable. +3. Give each result a distinct state, inspectable source/link, and safe next action. +4. Reuse existing policies and semantic read ports when their trust and credential roles match. + +### 4.2 Non-goals + +1. Model invocation, diagnosis, repair, setup provisioning, or mutation of files, labels, variables, secrets, projects, checks, or reviews. +2. Testing credential write access, secret values or health, organization grants unavailable to the runtime, or local setup-operator PAT behavior in an Action run. +3. Certifying CI, coverage, producer health, write grants, or the entire runtime credential from names or successful reads. + +### 4.3 Fixed product/safety invariants + +1. Explicit-command admission and bot-loop guards remain in force; any participant permitted by GitHub to comment may invoke the command. No organization-membership gate is added. +2. No new configuration flag, credential, or permission requirement is introduced. +3. Never infer a missing grant from a denied, absent, malformed, or incomplete read. Never expose tokens, secret values, raw provider diagnostics, or guessed zero values. +4. PR identity comes from the event PR marker and exact PR number, then canonical current identity/head reads. Never search other PRs or infer a linked issue from an equal number. +5. A successful policy read proves only that read. A declared `producerAttested` value is input, not independently verified producer health. + +## 5. Current versus proposed product journey + +| Stage | Current | Proposed | User/operator effect | +|---|---|---|---| +| Request | Runtime audit is not described as a deterministic conversation command | Explicit `/copilot doctor` follows existing admission and bot-loop rules | Participant requests a safe audit in context | +| Evidence | Incident investigation may require logs and local doctor | Fixed seven-group bounded reads and existing pure policies | Successful facts remain useful even when other reads fail | +| Reply | No command-specific report contract is established by issue evidence | One localized, correlated reply with state, source, link, and primary action | Participant can inspect and act without token sharing | + +```mermaid +flowchart LR + A[Explicit issue or PR command] --> B[Validate target and gather independent bounded evidence] + B --> C[Apply fixed state priority and choose one action] + C --> D[Reconcile one localized correlated reply] +``` + +Text equivalent: explicit issue/PR command -> independent bounded reads and policy evaluation -> one prioritized action -> one reconciled reply. + +## 6. Functional behavior and state model + +### 6.1 Happy path + +1. The existing comment entrypoint admits an explicit `/copilot doctor` request and rejects bot loops using current guards. +2. The application identifies an issue target or exact PR target. For a PR it verifies canonical identity and current head; a final freshness check detects a changed head. +3. The application evaluates the seven groups in §6.2. It continues independent reads after failures and retains safe results. +4. Pure aggregation assigns each group `pass`, `warning`, `blocked`, `unavailable`, or `not-applicable`. Overall severity is `blocked`, then `unavailable`, then `warning`, then `pass`; not-applicable checks are excluded. If all checks are not applicable, overall state is not applicable. +5. The presenter chooses the first actionable check in the fixed group order within the highest severity and produces one localized reply through the current reply reconciler. + +### 6.2 Evidence groups and alternatives + +The fixed order is: + +1. Effective issue-workflow and dependency configuration, validated by existing pure policies. +2. Pre-branch SDD eligibility and repository catalog/template prerequisites. Help/exempt issues mark these checks not applicable. +3. Linked-branch readiness when the current issue requires branch work. Missing optional linked issue context on a PR does not invalidate the PR; issue checks depending on it are not applicable. +4. Installed issue, comment, and PR workflow contracts read from one verified default-branch revision. Inspect documented structural contracts only; do not infer token write grants from workflow contents. +5. Approval policy accessibility and strict validity, using the existing effective repository/organization Actions Variables policy reader and strict `PR_APPROVAL_POLICY` v1 parser, including established precedence. +6. Declared approval prerequisites when policy mode is active. Valid `off` makes active-assessment checks not applicable; valid `recommend` or `guarded` reports declared settings. A verified missing value retains existing off semantics. Never fall back to an injected variable after trusted read failure. +7. Exact current PR identity/head when the target is a PR. For issue targets this group is not applicable. + +Proven invalid required configuration, invalid policy, or failed required contract is blocked. Denied/missing ports, malformed provider response, network error, incomplete sample, or failed canonical identity/head read is unavailable, never proof of missing permission. A failure to read policy is unavailable; a successfully read malformed policy is blocked. + +### 6.3 State machine and delivery behavior + +| State | Entered when | User-visible meaning | Allowed next states | Recovery/owner | +|---|---|---|---|---| +| Gathering | An admitted request starts | Checks are being evaluated | Reported, unavailable report | Application use case | +| Reported | Checks are summarized | Current evidence and next action are shown | Updated on same delivery replay | Reply reconciler | +| Partial report | One or more checks are unavailable but other results exist | Some evidence is usable; remaining checks need investigation | Updated on replay or new request | Participant/operator | +| Stale target | PR head changes before final freshness check | Current PR evidence cannot be confirmed | Unavailable report | Participant retries after inspecting PR | + +The existing correlated reply identity is exact repository, conversation target, and source comment ID. Redelivery/replay for the same delivery updates or reuses one reply. A new user request creates a new correlated reply. No extra cards or notifications are published. Existing lifecycle and waiting labels are preserved. No other durable state schema is introduced by this feature. + +## 7. User-facing configuration + +There is no new user-facing configuration. The command uses existing effective configuration and policy semantics. Recommended configuration is the repository’s existing valid setup and approval policy; a meaningful alternative is a valid approval policy mode of `off`, which makes active-assessment prerequisites not applicable. No new override is permitted. + +- Existing precedence and operation-time reread behavior must be preserved by the owning readers and policies; implementation must verify their actual contract. +- Missing policy value follows existing off semantics only when the trusted reader successfully establishes absence. Read failure is unavailable and must not use an injected fallback. +- Unknown, malformed, or retired policy values are handled by the existing strict parser; malformed successfully read policy is blocked. +- Safety invariants in §4.3 are not configurable. + +## 8. Clean Architecture design + +### 8.1 Responsibilities and dependency direction + +| Layer/boundary | Owns | Must not own/import | +|---|---|---| +| Domain/pure policy | Check state, severity priority, applicability, action selection, and validation decisions | GitHub SDK, provider DTOs, network or rendering | +| Application | Doctor use case, fixed evidence plan, independent reads, aggregation inputs, immutable outputs | Concrete SDK/process calls, provider DTOs, mutations | +| Semantic ports | Narrow runtime configuration, classification, repository content, linked-branch readiness, canonical PR, approval-policy reads, and reply reconciliation capabilities | Provider response models or policy decisions | +| Data/provider adapters | Mapping GitHub/runtime responses and provider errors into semantic results | Product state priority or permission inference | +| Infrastructure/composition | Wiring existing runtime credential and installed capabilities | Business policy or local operator PAT fabrication | +| Entrypoints | Parse/adapt comment event and enforce existing admission/loop guards | Duplicate orchestration or target-number inference | +| Presentation | Localized bounded report and sanitized links | Reads, mutations, or state transitions | + +```mermaid +flowchart LR + E[Comment event entrypoint] --> A[Doctor application use case] + A --> D[Pure doctor and existing setup policies] + A --> P[Typed semantic read ports] + I[Existing runtime adapters] --> P + A --> R[Existing correlated reply reconciler] + A --> V[Localized report model and renderer] +``` + +Text equivalent: event entrypoint calls the application; application combines pure policy with typed read ports, then supplies a view model to the renderer and the existing reply reconciler. + +### 8.2 Contracts, state, and trust boundaries + +- Pure decisions: group applicability, check classification from explicit semantic outcomes, severity priority, and primary action selection. +- Application contracts: immutable request target, bounded evidence results, check view models, and report outcome. No provider DTOs cross the application boundary. +- Semantic ports: reuse existing narrow runtime configuration, issue classification, repository content, linked-branch readiness, canonical PR, approval-policy read, and correlated reply capabilities only where trust and runtime credential roles match. +- Durable state/schema ownership: existing comment correlation/reconciler owns reply identity. No feature-specific durable state is required. +- Concurrency/idempotency: use existing delivery correlation and reconciler; recheck PR head freshness before publication. A stale head produces unavailable target evidence. +- Trusted and untrusted inputs: GitHub event identity and authoritative read results are validated at adapters; comments, repository content, provider strings, and workflow files are untrusted data. Workflow files are read, parsed for documented structure, and never executed. +- Provider error mapping: permission denied, missing port, malformed response, network failure, and incomplete sample map to unavailable with safe category only. Do not expose raw diagnostics or synthesize absent values. + +### 8.3 Executable architecture constraints + +Automated checks MUST enforce no provider SDK/DTO imports in domain or application contracts, no dependency from domain to adapters or presentation, no write port in the doctor use case, and no workflow-content execution path. Contract tests MUST parse workflow structure at one authoritative default-branch revision. Existing architecture and dependency checks remain required. + +## 9. UI/UX and content contract + +### 9.1 Information hierarchy + +The reply starts with overall state, completed facts, next transition, one primary action, impact, and inspectable links. Technical details follow in a compact details section. Each check shows a textual state, evidence source/link when available, and short safe detail. No raw provider diagnostics or guessed zeros appear. + +### 9.2 Representative primary view and variants + +```markdown + + +# 🩺 Runtime doctor: Partially available + +> **Current status:** Some runtime prerequisites could not be read. +> +> **Action required:** Inspect the [workflow run](RUN_URL) and documented read access, then retry `/copilot doctor`. + +## Checks + +- **Pass — Effective workflow configuration:** Validated by the existing setup policy. [Configuration source](SOURCE_URL) +- **Unavailable — Approval policy:** The policy could not be read, so its validity is unknown. [Workflow run](RUN_URL) +- **Not applicable — PR identity:** This command was run in an issue. + +Other checks completed; unavailable evidence does not establish a missing permission. + +
+Technical details + +Safe check categories and authoritative revision or PR links only. + +
+``` + +Concrete state variants: + +- Pending/no action: “Runtime doctor is collecting the bounded checks. No repository changes are being made.” A pending comment is only used if existing entrypoint lifecycle already publishes one; no second comment is created. +- Action required: “Blocked — the required SDD template prerequisite is invalid. Correct the linked template/catalog entry, then retry.” Link to the specific source. +- Blocked/failed before side effects: “Blocked — the policy was read but is invalid. Correct the policy at [policy source](URL), then retry. No repository changes were made.” +- Partially complete: use the representative view; retain pass facts and identify unavailable groups without implying failure of successful reads. +- Complete: “Pass — all applicable checks passed. No action required.” List evidence sources and explain excluded not-applicable checks. + +### 9.3 Issue, PR, and comment behavior + +- Deterministic title: “Runtime doctor: ``”; purpose is bounded runtime prerequisite evidence. +- Merge/close/retry does not trigger repair. A new explicit command performs a new audit. Existing PR and issue lifecycle behavior remains authoritative. +- Durable marker and idempotency use the existing correlated reply identity; replay updates/reuses the same reply. +- One correlated reply per source comment/delivery; no additional cards or notifications. +- Labels and checks are not modified and are not used as inferred evidence. +- Links point to authoritative source files/settings where safe, exact PR, and relevant workflow run. Never fabricate links or infer a linked issue. + +### 9.4 Accessibility, localization, and responsive behavior + +English and Spanish catalogs are complete and use existing atomic fallback. Status is text, not color or emoji alone. Markdown remains readable on narrow screens, uses logical headings and descriptive links, and avoids wide tables. Escape/sanitize untrusted Markdown, HTML, mentions, commands, markers, and URLs according to existing publication policy. Render only allowlisted repository/GitHub destinations; reject unsafe schemes. Provide textual explanations for all icons and status distinctions. + +## 10. Failure, recovery, and cleanup + +| Failure/partial state | User impact | Retained facts | Automatic retry | Required action | Cleanup | +|---|---|---|---|---|---| +| Trusted policy read denied or unavailable | Policy status unknown | Other successful checks | Existing bounded workflow retry only | Inspect run and documented read access; retry | None | +| Policy read succeeds but strict parse fails | Required policy blocked | Read source and other checks | No automatic repair | Correct policy and retry | None | +| Workflow sample incomplete or malformed | Contract evidence unavailable | Verified checks from same operation | Bounded existing retry if supported | Inspect run/source and retry | None | +| Required contract proven invalid | Blocked prerequisite | Contract revision and other checks | No | Correct contract at linked source | None | +| PR identity/head read fails or head becomes stale | PR evidence unavailable | Non-PR independent checks | No blind repeat | Inspect exact PR and retry | None | +| Reply publication fails | User may not see the report | Safe operation result may be logged with correlation | Existing reconciler behavior | Inspect workflow run | No duplicate reply on replay | + +User-facing error order is impact, cause, action, retained state. Successful reads remain distinct from failed reads. Cleanup is not applicable because the use case performs no repair or repository mutation. + +## 11. Security, permissions, and privacy + +1. Use only installed runtime credential capabilities and existing read ports. GitHub’s ability to access a private conversation remains authoritative; do not add organization-membership checks. +2. Treat comments, repository files, workflow contents, and provider strings as untrusted. Parse documented structure only; never execute file contents. +3. Never disclose tokens, secret values, credential health, raw provider diagnostics, or local setup-operator PAT details. Do not claim write permission, producer health, or whole-credential validity. +4. Preserve explicit command admission and bot-loop guards. Correlate publication to exact repository, conversation target, and source comment; revalidate exact PR identity/head. +5. Make no mutations to files, labels, variables, secrets, projects, checks, reviews, or approval state. + +## 12. Observability and operational UX + +- User-facing state: overall state and independent check states, source/link, and one primary action. +- Job Summary/operator evidence: existing workflow summary/log facilities may record correlation and safe categories; exact existing facility is to be verified. Never log secrets or raw provider diagnostics. +- Logs, metrics, and correlation: use existing workflow run and reply correlation identifiers; do not create a new externally visible event stream. +- Pending external dependency versus workflow failure: unavailable evidence identifies the safe category and run link; it is not described as a proven missing grant. +- Rate-limit/retry behavior: follow existing read adapter and workflow retry bounds; no unbounded polling. Exact bounds must match current repository policy. +- Noise/comment budget: one correlated reply, updated on replay; no extra cards or notifications. + +## 13. Compatibility, migration, rollout, and rollback + +No persisted feature schema or configuration migration is introduced. Existing in-flight comment lifecycle, waiting labels, locale selection, command admission, and reply reconciliation remain in force. Deploy through the installed feature workflow and its in-progress label before implementation is considered ready, as required by the issue context; do not introduce a feature flag. Rollback removes command routing and presentation while retaining existing comments as historical records. No irreversible repository effects are performed. + +## 14. Testing strategy and numeric budget + +The minimum is **52 distinct cases**, derived from seven evidence groups, multiple provider failure classes, replay/identity risks, and localized user-visible states. A parameterized row counts only when it exercises a distinct behavior. + +| Area | Minimum distinct cases | Behaviors/risks covered | +|---|---:|---| +| Domain/configuration/pure planning | 12 | State mapping, applicability, strict policy states, off/recommend/guarded, precedence, severity, action selection | +| State/application/idempotency/races | 10 | Independent reads, partial reports, replay reuse, new request identity, stale PR head, ordering, cancellation/error retention | +| Adapters/provider contracts | 8 | Denied, missing port, malformed response, network failure, incomplete sample, trusted absence, canonical PR mapping | +| Workflows/setup/schema | 6 | One verified default revision, structural workflow contract, existing admission/loop guards, no permission inference, installed workflow integration | +| UI/UX/localization/sanitization | 8 | Five states, English/Spanish fallback, safe links, mention/Markdown/HTML sanitization, compact mobile-readable content | +| Integration/security/compatibility | 8 | No-write/no-model guarantee, exact PR identity, no issue-number inference, secret non-disclosure, authorization, replay and existing lifecycle | +| **Total** | **52** | No double counting | + +Keep repository-wide coverage budgets. New pure policy MUST meet at least 90% branch coverage or the repository’s higher existing threshold. Changed modules MUST meet existing thresholds without reducing repository coverage. Use table-driven tests, deterministic IDs/clocks and fakes; no live GitHub services or real waits. Workflow checks parse structure rather than grep prose. Golden reply fixtures are supplemented by semantic assertions for headings, status, action, links, locale, and sanitization. Manual UX evidence: reviewers inspect narrow-screen rendering in English and Spanish and confirm the primary action and evidence links are understandable without logs. + +## 15. Documentation and discoverability + +| Audience | Artifact/page | Required content | Validation/navigation | +|---|---|---|---| +| User | Existing command/help documentation | `/copilot doctor` use, states, example, no-write boundary | Help catalog and route/link validation | +| Setup owner | Setup/doctor documentation | Seven evidence groups, existing policy precedence, unavailable vs invalid | Contract fixtures and docs links | +| Operator | Troubleshooting/runbook | State interpretation, run inspection, retry, exact PR identity, safe recovery | Decision-tree review and route validation | +| Contributor | Architecture/spec and catalog | Ports, trust boundaries, correlation, test budget, workflow contracts | Specification validator and architecture checks | + +Document that local setup operator PAT and Action runtime PAT are distinct and that the former is not available in the Action run. Examples must be checked against reply fixtures and existing policy fixtures. Update catalog evidence and generated catalog view as required by repository specification workflow. + +## 16. Acceptance scenarios + +1. Given an admitted issue command and all applicable valid reads, when `/copilot doctor` runs, then one localized reply reports pass, links to sources, and says no action is required. +2. Given a verified invalid required configuration, when the command runs, then that check and overall report are blocked with the linked source and a correction action. +3. Given a valid approval policy, when the trusted policy reader succeeds, then parser and policy-owned precedence determine the result; an injected value does not override a trusted read. +4. Given a denied policy read and successful unrelated reads, when the command runs, then policy is unavailable, successful checks remain visible, and no missing permission is claimed. +5. Given a successfully read malformed policy, when parsing completes, then policy is blocked; this differs from unavailable read evidence. +6. Given a valid `off` policy, when active approval prerequisites are considered, then those checks are not applicable; a missing value is off only when the trusted read proves absence. +7. Given an issue exempt from pre-branch SDD checks, when the audit runs, then those checks are not applicable and do not lower overall severity. +8. Given a PR with no optional linked issue, when the audit runs, then PR checks proceed and linked-issue checks are not applicable. +9. Given a PR event with exact PR identity, when canonical identity succeeds but the head changes before publication, then PR evidence is unavailable and no other PR is searched. +10. Given issue and PR numbers that happen to match, when the command runs, then no issue is inferred from the PR number. +11. Given two workflow contract reads from different revisions, when the evidence is assembled, then the sample is unavailable or rejected; a report never combines revisions as one verified contract set. +12. Given a provider response containing unsafe Markdown, mentions, HTML markers, or an unsafe URL, when rendered, then content is sanitized and no unintended mention or link is emitted. +13. Given the same delivery is replayed, when reconciliation runs, then the same correlated reply is updated/reused; a new source comment creates a new reply. +14. Given a participant allowed by GitHub to comment but not known to be an organization member, when they invoke the command, then existing admission rules apply and no new membership gate is added. +15. Given any result state, when rendered in English or Spanish, then atomic locale fallback produces readable text status and exactly one primary action. +16. Given the completed implementation, when documentation and architecture checks run, then catalog/spec validation, workflow structural contracts, test budget, and no-write/no-model checks pass. + +## 17. Requirements traceability + +| Requirement | Policy/use case/adapter/presentation | Test or evidence | Documentation | +|---|---|---|---| +| Seven bounded groups and applicability | Application plan and setup/doctor policies | Pure policy and orchestration cases | Setup/doctor reference | +| Partial results and unavailable semantics | Application result mapping | Read failure matrix | Operator troubleshooting | +| Approval precedence and strict parse | Existing policy reader/parser | Trusted absence, failure, malformed, mode cases | Setup/doctor reference | +| Exact PR identity and freshness | Event adapter and canonical PR port | Identity/head/replay integration cases | Operator troubleshooting | +| One correlated localized reply | Existing reconciler and renderer | Replay, locale, semantic UI cases | User command/help docs | +| No mutation, model, or secret disclosure | Use case ports and output sanitizer | Security/no-write/no-model tests | User and contributor docs | +| Architecture and workflow boundaries | Ports/adapters and workflow contract parser | Architecture and workflow checks | Contributor spec | + +## 18. Implementation sequence + +1. Verify repository standard, catalog capability, owning setup/doctor SDD, comment-automation and semantic-publication contracts, issue-start-and-sdd-readiness, and installed feature workflow. Keep this companion proportional and preserve the owning contract. +2. Define immutable application result and semantic port contracts; add architecture constraints forbidding write/model dependencies. +3. Implement pure status, applicability, severity, and action policies with risk-derived tests. +4. Orchestrate independent reads, policy validation, exact PR freshness, and partial-result retention. +5. Reuse existing adapters and correlate publication through the existing reply reconciler; add structural workflow contract checks. +6. Implement localized presentation, safe links, sanitization, and golden/semantic fixtures. +7. Complete user, setup, operator, and contributor documentation; update catalog evidence and generated artifacts as required. +8. Run repository validation, coverage, workflow/security checks, and manual English/Spanish narrow-screen UX review before readiness. + +## 19. Definition of Done + +Implementation status: **implemented in the managed feature branch; live +promotion and browser acceptance pending**. The runtime composition binds only +metadata queries using the installed runtime credential. The command performs +all seven groups, retains independent successful checks, validates the effective +profile/start settings using the owning policies, and inspects the dependency +reference already resolved by runtime setup. It does not certify an entire remote +dependency graph from that setting. + +The canonical policy reader is shared with the approval observer and retains +repository-over-organization precedence. Its read-only method never consults an +injected variable after failure. Files use six fixed paths, 200,000 decoded UTF-8 +bytes per file, one exact 40-hex default-branch revision, and a final revision +fence. Workflow YAML uses the JSON schema and is never executed. Native branch +queries retain their 100-node complete-page boundary and refuse ambiguous, +foreign or moved refs. Exact PR identity uses the existing canonical read policy +and a final head fence. Group states and actions are closed; raw source text, +provider errors and credentials never enter the report. + +The fixed 52-case floor is retained. Original implementation evidence covered **167 distinct +feature cases** without double counting existing observer tests: 57 domain and +configuration cases (the original ten domain cases plus fifteen new prerequisite +cases); 23 application cases; 46 provider cases (39 metadata adapters plus seven +new policy-reader cases); ten workflow structural cases; 17 presentation cases; +and 14 integration cases (eight command/security, three composition, two +semantic correlation cases, and one real issue-route configuration regression). The nine-suite focused run passed 138 tests; the additional real issue-route suite passed 18 tests, +including compatibility tests outside this 167-case feature budget. The full managed-branch suite passed 569 suites and 7,454 tests (35 existing +platform skips), with every repository coverage budget and setup ledger check +passing. Doctor bypasses only the incompatible-configuration mutation guard and SDD-continuation getter so it can report their blocked state; mutation commands retain those gates. Generated bundles, live issue/PR commands and narrow-screen browser +acceptance must be recorded separately before final readiness. + +- [ ] Every normative requirement has acceptance and traceability. +- [ ] Architecture boundaries are implemented and automatically enforced; doctor has no mutation or model capability. +- [ ] Numeric 52-case minimum, distribution, coverage requirements, and repository budgets pass. +- [ ] Existing policy defaults, precedence, validation, and trusted-read semantics are preserved. +- [ ] Primary report states and content examples are implemented and reviewed. +- [ ] English/Spanish, fallback, accessibility, responsive behavior, sanitization, and one-reply noise budget pass. +- [ ] User/setup/operator/contributor documentation is discoverable and examples match fixtures. +- [ ] Partial evidence, failures, retry, idempotency, PR freshness, security, and cleanup pass. +- [ ] Required workflow structural contracts and repository validation commands pass. +- [ ] `specs/catalog.json` evidence and `specs/CATALOG.md` are current and `pnpm run validate:specifications` passes. +- [ ] Feature workflow and in-progress label requirements are satisfied before implementation readiness. +- [ ] No readiness-blocking decision remains unresolved. + +## 20. References and decisions + +- Primary sources: Issue #461 and supplied answers Q1–Q7; repository standard and template as provided in the application task. +- Related specs/ADRs/issues/PRs: setup-and-doctor owning specification; comment-automation; semantic GitHub communication; issue-start-and-sdd-readiness; exact paths and current contract details to be verified in repository. +- Decisions: preserve existing owning contract and use a focused companion; no new configuration or permission; independent partial reads; blocked/unavailable distinction; exact PR identity; one correlated localized reply. +- Follow-up work outside this spec: any repair workflow, credential write testing, local setup provisioning, or broader credential-health certification. + +### Live dogfooding correction: ambiguous content absence + +Bugbot review `38162038624` on PR #466 found that a contents API 404 was falsely reported as proven missing. Human `/copilot fix` comment `6112079942` correctly fetched the current managed head but the Windows agent produced no safe changes; run `38162629567` failed without a commit. That failure is retained as an unfulfilled agent-write test, not certified repair. + +The managed correction now proves absence independently: a lazily shared read resolves the exact default commit to its tree, then reads a complete recursive tree at that immutable tree SHA. It rejects changed identities, truncation, duplicate/unsafe/malformed entries, and metadata beyond 10,000 entries. Only verified absence may become `missing`; a file present in the tree with contents 404, denied proof, or incomplete proof is unavailable. The six fixed content paths and 200,000-byte decoded-file budget stay unchanged; at most two additional immutable reads are shared across all 404s. Eleven distinct provider cases cover the live finding and proof boundaries. Fresh full validation and a live review of this correction remain pending. + +The managed absence correction passed 572 full suites and 7,544 tests, with 35 existing platform skips (7,579 total). All existing budgets passed; the changed provider reached 100% lines/statements/functions and 95.53% branches. This evidence validates the manual correction, not a successful Windows agent write. The second read-only diagnosis with explicit `login=false` also reported rejection before process startup, so login handling is not established as the cause and no runtime permission was relaxed. + +### Workflow eligibility correction from live Bugbot review + +Review `4242759214` correctly found that any Action step in any job could make +the workflow group pass. The group now requires a job and Action step eligible +for the expected event. Disabled jobs/steps, event-name contradictions and empty +activity filters are blocked. A bounded three-valued condition policy recognizes +booleans, event-name comparisons, negation, parentheses and AND/OR; runtime facts +or unsupported expressions remain unavailable. YAML/expressions are never +executed. A separate enabled Action job can establish evidence independently of +a disabled candidate. This structural check does not certify a future job run, +runner availability or all configured activity filters. + +The repair adds 32 distinct pure-policy cases (25 condition cases and 7 workflow +scenarios), increasing this feature from 135 to 167 cases. Fresh hosted checks +and a new independent review remain required before promotion. + +The workflow eligibility repair passed 573 full suites and 7,576 tests, with 35 existing platform skips (7,611 total). Its condition policy reached 100% lines/functions, 98.33% statements and 92.64% branches. Specification/catalog, documentation, lint, graph update and bundle compilation passed; hosted verification and review remain separate live gates. + +### Native synchronization and independent review + +The Action resolved both the ambiguous-absence finding `4242617946` and the workflow-eligibility finding `4242759214` in subsequent independent review. Run `38167519200` on `windows-intel-runner-2` completed 573 suites and 7,596 tests, with 20 platform skips (7,616 total), validated the integrated branch and published synchronized head `00fef5bdcc31c010e4755e696512ed973df6c2f7`. The correlated receipt is PR comment `6113381932` for request `6112743331`. The contributor checkout confirms the common parent `c47f612f3fceac6ae54908f62bdad9977da06a4d` is an ancestor and validates the published bundles. + +This is successful native Windows verification and an Action-owned remote synchronization. It does not establish successful Codex sandbox commands on the Windows service account, nor live `/copilot doctor` acceptance before promotion. Hosted CI and a fresh review of the synchronized head remain separate gates. + +### Native current-parent integration and activity isolation (2026-10-12) + +The Action synchronized the exact managed branch as `dd3921f44f51edce6b79a46a52412517497d01ed` in run `38254943897` on `apple-intel-runner-3`, passing 580 suites, 7,902 tests and all five configured checks with the existing 35 skips. No manual parent merge or foundation copy was used. + +The integrated shared activity policy treated the newly recognized `doctor` command as agent-capable, despite the query having no model port. Two actual issue/review-comment policy contracts failed before the correction. `doctor` is now included in the deterministic command set, so it does not inspect agent configuration or acquire activity labels. These two cases extend the current distinct feature inventory from 167 to 169 without changing the original 52-case floor or recounting shared activity tests. Fresh current-head review and live command acceptance remain required. + +### Bound compiler fixture memory after native worker exhaustion (2026-10-12) + +Native parent synchronization `38277723562` on Ubuntu resolved the conflict and passed 7,955 tests, but Jest reported “worker ran out of memory and crashed” while loading `raw_error_logging_boundaries.test.ts`. Its repository scan and six taint/sanitizer fixtures each rebuilt the full TypeScript project. The architecture test now constructs one real compiler/checker graph containing the unchanged project and all six isolated fixture modules. Every original assertion still resolves real production logger and sanitizer symbols; no mock, skip, worker/heap adjustment, coverage or platform reduction is introduced. The 169-case doctor inventory is unchanged because this is shared test infrastructure rather than a new capability. A fresh native integration must certify the correction. + +### Native prerequisite and absence-proof findings (2026-10-12) + +Native review found three concrete defects: `4249698862` dropped unavailable linked refs before claiming absence or uniqueness; `4249700033` accepted non-string dependency values through regular-expression coercion; `4249700043` accepted an empty capability catalog despite the canonical validator requiring a non-empty array. The provider MUST keep any null association, missing/null ref or mixed unavailable/valid page unavailable, without a fallback live-ref read. Only an actually empty complete page proves absence. Effective dependency branches MUST be strings before syntax validation. The prerequisite check MUST reject an empty catalog. + +Ten additional distinct cases cover four incomplete native pages, five coerced non-string branch values and the canonical empty-catalog contract. The own feature inventory increases from 169 to 179; the original 52-case floor, six fixed content reads, permissions, query-only ownership and all coverage gates remain unchanged. Fresh native review, parent integration and live command acceptance remain required. + +Local verification of these findings passed 581 suites and 7,914 tests with 35 configured platform skips (7,949 total), every unchanged global/module coverage budget and 308/350 setup acceptance with 42 explicitly open. Types, lint, documentation, specification/catalog validation and graph update passed. + +### Native nullable bodies and malformed report/workflow boundaries (2026-10-12) + +Native findings `4251088780`, `4251088791` and `4251088796` exposed three additional boundaries. A valid native null issue body MUST normalize to an empty body while preserving independently verified issue fields; missing or malformed body values remain rejected. Aggregation MUST reject an unknown check group rather than drop blocked evidence, with a fixed error that excludes the supplied group. A present workflow activity filter MUST be a non-empty array of non-empty strings; scalar, null, object or mixed non-string forms are blocked. These checks neither execute workflow content nor certify write permission. + +Six new cases reproduced all three findings: one native null body, one unknown blocked group among otherwise not-applicable groups and four malformed activity-filter shapes. The own feature inventory is 185 distinct cases, with the original 52-case floor and all read/provenance/coverage boundaries retained. Native review, parent recovery and post-promotion live acceptance remain required. + +Local verification of the second native findings passed 581 suites and 7,920 tests with 35 configured platform skips (7,955 total). All global/module gates, types, lint, documentation, specifications and graph update passed. Bundles were rebuilt before staging. + +### Conservative mixed-type workflow expressions (2026-10-12) + +Native finding `4251251731` identified strict mixed-type equality that contradicted GitHub's documented numeric coercion. This bounded interpreter deliberately does not model mixed-type coercion; it MUST return unknown for such comparisons, rather than certify the job as disabled or active. Same-type supported literals and existing three-valued boolean combinations remain unchanged. A workflow whose only eligible candidate depends on such a comparison is unavailable. See [GitHub expression operators](https://docs.github.com/en/actions/reference/workflows-and-actions/expressions#operators). + +Four additional cases reproduced the old classification: mixed equality in both operand orders, mixed inequality, and the complete workflow eligibility projection. The own feature inventory increases from 185 to 189 distinct cases; the original 52-case floor, 1,000-character/32-depth bounds, no-eval rule and all coverage gates remain unchanged. Native review and parent/live acceptance remain required. + +Local mixed-type verification passed 581 suites and 7,924 tests with 35 configured platform skips (7,959 total). All global/module gates, types, lint, documentation, specifications and graph update passed. Generated bundles are included. Native review and parent/live acceptance remain required. + +### Required entry activities (2026-10-13) + +Native finding `4251622516` showed that a syntactically valid `types: [deleted]` +for `issue_comment` could pass even though a newly posted doctor request cannot +start that workflow. The parser MUST require `labeled` for issue start, `created` +for new issue-comment commands, and `opened` for PR enrichment when the corresponding +explicit activity filter is present. Omitted filters retain GitHub's all-activity +behavior. Unknown dynamic conditions remain unknown; this does not execute YAML, +expand permissions, or certify runtime/runner health. + +Three distinct workflow regression cases failed before the fix and cover these +three entry points, including acceptance of their required activities. The own +inventory is **192 distinct feature contracts** (189 retained plus these three); +the original 52-case floor and all coverage guards remain in force. +`docs/issues/comment-commands.mdx` describes the operational check and the +conservative dynamic-condition limitation. Primary event semantics: +https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows. +Native current-head review and live promotion remain pending. + + +### Catalog prerequisites and literal branch references (2026-10-13) + +Finding `4253464300` remained valid after being automatically marked obsolete: +a catalog with missing canonical metadata or nonexistent specifications could +still pass. Finding `4253483689` also showed invalid Git branch components such +as `develop/`, `develop//main` and `develop/.` being certified. Thirteen of the +14 initial regressions failed before the correction; focused verification now +passes 148 cases across configuration, catalog, provider, use case and domain. + +Prerequisites MUST require the canonical nonempty id/title/status/scope/owner/date +metadata, supported status and real calendar date, unique capability identities +and titles, six evidence arrays with required nonempty values except workflows, +normalized paths inside their declared boundaries, and unique specification +ownership. Every referenced evidence path MUST resolve to a regular file in the +complete immutable Git-tree inventory at the same default revision. Tree or +symlink entries cannot certify a file. This is bounded structural/evidence +validation; as-built document contents and uncatalogued specification discovery +remain the full repository validator's responsibility rather than a six-file +remote content claim. + +The complete tree proof is now required for successful catalog evidence as well +as ambiguous content 404s. It retains the fixed six content reads, 200,000-byte +per-file budget and 10,000-entry tree ceiling. Truncation, denied proof, malformed +identity or moved default head remain unavailable. Configuration, default branch +and linked branch use one pure bounded literal-reference decision, rejecting +empty, dot-prefixed, dot-terminated and `.lock` path components. No process or +sandbox invocation is added to doctor. + +The own inventory is **224 distinct feature cases**: 192 retained + 14 initial +regressions + 14 canonical-structure/real-catalog contracts + four inventory/ref +adapter cases. No shared cases are counted again. Full gates, native review and +live acceptance remain pending; a structural pass still cannot certify model or +tool execution, runner availability, or write permissions. + +Native finding `4254657957` exposed that the Contents API can return a symlink target as a file, as documented by [GitHub](https://docs.github.com/en/rest/repos/contents#get-repository-content). Every successful content read MUST also prove a regular blob at the same path in the already pinned immutable tree (`100644` or `100755`). A symlink/directory response cannot establish required configuration or workflow presence. Three distinct provider regressions cover symlink mode, non-regular tree mode and accepted executable regular blobs; the dedicated inventory becomes 224 + 3 = **227**. All six content paths, the shared tree read, decoded-file/tree ceilings, credential scope and current-head fence remain unchanged. + + +### Conservative matrix eligibility (2026-10-13) + +Native review `38355935682` identified an Action job with a statically empty +matrix being reported as runnable. The workflow contract MUST reject an empty +static axis without include/exclude transformations as blocked. Simple nonempty +static literal axes can establish structural eligibility; dynamic matrices or +axes, expression-valued entries, unsupported values and include/exclude +transformations MUST remain unavailable rather than claiming a job will run. +Another independently eligible Action job may still establish the workflow +contract. This is read-only evidence, not a runtime execution simulator; workflow +conditions and all existing callback requirements continue to apply. + +See GitHub's primary [matrix documentation](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/run-job-variations) +for combinations, dynamic input and include/exclude behavior. The pure policy +never evaluates expressions, expands combinations, reads provider state or +changes workflow configuration. There are no new settings or permissions. + +Nineteen distinct regressions extend the inventory from 227 to **246**: sixteen +pure shape/literal/empty/dynamic/transformation cases and three complete workflow +projections that failed before the correction. The new pure policy requires +100% lines, statements, branches and functions; global/module gates remain. +User documentation explains that unsupported scheduling evidence yields +Unavailable, and the catalog includes the pure policy and its tests. Acceptance +requires a static empty matrix to withhold Pass, dynamic/transformed matrices to +withhold certification, and ordinary nonmatrix/static eligible jobs to retain +their existing structural result. Native review and integrated live commands +remain required before completion. + + +Native parent synchronization `38373281279` rejected an incomplete constructor +merge: the Doctor port displaced existing optional read capabilities and two +suites failed, without publication. New Doctor dependencies MUST be appended +after existing optional adapters, preserving their positional call contracts. +Composition assertions identify the distinct frozen semantic port by its +capabilities, preserving exact capability and immutability checks without +assuming the newest port's position. Existing Doctor entrypoint fixtures follow +the appended argument; the 246-case Doctor budget remains unchanged. Parent +integration must also retain existing History and native-status arguments and +pass their real entrypoint suites before publication. + + +Native retry `38377919836` still rejected a merge that displaced the parent +History reader and the Doctor test's fixed positional slot. The Doctor-only +entrypoint fixture now supplies the appended Doctor capability in the final +constructor position, preserving all forbidden model, mutation, authorization +and SDD-continuation spies. Its behavioral assertions remain unchanged; parent +readers can occupy their retained positions without requiring edits to an +automatically merged test outside the fixer's original conflict boundary. The +246-case budget and exact production composition checks remain unchanged. + +### Static matrix ceiling and help applicability (2026-10-13) + +Native review `38388018066` on the Action-integrated head `aa573118` published +findings `4257865633` and `4257865647`. The first exposed static matrices that +exceed GitHub's [256-job matrix ceiling](https://docs.github.com/en/actions/reference/limits); +the second exposed incomplete help forms being shown as blocked SDD readiness. +Both are real diagnostic false positives/negatives, distinct from execution +failure or lack of bot token permissions. + +The pure matrix policy MUST certify only supported literal axes with a nonzero +Cartesian product at most 256. It computes a bounded product without expanding +jobs, evaluating expressions or executing workflow content; compare against +256 divided by the next length before multiplication to avoid overflow. Exact +256 remains eligible, 257 or a multi-axis product above 256 is blocked, and +unsupported dynamic/transformed evidence remains unavailable even if another +axis looks excessive. Another independently eligible job retains its existing +meaning. The ceiling is a provider contract, not a user-configurable limit or a +change to runner capacity, platform selection, concurrency or heap size. + +A unique enabled help kind MUST make branch and SDD checks not applicable before +validating its form content, because that work is branchless. Missing, duplicate +or empty question headings do not create an SDD obligation. Disabled help and +conflicting help/feature labels MUST retain their existing admission block; +invalid branch-bearing forms remain blocked. Effective configuration and all +independent workflow/policy facts remain available. No query gains model, +label, branch, configuration or source-comment mutation authority. + +Twelve new distinct cases extend the dedicated Doctor inventory from 246 to +**258**, without counting parent History/native-closure cases: five pure matrix +ceiling/dynamic cases, two complete workflow projections and five help +applicability/admission cases. Six new cases failed against the previous +implementation (two oversized pure matrices, the complete 257-row workflow, +and three malformed help forms); exact-limit, dynamic, disabled and conflicting +cases preserve existing behaviour. Pure matrix coverage stays 100% and all +repository/module thresholds remain mandatory. Existing bounded YAML/content, +provider-read, identity and reply-witness contracts stay unchanged. + +Acceptance: a verified 256-row static Action workflow reports its normal +structural result; 257 rows cannot produce Pass; malformed unique enabled help +shows Not applicable for branch/SDD; disabled/conflicting kinds remain Blocked. +`docs/issues/comment-commands.mdx` explains these diagnostic limits. Existing +catalog paths already own the three changed suites and two implementations. +Completion still requires current-head full checks and native review, followed +by issue/PR EN/ES and quiet-replay browser acceptance after promotion. + +### Present policy and complete specification ownership (2026-10-13) + +Native review of `b205cec1` exposed `4258008714` (orphan specification) and +`4258030525` (present empty policy). The related `4258008697` claim about `@` +was disproved: the current literal branch alphabet already excludes `@`, and +both the pure predicate and effective configuration reject it without a +production change. Native finding dismissal must remain explicit and inspectable. + +A trusted read marked Available with an empty or whitespace-only string MUST +be Invalid/Blocked. Only the explicit Absent result proves the existing off +default. A malformed non-string provider value remains Unavailable and cannot +silently choose off. Other successful groups survive either result; read scope +or permission failure is not inferred from a malformed value. This correction +changes Doctor diagnostics, not the shared approval engine's public empty-input +configuration semantics or any policy variable stored in GitHub. + +The pinned regular-file inventory MUST prove ownership for every tracked +`specs/**/*.md` product specification, including nested specifications, matching +the canonical validator's recursive discovery. Only the exact three top-level +standard documents `specs/README.md`, `specs/_template.md`, `specs/CATALOG.md` +are excluded; a nested README is still a product specification. Existing catalog +path/metadata/duplicate-owner and complete-tree requirements remain mandatory. +The check uses the already bounded immutable tree, not extra provider reads, +filesystem execution or guessed paths. Orphan files make the prerequisite +Blocked; unavailable inventories remain Unavailable. + +Eleven distinct regressions add four domain cases, five catalog/configuration +cases and two full-report cases: empty/whitespace Available, malformed value, +the existing single-at rejection, root/nested/nested-README orphans, the three +standard exclusions, effective single-at rejection and retained independent +report facts. Eight failed against `b205cec1`; the two single-at assertions and +standard-document exclusion passed already. The dedicated inventory becomes +258 + 11 = **269**. All pure and module coverage requirements remain intact. +Native review and live command/replay acceptance remain required before merge +and promotion. Documentation explains present-invalid versus verified-absent +and complete recursive catalog ownership; no input or permission is added. + +### Conservative dependent-job eligibility (2026-10-13) + +Native finding `4258185759` on `07689fbe` exposed a sole Action job depending on +an `if: false` prerequisite while still receiving Pass. GitHub's documented +[`jobs..needs`](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idneeds) +requires prerequisite success and normally skips downstream work when a +prerequisite is skipped; explicit status overrides can change that behaviour. + +A candidate with a supported own condition and a directly proven skipped +prerequisite MUST NOT establish workflow eligibility. Direct scalar and array +needs are inspected within the already parsed workflow; at most 20 literal job +IDs of at most 100 ASCII characters are considered. No dependency expansion, +runtime-success inference, expression evaluation or additional provider read is +introduced. Other dependency evidence, including runtime-success requirements, +missing/unsupported IDs, empty/malformed/oversized lists, cycles and transitive +chains, stays Unavailable. An unsupported own condition such as `always()` +remains Unavailable and is not mistaken for a proven skipped downstream job. +An independent eligible Action job may still establish the workflow contract. +These are fixed diagnostic bounds, not platform/concurrency/runner changes. + +Fifteen distinct complete-workflow cases cover no needs, skipped scalar/array, +runtime prerequisite, missing/unknown dependency, explicit override, transitive +chain, self-reference, empty/malformed/over-budget lists and independent Action +fallback. Ten of the initial thirteen cases failed before correction; three controls already passed. Two further shape/name cases close defensive branch coverage without being claimed as failed-before evidence. The +Doctor inventory becomes 269 + 15 = **284**. All coverage budgets remain in +force; existing catalog evidence already owns this policy and suite. User docs +explain dependency limitations and the Unknown-versus-Blocked distinction. +Current-head native review and post-promotion command/replay acceptance remain +mandatory before completion. + +### Incomplete samples are not proved absence (2026-10-13) + +Native finding `4258345307` on `d5b5f74e` reproduced omitted or malformed file +entries being treated like an explicit missing-file result. Each prerequisite +and workflow group MUST first validate its relevant three entries as explicit +Present with string content or Missing. Omitted/null/unknown-state/non-string +entries are Unavailable; an explicit Missing entry remains Blocked. SDD tree +inventory MUST also be an array of strings; malformed inventory is Unavailable. +A present string containing invalid catalog/YAML stays a proved invalid +contract, independently of this provider-shape check. Successful unrelated +policy, workflow or issue facts are preserved; no permission or absence is +inferred from incomplete evidence. The six fixed content reads and tree bounds +are unchanged, with no additional provider request or runtime/model work. + +Sixteen distinct cases add two explicit-absence controls, eight malformed +samples across the two groups, three malformed tree inventories and three malformed whole file maps. Eleven of the first thirteen +failed against the prior implementation; the two absence controls already +passed. The three whole-map cases additionally cover defensive shape branches and are not claimed as failed-before evidence. The Doctor inventory becomes 284 + 16 = **300**. Existing pure/module +coverage, normal hooks and all native platform gates remain mandatory. +Documentation distinguishes missing from incomplete evidence. Current-head +review and post-promotion commands/replay/browser acceptance remain required. diff --git a/specs/setup-configuration-credentials-and-doctor.md b/specs/setup-configuration-credentials-and-doctor.md index 56d8f0390..7dbb189e6 100644 --- a/specs/setup-configuration-credentials-and-doctor.md +++ b/specs/setup-configuration-credentials-and-doctor.md @@ -676,3 +676,21 @@ widths, canceled prompts, secret masking, and GitHub permission variants. - Decision: one configuration policy serves setup, doctor, and workflow inputs. - Rejected: storing credentials in YAML/JSON or silently overwriting managed files. - Follow-up: cross-provider transactional rollback is outside this baseline. + + +## GitHub conversation runtime doctor extension — issue #461 + +The companion `github-runtime-doctor-command.md` owns the deterministic +`/copilot doctor` conversation audit. It uses the Action runtime credential, +not the local setup operator PAT. Its physically frozen query port exposes only +repository, issue, native linked branch, canonical PR and trusted effective +approval policy reads. It never exposes local setup provisioning, credential +health dispatch, approval submission, model or Git/file mutation capabilities. + +All public strings join the existing publication catalog (35 reviewed English +and Spanish message IDs). Independent unavailable checks preserve successful +facts. A valid active approval policy reports declared prerequisites as a warning, +without inferring producer health or write grants. Existing off semantics require +trusted verified absence. The feature adds 124 distinct contract and risk cases +across the numeric companion budget; live promotion and browser proof remain +pending until their actual results are recorded. diff --git a/src/application/policies/__tests__/doctor_workflow_condition_policy.test.ts b/src/application/policies/__tests__/doctor_workflow_condition_policy.test.ts new file mode 100644 index 000000000..ec720ddd6 --- /dev/null +++ b/src/application/policies/__tests__/doctor_workflow_condition_policy.test.ts @@ -0,0 +1,47 @@ +import { doctorWorkflowCondition } from '../doctor_workflow_condition_policy'; +import { doctorWorkflowContracts } from '../github_runtime_doctor_policy'; +import { doctorRepositoryFixture } from '../../usecases/__tests__/fixtures/runtime_doctor_fixture'; +const contract = (job: string, event = 'issues') => { + const read = doctorRepositoryFixture(); + return doctorWorkflowContracts({ ...read, files: { ...read.files, + '.github/workflows/copilot_issue.yml': { state: 'present', text: `on: ${event}\njobs:\n${job}` } } }); +}; +describe('bounded workflow eligibility evidence', () => { + it.each([ + [undefined, true], [true, true], [false, false], ['${{ false }}', false], + ["github.event_name == 'issues'", true], ["github.event_name == 'push'", false], + ["github.event_name != 'push'", true], ["!(github.event_name == 'issues')", false], + ['false && vars.UNKNOWN', false], ['true || vars.UNKNOWN', true], + ['true && vars.UNKNOWN', undefined], ['false || vars.UNKNOWN', undefined], + ["''", false], ["'enabled'", true], ["'a''b' == 'a''b'", true], + ['false == true', false], ['true != true', false], ['vars.X != false', undefined], + ['true false', undefined], + ["vars.BOT == ''", undefined], ['success()', undefined], ['(', undefined], [null, undefined], + ['x'.repeat(1001), undefined], ['('.repeat(33) + 'true' + ')'.repeat(33), undefined], + ] as const)('keeps condition %s evidence closed', (value, expected) => expect(doctorWorkflowCondition(value, 'issues')).toBe(expected)); + it.each(["'0' == false", "false == '0'", "'1' != true"])( + 'withholds a decision for unsupported mixed-type coercion %s', expression => { + expect(doctorWorkflowCondition(expression,'issues')).toBeUndefined(); + }); + it('keeps a workflow with mixed-type eligibility unavailable instead of proven blocked or active', () => { + expect(contract(' action: {if: "\'0\' == false", steps: [{uses: "./"}]}')).toBe('unavailable'); + }); + it('blocks a disabled Action job even when an unrelated job runs', () => expect(contract(' disabled: {if: false, steps: [{uses: "./"}]}\n active: {steps: [{run: "echo fixture"}]}')).toBe('blocked')); + it('blocks an Action job restricted to a different event', () => expect(contract(' action: {if: "github.event_name == \'push\'", steps: [{uses: "./"}]}')).toBe('blocked')); + it('blocks a disabled Action step in an eligible job', () => expect(contract(' action: {steps: [{if: false, uses: "./"}]}')).toBe('blocked')); + it('keeps runtime-dependent job conditions unavailable', () => expect(contract(' action: {if: "vars.BOT == \'\'", steps: [{uses: "./"}]}')).toBe('unavailable')); + it('keeps unsupported step expressions unavailable', () => expect(contract(' action: {steps: [{if: "success()", uses: "./"}]}')).toBe('unavailable')); + it('accepts an eligible Action job independently of a disabled candidate', () => expect(contract(' disabled: {if: false, steps: [{uses: "./"}]}\n active: {if: "github.event_name == \'issues\'", steps: [{uses: "./"}]}')).toBe('pass')); + it('blocks an event filter that cannot admit any activity', () => expect(contract(' active: {steps: [{uses: "./"}]}', '{issues: {types: []}}')).toBe('blocked')); +}); + + +describe('matrix eligibility in the complete workflow contract', () => { + it.each([ + ['{os: []}', 'blocked'], + ['"${{ fromJSON(needs.prepare.outputs.matrix) }}"', 'unavailable'], + ['{os: [ubuntu-latest], exclude: [{os: ubuntu-latest}]}', 'unavailable'], + ])('withholds a passing contract for matrix %s', (matrix, expected) => { + expect(contract(` action: {strategy: {matrix: ${matrix}}, steps: [{uses: "./"}]}`)).toBe(expected); + }); +}); diff --git a/src/application/policies/__tests__/doctor_workflow_matrix_policy.test.ts b/src/application/policies/__tests__/doctor_workflow_matrix_policy.test.ts new file mode 100644 index 000000000..131d755b8 --- /dev/null +++ b/src/application/policies/__tests__/doctor_workflow_matrix_policy.test.ts @@ -0,0 +1,35 @@ +import { doctorWorkflowMatrix } from '../doctor_workflow_matrix_policy'; +describe('conservative runnable matrix evidence', () => { + it.each([ + [undefined, true], + [{ 'max-parallel': 2 }, true], + [null, undefined], + [[], undefined], + [{ matrix: '${{ fromJSON(needs.prepare.outputs.matrix) }}' }, undefined], + [{ matrix: null }, undefined], + [{ matrix: [] }, undefined], + [{ matrix: {} }, undefined], + [{ matrix: { os: ['ubuntu', 'macos'], version: [20, true] } }, true], + [{ matrix: { os: [], version: [20] } }, false], + [{ matrix: { os: [], include: [{ os: 'ubuntu' }] } }, undefined], + [{ matrix: { os: ['ubuntu'], exclude: [{ os: 'ubuntu' }] } }, undefined], + [{ matrix: { os: '${{ github.event.client_payload.os }}' } }, undefined], + [{ matrix: { os: ['${{ vars.OS }}'] } }, undefined], + [{ matrix: { os: [{ image: 'ubuntu' }] } }, undefined], + [{ matrix: { version: [Number.NaN] } }, undefined], + ] as const)('returns only proved structural eligibility for %j', (strategy, expected) => { + expect(doctorWorkflowMatrix(strategy)).toBe(expected); + }); +}); + +describe('GitHub static matrix job ceiling', () => { + it.each([ + [{ matrix: { version: Array.from({ length: 256 }, (_, i) => i) } }, true], + [{ matrix: { version: Array.from({ length: 257 }, (_, i) => i) } }, false], + [{ matrix: { os: Array.from({ length: 16 }, (_, i) => i), version: Array.from({ length: 16 }, (_, i) => i) } }, true], + [{ matrix: { os: Array.from({ length: 17 }, (_, i) => i), version: Array.from({ length: 16 }, (_, i) => i) } }, false], + [{ matrix: { os: Array.from({ length: 257 }, (_, i) => i), version: '${{ vars.VERSION }}' } }, undefined], + ] as const)('proves the static product within 256 without evaluating dynamic shapes (%j)', (strategy, expected) => { + expect(doctorWorkflowMatrix(strategy)).toBe(expected); + }); +}); diff --git a/src/application/policies/__tests__/github_runtime_doctor_policy.test.ts b/src/application/policies/__tests__/github_runtime_doctor_policy.test.ts new file mode 100644 index 000000000..6e5a006f5 --- /dev/null +++ b/src/application/policies/__tests__/github_runtime_doctor_policy.test.ts @@ -0,0 +1,199 @@ +import { doctorRepositoryFixture } from '../../usecases/__tests__/fixtures/runtime_doctor_fixture'; +import { doctorRepositoryPrerequisites, doctorWorkflowContracts, validDoctorConfiguration } from '../github_runtime_doctor_policy'; +import type { DoctorConfiguration, DoctorRepositoryRead } from '../../ports/github_runtime_doctor_ports'; +const config: DoctorConfiguration = { profile: { schemaVersion: 1, enabled: ['feature'] }, preBranchSdd: true, + issueManagedBranches: true, dependencyBranch: 'develop' }; +function changed(path: keyof ReturnType['files'], text?: string) { + const snapshot = doctorRepositoryFixture(); + return { ...snapshot, files: { ...snapshot.files, [path]: text === undefined ? { state: 'missing' as const } : { state: 'present' as const, text } } }; +} +describe('runtime doctor pure prerequisite decisions', () => { + it('validates admitted configuration through the existing profile and start policies', () => expect(validDoctorConfiguration(config)).toBe(true)); + it('does not turn a missing configuration into valid defaults', () => expect(validDoctorConfiguration(undefined)).toBe(false)); + it('rejects incompatible prebranch and managed-branch flags', () => expect(validDoctorConfiguration({ ...config, issueManagedBranches: false })).toBe(false)); + it('rejects retired issue-profile versions', () => expect(validDoctorConfiguration({ ...config, profile: { schemaVersion: 2 } } as unknown as DoctorConfiguration)).toBe(false)); + it('rejects unknown issue kinds', () => expect(validDoctorConfiguration({ ...config, profile: { schemaVersion: 1, enabled: ['made-up'] } } as unknown as DoctorConfiguration)).toBe(false)); + it('rejects unsafe dependency references', () => expect(validDoctorConfiguration({ ...config, dependencyBranch: '--upload=/private' })).toBe(false)); + it.each([null, 123, true, ['develop'], undefined])('rejects non-string dependency branch %#', dependencyBranch => { + expect(validDoctorConfiguration({...config, dependencyBranch} as unknown as DoctorConfiguration)).toBe(false); + }); + it('blocks an empty catalog according to the canonical specification contract', () => { + expect(doctorRepositoryPrerequisites(changed('specs/catalog.json', JSON.stringify({version: 1, capabilities: []})))).toBe('blocked'); + }); + it('validates catalog, template and standard from one snapshot', () => expect(doctorRepositoryPrerequisites(doctorRepositoryFixture())).toBe('pass')); + it('keeps unreadable prerequisites unavailable', () => expect(doctorRepositoryPrerequisites({ state: 'unavailable' })).toBe('unavailable')); + it('blocks a proven missing specification standard', () => expect(doctorRepositoryPrerequisites(changed('specs/README.md'))).toBe('blocked')); + it('blocks malformed catalog JSON', () => expect(doctorRepositoryPrerequisites(changed('specs/catalog.json', '{'))).toBe('blocked')); + it('blocks unsupported catalog versions', () => expect(doctorRepositoryPrerequisites(changed('specs/catalog.json', '{"version":2,"capabilities":[]}'))).toBe('blocked')); + it('blocks duplicate capability identities', () => expect(doctorRepositoryPrerequisites(changed('specs/catalog.json', JSON.stringify({ version: 1, + capabilities: [{ id: 'safe', specs: ['specs/safe.md'] }, { id: 'safe', specs: ['specs/safe.md'] }] })))).toBe('blocked')); + it('blocks unsafe capability source paths', () => expect(doctorRepositoryPrerequisites(changed('specs/catalog.json', JSON.stringify({ version: 1, + capabilities: [{ id: 'safe', specs: ['../private.md'] }] })))).toBe('blocked')); + it('blocks malformed capability identifiers', () => expect(doctorRepositoryPrerequisites(changed('specs/catalog.json', JSON.stringify({ version: 1, + capabilities: [{ id: '@mention', specs: ['specs/safe.md'] }] })))).toBe('blocked')); + it('blocks a template without a Markdown title', () => expect(doctorRepositoryPrerequisites(changed('specs/_template.md', 'empty standard'))).toBe('blocked')); +}); +describe('installed workflow structural contracts', () => { + it('accepts all required events and local Action steps without claiming write grants', () => expect(doctorWorkflowContracts(doctorRepositoryFixture())).toBe('pass')); + it('accepts documented event arrays', () => expect(doctorWorkflowContracts(changed('.github/workflows/copilot_issue.yml', + 'on: [issues]\njobs: {a: {steps: [{uses: "vypdev/copilot@v3"}]}}'))).toBe('pass')); + it('accepts event maps parsed with the JSON YAML schema', () => expect(doctorWorkflowContracts(changed('.github/workflows/copilot_issue.yml', + 'on: {issues: {types: [labeled]}}\njobs: {a: {steps: [{uses: "./"}]}}'))).toBe('pass')); + it.each(['opened','null','{unexpected: opened}','[opened, 5]'])('blocks malformed workflow activity types %s', types => { + expect(doctorWorkflowContracts(changed('.github/workflows/copilot_issue.yml', + `on: {issues: {types: ${types}}}\njobs: {a: {steps: [{uses: "./"}]}}`))).toBe('blocked'); + }); + it('keeps an incomplete repository sample unavailable', () => expect(doctorWorkflowContracts({ state: 'unavailable' })).toBe('unavailable')); + it('blocks a missing required comment workflow', () => expect(doctorWorkflowContracts(changed('.github/workflows/copilot_issue_comment.yml'))).toBe('blocked')); + it('blocks the wrong event instead of searching another workflow', () => expect(doctorWorkflowContracts(changed('.github/workflows/copilot_issue.yml', + 'on: push\njobs: {a: {steps: [{uses: "./"}]}}'))).toBe('blocked')); + it('blocks a workflow without the installed Action', () => expect(doctorWorkflowContracts(changed('.github/workflows/copilot_issue.yml', + 'on: issues\njobs: {a: {steps: [{run: "echo SECRET"}]}}'))).toBe('blocked')); + it('does not execute YAML constructors or repository code', () => expect(doctorWorkflowContracts(changed('.github/workflows/copilot_issue.yml', + 'on: !!js/function "function(){ throw new Error(SECRET) }"'))).toBe('blocked')); + it('rejects a malformed workflow document', () => expect(doctorWorkflowContracts(changed('.github/workflows/copilot_issue.yml', 'on: ['))).toBe('blocked')); + it('rejects a non-record repository document', () => expect(doctorWorkflowContracts(changed('.github/workflows/copilot_issue.yml', '- issues'))).toBe('blocked')); +}); + +describe('required workflow entry activities', () => { + it.each([ + ['.github/workflows/copilot_issue.yml', 'issues', 'labeled', 'closed'], + ['.github/workflows/copilot_issue_comment.yml', 'issue_comment', 'created', 'deleted'], + ['.github/workflows/copilot_pull_request.yml', 'pull_request', 'opened', 'closed'], + ] as const)('does not certify %s when its valid activity filter cannot admit the documented entry', (path, event, required, irrelevant) => { + const text = (activity: string) => `on: {${event}: {types: [${activity}]}}\njobs: {a: {steps: [{uses: "./"}]}}`; + expect(doctorWorkflowContracts(changed(path, text(irrelevant)))).toBe('blocked'); + expect(doctorWorkflowContracts(changed(path, text(required)))).toBe('pass'); + }); +}); + + +describe('strict prerequisite regressions from live review', () => { + it.each(['develop/', 'develop//main', 'develop/.', '/develop', '.hidden/main', 'feature/topic.lock/child', 'develop.', 'feature/@{previous}'])('rejects an invalid Git branch component %s', dependencyBranch => { + expect(validDoctorConfiguration({ ...config, dependencyBranch })).toBe(false); + }); + it.each(['title', 'status', 'scope', 'owner', 'lastVerified'] as const)('does not certify a catalog without canonical %s metadata', field => { + const snapshot = doctorRepositoryFixture(); + const file = snapshot.files['specs/catalog.json']; if (file.state !== 'present') throw new Error('Fixture unavailable'); + const catalog = JSON.parse(file.text); delete catalog.capabilities[0][field]; + expect(doctorRepositoryPrerequisites(changed('specs/catalog.json', JSON.stringify(catalog)))).toBe('blocked'); + }); + it('does not certify a catalog whose specification is absent from the verified file inventory', () => { + const snapshot = doctorRepositoryFixture(); + expect(doctorRepositoryPrerequisites({ ...snapshot, trackedFiles: [] } as DoctorRepositoryRead)).toBe('blocked'); + }); +}); + + +describe('bounded canonical catalog structure', () => { + function catalogChange(edit: (capabilities: Record[]) => void) { + const snapshot = doctorRepositoryFixture(), file = snapshot.files['specs/catalog.json']; + if (file.state !== 'present') throw new Error('Fixture unavailable'); + const catalog = JSON.parse(file.text); edit(catalog.capabilities); + return changed('specs/catalog.json', JSON.stringify(catalog)); + } + it.each(['unsupported status', 'invalid date syntax', 'impossible date', 'non-text metadata', 'blank metadata', + 'duplicate title', 'duplicate spec owner', 'missing evidence array', 'empty required evidence', 'duplicate evidence', + 'wrong evidence boundary', 'unsafe normalized path', 'missing non-spec file'] as const)('blocks %s', scenario => { + expect(doctorRepositoryPrerequisites(catalogChange(capabilities => { + const c = capabilities[0]; + if (scenario === 'unsupported status') c.status = 'done'; + if (scenario === 'invalid date syntax') c.lastVerified = '13/10/2026'; + if (scenario === 'impossible date') c.lastVerified = '2026-02-29'; + if (scenario === 'non-text metadata') c.owner = 42; + if (scenario === 'blank metadata') c.title = ' '; + if (scenario === 'duplicate title') capabilities.push({ ...c, id: 'other', specs: ['specs/other.md'] }); + if (scenario === 'duplicate spec owner') capabilities.push({ ...c, id: 'other', title: 'Other' }); + if (scenario === 'missing evidence array') delete c.code; + if (scenario === 'empty required evidence') c.documentation = []; + if (scenario === 'duplicate evidence') c.documentation = ['README.md', 'README.md']; + if (scenario === 'wrong evidence boundary') c.documentation = ['src/domain/locale.ts']; + if (scenario === 'unsafe normalized path') c.specs = ['specs/../safe.md']; + if (scenario === 'missing non-spec file') c.documentation = ['docs/missing.mdx']; + }))).toBe('blocked'); + }); + it('accepts the actual canonical catalog with all verified referenced files and nested specification paths', () => { + const canonical = require('../../../infrastructure/specification_catalog_validator.cjs'); + const catalog = canonical.readCatalog(process.cwd()); + expect(canonical.validateCatalog(process.cwd(), catalog)).toEqual([]); + const snapshot = changed('specs/catalog.json', JSON.stringify(catalog)); + const trackedFiles = catalog.capabilities.flatMap((c: Record) => + ['specs', 'workflows', 'entrypoints', 'code', 'tests', 'documentation'].flatMap(field => c[field] as string[])); + expect(doctorRepositoryPrerequisites({ ...snapshot, trackedFiles })).toBe('pass'); + }); +}); + +describe('complete workflow matrix job ceiling', () => { + it.each([[256, 'pass'], [257, 'blocked']] as const)('reports a %s-row Action job as %s', (count, expected) => { + const yaml = `on: issues\njobs:\n action:\n strategy:\n matrix:\n version: [${Array.from({ length: count }, (_, i) => i).join(', ')}]\n steps:\n - uses: ./\n`; + expect(doctorWorkflowContracts(changed('.github/workflows/copilot_issue.yml', yaml))).toBe(expected); + }); +}); + +describe('complete tracked specification ownership', () => { + it.each(['specs/orphan.md', 'specs/nested/orphan.md', 'specs/nested/README.md'])('blocks the unregistered product specification %s', path => { + const snapshot = doctorRepositoryFixture(); + expect(doctorRepositoryPrerequisites({ ...snapshot, trackedFiles: [...snapshot.trackedFiles, path] })).toBe('blocked'); + }); + it('excludes only the three top-level standard documents from ownership discovery', () => { + const snapshot = doctorRepositoryFixture(); + expect(doctorRepositoryPrerequisites({ ...snapshot, trackedFiles: [...snapshot.trackedFiles, 'specs/README.md', 'specs/_template.md', 'specs/CATALOG.md', 'docs/guide.md'] })).toBe('pass'); + }); + it('already blocks single-at effective dependency configuration', () => expect(validDoctorConfiguration({ ...config, dependencyBranch: '@' })).toBe(false)); +}); + +describe('conservative workflow prerequisite eligibility', () => { + it.each([ + ['none', undefined, {}, undefined, 'pass'], + ['disabled-scalar', 'disabled', { disabled: { if: false } }, undefined, 'blocked'], + ['disabled-array', ['ready', 'disabled'], { ready: { if: true }, disabled: { if: false } }, undefined, 'blocked'], + ['runtime-success', 'ready', { ready: { if: true } }, undefined, 'unavailable'], + ['missing', 'missing', {}, undefined, 'unavailable'], + ['unknown-condition', 'unknown', { unknown: { if: 'vars.ENABLED' } }, undefined, 'unavailable'], + ['explicit-override', 'disabled', { disabled: { if: false } }, 'always()', 'unavailable'], + ['transitive', 'middle', { middle: { needs: 'disabled' }, disabled: { if: false } }, undefined, 'unavailable'], + ['self-reference', 'action', {}, undefined, 'unavailable'], + ['empty', [], {}, undefined, 'unavailable'], + ['malformed', [true], {}, undefined, 'unavailable'], + ['unsupported-shape', null, {}, undefined, 'unavailable'], + ['unsupported-name', ['bad dependency'], {}, undefined, 'unavailable'], + ['over-budget', Array.from({ length: 21 }, (_, i) => `job${i}`), {}, undefined, 'unavailable'], + ['independent-action', 'missing', {}, undefined, 'pass'], + ] as const)('reports %s dependency evidence without guessing runtime success', (scenario, needs, dependencies, condition, expected) => { + const snapshot = doctorRepositoryFixture(); + const jobs = { ...dependencies, action: { ...(needs === undefined ? {} : { needs }), ...(condition === undefined ? {} : { if: condition }), steps: [{ uses: './' }] }, + ...(scenario === 'independent-action' ? { independent: { steps: [{ uses: './' }] } } : {}) }; + const yaml = JSON.stringify({ on: 'issues', jobs }); + expect(doctorWorkflowContracts({ ...snapshot, files: { ...snapshot.files, '.github/workflows/copilot_issue.yml': { state: 'present', text: yaml } } })).toBe(expected); + }); +}); + +describe('incomplete provider samples versus proved missing files', () => { + it.each(['specifications', 'workflows'] as const)('retains an explicitly missing %s file as blocked', group => { + const path = group === 'specifications' ? 'specs/catalog.json' : '.github/workflows/copilot_issue.yml'; + const sample = changed(path); + expect(group === 'specifications' ? doctorRepositoryPrerequisites(sample) : doctorWorkflowContracts(sample)).toBe('blocked'); + }); + it.each(['specifications', 'workflows'].flatMap(group => ['omitted', 'null', 'unknown-state', 'non-string-text'].map(kind => [group, kind])))('keeps malformed %s / %s evidence unavailable', (group, kind) => { + const snapshot = doctorRepositoryFixture(); + const path = group === 'specifications' ? 'specs/catalog.json' : '.github/workflows/copilot_issue.yml'; + const files: Record = { ...snapshot.files }; + if (kind === 'omitted') delete files[path]; + if (kind === 'null') files[path] = null; + if (kind === 'unknown-state') files[path] = { state: 'future' }; + if (kind === 'non-string-text') files[path] = { state: 'present', text: 42 }; + const sample = { ...snapshot, files } as unknown as typeof snapshot; + expect(group === 'specifications' ? doctorRepositoryPrerequisites(sample) : doctorWorkflowContracts(sample)).toBe('unavailable'); + }); + it.each(['omitted', 'null', 'non-string-path'])('keeps a malformed %s tree inventory unavailable', kind => { + const snapshot = doctorRepositoryFixture(); + const sample = { ...snapshot, trackedFiles: kind === 'omitted' ? undefined : kind === 'null' ? null : [...snapshot.trackedFiles, 42] } as unknown as typeof snapshot; + expect(doctorRepositoryPrerequisites(sample)).toBe('unavailable'); + }); +}); + +it.each(['omitted', 'null', 'array'])('keeps a malformed whole files map (%s) unavailable in both groups', kind => { + const snapshot = doctorRepositoryFixture(); + const sample = { ...snapshot, files: kind === 'omitted' ? undefined : kind === 'null' ? null : [] } as unknown as typeof snapshot; + expect(doctorRepositoryPrerequisites(sample)).toBe('unavailable'); expect(doctorWorkflowContracts(sample)).toBe('unavailable'); +}); diff --git a/src/application/policies/__tests__/github_runtime_doctor_presentation_policy.test.ts b/src/application/policies/__tests__/github_runtime_doctor_presentation_policy.test.ts new file mode 100644 index 000000000..8edea65a3 --- /dev/null +++ b/src/application/policies/__tests__/github_runtime_doctor_presentation_policy.test.ts @@ -0,0 +1,60 @@ +import { formatGithubRuntimeDoctor, isGithubRuntimeDoctorProjection } from '../github_runtime_doctor_presentation_policy'; +import { aggregateGithubRuntimeDoctorReport, GITHUB_RUNTIME_DOCTOR_GROUPS } from '../../../domain/github_runtime_doctor'; +import { Result } from '../../../data/model/result'; +import { renderSemanticReply, selectSemanticReplyIntents } from '../semantic_result_publication_policy'; +const projection = () => ({ report: aggregateGithubRuntimeDoctorReport({ target: { kind: 'issue', repositoryId: 42, issueNumber: 7 }, + checks: GITHUB_RUNTIME_DOCTOR_GROUPS.map(group => ({ group, state: 'pass' as const })) }), + owner: 'o', repository: 'r', revision: 'a'.repeat(40), serverOrigin: 'https://github.com' }); +describe('runtime doctor public presentation', () => { + it.each(['pass', 'warning', 'blocked', 'unavailable', 'not-applicable'] as const)('renders textual %s state with exactly one primary action', state => { + const value = projection(); const report = aggregateGithubRuntimeDoctorReport({ target: value.report.target, + checks: GITHUB_RUNTIME_DOCTOR_GROUPS.map(group => ({ group, state })) }); + const body = formatGithubRuntimeDoctor({ ...value, report }); + expect(body.match(/Next action:/gu)).toHaveLength(1); expect(body.match(/^- /gmu)).toHaveLength(7); + expect(body.length).toBeLessThan(6000); expect(body).toContain('do not prove'); + }); + it('renders all labels and one primary action in Spanish', () => { + const body = formatGithubRuntimeDoctor(projection(), 'es-ES'); expect(body).toContain('Diagnóstico del runtime'); + expect(body).toContain('Siguiente acción'); expect(body).not.toContain('Next action'); expect(body).toContain('solo lee evidencias'); + }); + it('falls back atomically for unsupported locales', () => { + expect(formatGithubRuntimeDoctor(projection(), 'fr-FR')).toEqual(formatGithubRuntimeDoctor(projection(), 'en-US')); + }); + it.each(['https://github.com/?token=SECRET', 'https://user:SECRET@github.com', 'javascript:SECRET', 'https://github.com/prefix'])('suppresses unsafe configured link %s', serverOrigin => { + const body = formatGithubRuntimeDoctor({ ...projection(), serverOrigin }); expect(body).not.toContain('SECRET'); expect(body).not.toContain(']('); + }); + it('accepts independently configured enterprise origin', () => { + expect(formatGithubRuntimeDoctor({ ...projection(), serverOrigin: 'https://github.enterprise.test' })).toContain('https://github.enterprise.test/o/r/tree/'); + }); + it('suppresses unsafe repository identity rather than emitting Markdown or mentions', () => { + const body = formatGithubRuntimeDoctor({ ...projection(), owner: '@all](SECRET)