@vypdev/copilot 3.10.4 → 3.10.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +14 -0
  2. package/action.yml +16 -1
  3. package/build/api/index.js +1162 -76
  4. package/build/api/src/application/ports/bugbot_pull_request_resolution_ports.d.ts +4 -3
  5. package/build/api/src/application/ports/bugbot_pull_request_write_ports.d.ts +4 -3
  6. package/build/api/src/application/ports/pull_request_review_comment_ports.d.ts +4 -0
  7. package/build/api/src/data/model/application_error.d.ts +17 -2
  8. package/build/api/src/domain/change_size_reconciliation.d.ts +15 -0
  9. package/build/api/src/domain/copilot_command.d.ts +25 -0
  10. package/build/api/src/domain/copilot_command_validation.d.ts +10 -0
  11. package/build/api/src/domain/copilot_command_vocabulary.d.ts +3 -0
  12. package/build/api/src/domain/issue_workflow_diagnostic.d.ts +10 -0
  13. package/build/api/src/domain/issue_workflow_field_mapping.d.ts +10 -0
  14. package/build/api/src/domain/issue_workflow_profile.d.ts +67 -0
  15. package/build/api/src/domain/progress_binary_evidence_diagnostic.d.ts +9 -0
  16. package/build/api/src/domain/verification_failure.d.ts +15 -0
  17. package/build/cli/index.js +4352 -530
  18. package/build/deployment_recovery/index.js +41863 -0
  19. package/build/github_action/index.js +4930 -550
  20. package/deployment-recovery/action.yml +52 -0
  21. package/deployment-recovery/index.cjs +3 -0
  22. package/issue-admission/action.yml +41 -0
  23. package/issue-admission/index.cjs +13 -0
  24. package/package.json +19 -8
  25. package/setup/workflows/copilot_commit.yml +2 -0
  26. package/setup/workflows/copilot_issue.yml +57 -1
  27. package/setup/workflows/hotfix_workflow.yml +151 -7
  28. package/setup/workflows/release_workflow.yml +151 -7
@@ -8,11 +8,25 @@ on:
8
8
  required: true
9
9
  default: prepare
10
10
  type: choice
11
+ # Add replace only after configuring the complete project verifier and enabling it below.
11
12
  options: [prepare, publish, resume]
12
13
  operation-id:
13
14
  description: 'Internal durable operation identifier'
14
15
  required: false
15
16
  default: ''
17
+ expected-revision:
18
+ description: 'Current durable revision; required only for replace'
19
+ required: false
20
+ default: ''
21
+ source-sha:
22
+ description: 'Reviewed corrected source before complete replacement preparation'
23
+ required: false
24
+ default: ''
25
+ confirm-unpublished:
26
+ description: 'Confirm the exact version is unpublished in every project registry'
27
+ type: boolean
28
+ required: false
29
+ default: false
16
30
  version:
17
31
  description: 'Release version (MAJOR.MINOR.PATCH)'
18
32
  required: false
@@ -50,6 +64,11 @@ jobs:
50
64
  ISSUE: ${{ inputs.issue }}
51
65
  TITLE: ${{ inputs.title }}
52
66
  CHANGELOG: ${{ inputs.changelog }}
67
+ EXPECTED_REVISION: ${{ inputs.expected-revision }}
68
+ SOURCE_SHA: ${{ inputs.source-sha }}
69
+ CONFIRM_UNPUBLISHED: ${{ inputs.confirm-unpublished }}
70
+ # Workflow-owned opt-in; never supplied by an issue or dispatch input.
71
+ REPLACEMENT_ENABLED: 'false'
53
72
  with:
54
73
  script: |
55
74
  const mode = process.env.MODE;
@@ -59,6 +78,10 @@ jobs:
59
78
  const issue = Number(issueText);
60
79
  const title = process.env.TITLE || '';
61
80
  const changelog = process.env.CHANGELOG || '';
81
+ const revisionText = process.env.EXPECTED_REVISION || '';
82
+ const revision = Number(revisionText);
83
+ const sourceSha = process.env.SOURCE_SHA || '';
84
+ const confirmed = process.env.CONFIRM_UNPUBLISHED || 'false';
62
85
  const errors = [];
63
86
  if (!/^[1-9][0-9]*$/.test(issueText) || !Number.isSafeInteger(issue) || issue < 1) errors.push('Issue must be a positive safe integer.');
64
87
  if (!/^[0-9]+\.[0-9]+\.[0-9]+$/.test(version)) errors.push('Version must use MAJOR.MINOR.PATCH.');
@@ -66,25 +89,89 @@ jobs:
66
89
  if (operationId) errors.push('Operation ID must be empty in prepare mode.');
67
90
  if (!title || title.length > 1000) errors.push('Title must contain 1-1000 characters.');
68
91
  if (!changelog || changelog.length > 50000) errors.push('Changelog must contain 1-50000 characters.');
92
+ } else if (mode === 'replace') {
93
+ if (process.env.REPLACEMENT_ENABLED !== 'true') errors.push('Candidate replacement is disabled. Configure the complete project verifier, enable it in this workflow and then add replace to the choices.');
94
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{7,127}$/.test(operationId)) errors.push('Operation ID is invalid.');
95
+ if (!/^[1-9][0-9]*$/.test(revisionText) || !Number.isSafeInteger(revision) || revision < 1 || revision >= Number.MAX_SAFE_INTEGER) errors.push('Replacement revision is invalid.');
96
+ if (!/^[a-f0-9]{40}$/.test(sourceSha)) errors.push('Replacement source must be a full reviewed SHA.');
97
+ if (version.length > 64 || confirmed !== 'true') errors.push('Confirm the exact unpublished replacement version.');
98
+ if (title || changelog) errors.push('Replacement preserves the stored title and changelog.');
69
99
  } else if (mode === 'publish' || mode === 'resume') {
70
100
  if (!/^[A-Za-z0-9][A-Za-z0-9._-]{7,127}$/.test(operationId)) errors.push('Operation ID is invalid.');
71
101
  if (title || changelog) errors.push('Title and changelog must be empty in publish or resume mode.');
72
- } else errors.push('Mode must be prepare, publish, or resume.');
102
+ } else errors.push('Mode must be prepare, replace, publish, or resume.');
103
+ if (mode !== 'replace' && (revisionText || sourceSha || confirmed !== 'false')) errors.push('Replacement proof inputs are allowed only in replace mode.');
73
104
  if (errors.length) core.setFailed(errors.join(' '));
74
105
 
106
+ candidate-preflight:
107
+ name: Inspect unpublished candidate replacement
108
+ if: ${{ inputs.mode == 'replace' }}
109
+ runs-on: ubuntu-latest
110
+ needs: validate
111
+ timeout-minutes: 5
112
+ permissions:
113
+ contents: read
114
+ issues: read
115
+ pull-requests: read
116
+ actions: read
117
+ outputs:
118
+ mode: ${{ steps.inspect.outputs.mode }}
119
+ source-branch: ${{ steps.inspect.outputs.source-branch }}
120
+ candidate-sha: ${{ steps.inspect.outputs.candidate-sha }}
121
+ production-base-sha: ${{ steps.inspect.outputs.production-base-sha }}
122
+ operation-id: ${{ steps.inspect.outputs.operation-id }}
123
+ expected-revision: ${{ steps.inspect.outputs.expected-revision }}
124
+ steps:
125
+ - name: Read native candidate authority
126
+ id: inspect
127
+ uses: vypdev/copilot/deployment-recovery@v3
128
+ with:
129
+ issue: ${{ inputs.issue }}
130
+ operation-id: ${{ inputs.operation-id }}
131
+ expected-revision: ${{ inputs.expected-revision }}
132
+ version: ${{ inputs.version }}
133
+ source-sha: ${{ inputs.source-sha }}
134
+ confirm-unpublished: ${{ inputs.confirm-unpublished }}
135
+ issue-workflow-profile: ${{ vars.COPILOT_ISSUE_WORKFLOW_PROFILE || '' }}
136
+ release-label: release
137
+ hotfix-label: hotfix
138
+ repository-locale: ${{ vars.REPOSITORY_LOCALE || 'en-US' }}
139
+ token: ${{ secrets.PAT }}
140
+
75
141
  prepare-version-files:
76
142
  name: Prepare and validate release
77
- if: ${{ inputs.mode == 'prepare' }}
143
+ if: ${{ !cancelled() && needs.validate.result == 'success' && (inputs.mode == 'prepare' || (inputs.mode == 'replace' && needs.candidate-preflight.result == 'success' && needs.candidate-preflight.outputs.mode == 'prepare')) }}
78
144
  runs-on: ubuntu-latest
79
- needs: validate
145
+ needs: [validate, candidate-preflight]
80
146
  timeout-minutes: 30
81
147
  permissions:
82
148
  contents: write
149
+ outputs:
150
+ candidate-sha: ${{ steps.candidate-identity.outputs.candidate-sha }}
83
151
  steps:
84
152
  - uses: actions/checkout@v5
85
153
  with:
86
154
  persist-credentials: true
155
+ ref: ${{ needs.candidate-preflight.outputs.source-branch || github.ref }}
156
+ - name: Verify reviewed replacement source
157
+ if: ${{ inputs.mode == 'replace' }}
158
+ uses: actions/github-script@v9
159
+ env:
160
+ EXPECTED_SHA: ${{ inputs.source-sha }}
161
+ with:
162
+ script: |
163
+ const { execFileSync } = require('node:child_process');
164
+ const current = execFileSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
165
+ if (!/^[a-f0-9]{40}$/.test(process.env.EXPECTED_SHA || '') || current !== process.env.EXPECTED_SHA) {
166
+ core.setFailed('The reviewed replacement source moved before preparation. No new candidate proof is available.');
167
+ }
87
168
  # Add project-specific version, build, validation, smoke-test and generated-file steps here.
169
+ - name: Complete fresh replacement verification
170
+ id: replacement-verification
171
+ if: ${{ inputs.mode == 'replace' }}
172
+ run: |
173
+ echo "Configure this step with every required project validator, including registry vacancy, before enabling candidate replacement."
174
+ exit 1
88
175
  - name: Commit prepared release files
89
176
  uses: EndBug/add-and-commit@v9
90
177
  with:
@@ -93,12 +180,29 @@ jobs:
93
180
  committer_email: actions@github.com
94
181
  default_author: user_info
95
182
  message: 'gh-action: prepare release ${{ inputs.version }}'
183
+ - name: Confirm prepared replacement identity
184
+ id: candidate-identity
185
+ if: ${{ inputs.mode == 'replace' }}
186
+ uses: actions/github-script@v9
187
+ env:
188
+ SOURCE_BRANCH: ${{ needs.candidate-preflight.outputs.source-branch }}
189
+ VERIFICATION_RESULT: ${{ steps.replacement-verification.outcome }}
190
+ with:
191
+ script: |
192
+ if (process.env.VERIFICATION_RESULT !== 'success') throw new Error('Complete replacement verification was not confirmed.');
193
+ const { execFileSync } = require('node:child_process');
194
+ const candidate = execFileSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
195
+ const dirty = execFileSync('git', ['status', '--porcelain'], { encoding: 'utf8' }).trim();
196
+ if (!/^[a-f0-9]{40}$/.test(candidate) || !process.env.SOURCE_BRANCH || dirty) throw new Error('The prepared candidate is not an exact clean commit.');
197
+ const response = await github.rest.git.getRef({ ...context.repo, ref: `heads/${process.env.SOURCE_BRANCH}` });
198
+ if (response.data.object?.type !== 'commit' || response.data.object.sha !== candidate) throw new Error('The prepared source moved before candidate proof was recorded.');
199
+ core.setOutput('candidate-sha', candidate);
96
200
 
97
201
  promote:
98
202
  name: Open production promotion
99
- if: ${{ inputs.mode == 'prepare' }}
203
+ if: ${{ !cancelled() && needs.validate.result == 'success' && ((inputs.mode == 'prepare' && needs.prepare-version-files.result == 'success') || (inputs.mode == 'replace' && needs.candidate-preflight.result == 'success' && ((needs.candidate-preflight.outputs.mode == 'prepare' && needs.prepare-version-files.result == 'success' && needs.prepare-version-files.outputs.candidate-sha != '') || needs.candidate-preflight.outputs.mode == 'recover'))) }}
100
204
  runs-on: ubuntu-latest
101
- needs: prepare-version-files
205
+ needs: [validate, candidate-preflight, prepare-version-files]
102
206
  timeout-minutes: 10
103
207
  permissions:
104
208
  contents: read
@@ -109,7 +213,9 @@ jobs:
109
213
  - uses: actions/checkout@v5
110
214
  with:
111
215
  persist-credentials: false
216
+ ref: ${{ inputs.mode == 'replace' && (needs.candidate-preflight.outputs.mode == 'recover' && needs.candidate-preflight.outputs.candidate-sha || needs.prepare-version-files.outputs.candidate-sha) || github.ref }}
112
217
  - name: Create or resume promotion PR
218
+ if: ${{ inputs.mode == 'prepare' }}
113
219
  uses: vypdev/copilot@v3
114
220
  with:
115
221
  single-action: prepare_deployment_action
@@ -140,6 +246,44 @@ jobs:
140
246
  issues-locale: ${{ vars.ISSUES_LOCALE || '' }}
141
247
  pull-requests-locale: ${{ vars.PULL_REQUESTS_LOCALE || '' }}
142
248
  token: ${{ secrets.PAT }}
249
+ - name: Replace or recover unpublished candidate
250
+ if: ${{ inputs.mode == 'replace' }}
251
+ uses: vypdev/copilot@v3
252
+ with:
253
+ single-action: replace_deployment_action
254
+ single-action-operation-id: ${{ needs.candidate-preflight.outputs.operation-id }}
255
+ single-action-expected-revision: ${{ needs.candidate-preflight.outputs.expected-revision }}
256
+ single-action-reviewed-source-sha: ${{ inputs.source-sha }}
257
+ single-action-candidate-sha: ${{ needs.candidate-preflight.outputs.mode == 'recover' && needs.candidate-preflight.outputs.candidate-sha || needs.prepare-version-files.outputs.candidate-sha }}
258
+ single-action-production-base-sha: ${{ needs.candidate-preflight.outputs.production-base-sha }}
259
+ single-action-confirm-unpublished: ${{ inputs.confirm-unpublished }}
260
+ single-action-issue: ${{ inputs.issue }}
261
+ issue-workflow-profile: ${{ vars.COPILOT_ISSUE_WORKFLOW_PROFILE || '' }}
262
+ single-action-version: ${{ inputs.version }}
263
+ single-action-title: ${{ inputs.title }}
264
+ single-action-changelog: ${{ inputs.changelog }}
265
+ main-branch: ${{ vars.MAIN_BRANCH || 'master' }}
266
+ development-branch: ${{ vars.DEVELOPMENT_BRANCH || 'develop' }}
267
+ release-tree: ${{ vars.RELEASE_TREE || 'release' }}
268
+ hotfix-tree: ${{ vars.HOTFIX_TREE || 'hotfix' }}
269
+ release-workflow: ${{ vars.RELEASE_WORKFLOW || 'release_workflow.yml' }}
270
+ hotfix-workflow: ${{ vars.HOTFIX_WORKFLOW || 'hotfix_workflow.yml' }}
271
+ release-reconciliation-strategy: ${{ vars.RELEASE_RECONCILIATION_STRATEGY || 'production-lineage' }}
272
+ hotfix-reconciliation-strategy: ${{ vars.HOTFIX_RECONCILIATION_STRATEGY || 'production-lineage' }}
273
+ reconciliation-pr-mode: ${{ vars.RECONCILIATION_PR_MODE || 'auto' }}
274
+ merge-queue-check-attestations: ${{ vars.MERGE_QUEUE_CHECK_ATTESTATIONS || '[]' }}
275
+ reconciliation-backmerge-mode: ${{ vars.RECONCILIATION_BACKMERGE_MODE || 'auto' }}
276
+ hotfix-active-release-policy: ${{ vars.HOTFIX_ACTIVE_RELEASE_POLICY || 'prefer-release' }}
277
+ reconciliation-tree: ${{ vars.RECONCILIATION_TREE || 'sync' }}
278
+ reconciliation-cleanup: ${{ vars.RECONCILIATION_CLEANUP || 'all' }}
279
+ reconciliation-issue-completion: ${{ vars.RECONCILIATION_ISSUE_COMPLETION || 'close' }}
280
+ orchestration-presentation-mode: ${{ vars.ORCHESTRATION_PRESENTATION_MODE || 'guided' }}
281
+ orchestration-diagrams: ${{ vars.ORCHESTRATION_DIAGRAMS || 'true' }}
282
+ orchestration-comment-mode: ${{ vars.ORCHESTRATION_COMMENT_MODE || 'update' }}
283
+ repository-locale: ${{ vars.REPOSITORY_LOCALE || 'en-US' }}
284
+ issues-locale: ${{ vars.ISSUES_LOCALE || '' }}
285
+ pull-requests-locale: ${{ vars.PULL_REQUESTS_LOCALE || '' }}
286
+ token: ${{ secrets.PAT }}
143
287
 
144
288
  publish:
145
289
  name: Publish accepted production commit
@@ -239,8 +383,8 @@ jobs:
239
383
  name: Report orchestration failure
240
384
  runs-on: ubuntu-latest
241
385
  timeout-minutes: 5
242
- needs: [validate, prepare-version-files, promote, publish, resume-reconciliation]
243
- if: ${{ failure() && needs.validate.result == 'success' }}
386
+ needs: [validate, candidate-preflight, prepare-version-files, promote, publish, resume-reconciliation]
387
+ if: ${{ failure() && needs.validate.result == 'success' && inputs.mode != 'replace' }}
244
388
  permissions:
245
389
  issues: write
246
390
  steps: