switchman-dev 0.1.6 → 0.1.8

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.
package/src/core/ci.js CHANGED
@@ -40,6 +40,66 @@ export function formatCiGateMarkdown(result) {
40
40
  ].join('\n');
41
41
  }
42
42
 
43
+ function getPipelineLandingCheckInfo(result) {
44
+ if (result.queue_state?.status === 'merged') {
45
+ return {
46
+ status: 'success',
47
+ title: 'Pipeline merged to target branch',
48
+ summary: result.queue_state.merged_commit
49
+ ? `Merged as ${result.queue_state.merged_commit}.`
50
+ : 'Merged successfully.',
51
+ };
52
+ }
53
+
54
+ if (['queued', 'validating', 'rebasing', 'merging', 'retrying'].includes(result.queue_state?.status)) {
55
+ return {
56
+ status: result.queue_state.status === 'retrying' ? 'action_required' : 'pending',
57
+ title: result.queue_state.status === 'retrying'
58
+ ? 'Pipeline is retrying in the landing queue'
59
+ : 'Pipeline is in the landing queue',
60
+ summary: result.queue_state.next_action || result.next_action,
61
+ };
62
+ }
63
+
64
+ if (result.ready_to_queue) {
65
+ return {
66
+ status: 'success',
67
+ title: 'Pipeline ready to queue',
68
+ summary: result.queue_state?.next_action || result.next_action,
69
+ };
70
+ }
71
+
72
+ if ((result.stale_clusters?.length || 0) > 0) {
73
+ return {
74
+ status: 'action_required',
75
+ title: 'Pipeline has stale work to revalidate',
76
+ summary: result.stale_clusters[0]?.next_action || result.queue_state?.next_action || result.next_action,
77
+ };
78
+ }
79
+
80
+ if (result.landing_error || result.landing?.last_failure) {
81
+ return {
82
+ status: 'action_required',
83
+ title: 'Pipeline landing needs attention',
84
+ summary: result.queue_state?.next_action || result.next_action,
85
+ };
86
+ }
87
+
88
+ if (result.landing?.stale) {
89
+ return {
90
+ status: 'action_required',
91
+ title: 'Pipeline landing is stale',
92
+ summary: result.queue_state?.next_action || result.next_action,
93
+ };
94
+ }
95
+
96
+ return {
97
+ status: 'action_required',
98
+ title: 'Pipeline not ready to queue',
99
+ summary: result.queue_state?.next_action || result.next_action,
100
+ };
101
+ }
102
+
43
103
  export function writeGitHubCiStatus({ result, stepSummaryPath = null, outputPath = null }) {
44
104
  const markdown = formatCiGateMarkdown(result);
45
105
 
@@ -67,6 +127,126 @@ export function writeGitHubCiStatus({ result, stepSummaryPath = null, outputPath
67
127
  };
68
128
  }
69
129
 
130
+ export function formatPipelineLandingMarkdown(result) {
131
+ const checkInfo = getPipelineLandingCheckInfo(result);
132
+ const landingStateLines = result.landing_error
133
+ ? [`- ${result.landing_error}`]
134
+ : result.landing?.last_failure
135
+ ? [
136
+ `- Failure: ${result.landing.last_failure.reason_code || 'landing_branch_materialization_failed'}`,
137
+ ...(result.landing.last_failure.failed_branch ? [`- Failed branch: ${result.landing.last_failure.failed_branch}`] : []),
138
+ ...(result.landing.last_failure.conflicting_files?.length
139
+ ? [`- Conflicts: ${result.landing.last_failure.conflicting_files.join(', ')}`]
140
+ : []),
141
+ ]
142
+ : result.landing?.stale
143
+ ? (result.landing.stale_reasons?.length
144
+ ? result.landing.stale_reasons.map((reason) => `- Stale: ${reason.summary}`)
145
+ : ['- Landing branch is stale and needs refresh'])
146
+ : ['- Current and ready for queueing'];
147
+ return [
148
+ '# Switchman Pipeline Landing',
149
+ '',
150
+ '## Check Summary',
151
+ `- Name: Switchman Pipeline Landing`,
152
+ `- Status: ${checkInfo.status}`,
153
+ `- Title: ${checkInfo.title}`,
154
+ `- Summary: ${checkInfo.summary}`,
155
+ '',
156
+ '## Pipeline',
157
+ `- Pipeline: ${result.pipeline_id}`,
158
+ `- Ready to queue: ${result.ready_to_queue ? 'yes' : 'no'}`,
159
+ `- Landing branch: ${result.landing.branch}`,
160
+ `- Strategy: ${result.landing.strategy}`,
161
+ `- Synthetic: ${result.landing.synthetic ? 'yes' : 'no'}`,
162
+ '',
163
+ '## Component Branches',
164
+ ...(result.landing.component_branches?.length
165
+ ? result.landing.component_branches.map((branch) => `- ${branch}`)
166
+ : ['- None']),
167
+ '',
168
+ '## Landing State',
169
+ ...landingStateLines,
170
+ '',
171
+ '## Recovery State',
172
+ ...(result.recovery_state
173
+ ? [
174
+ `- Status: ${result.recovery_state.status}`,
175
+ ...(result.recovery_state.recovery_path ? [`- Path: ${result.recovery_state.recovery_path}`] : []),
176
+ ...(result.recovery_state.resume_command ? [`- Resume: ${result.recovery_state.resume_command}`] : []),
177
+ ]
178
+ : ['- No active recovery worktree']),
179
+ '',
180
+ '## Stale Clusters',
181
+ ...(result.stale_clusters?.length
182
+ ? result.stale_clusters.map((cluster) => `- ${cluster.title}: ${cluster.detail} -> ${cluster.next_action}`)
183
+ : ['- None']),
184
+ '',
185
+ '## Stale Waves',
186
+ ...(result.stale_causal_waves?.length
187
+ ? result.stale_causal_waves.map((wave) => `- ${wave.summary}: affects ${wave.affected_pipeline_ids.join(', ') || 'unknown'} -> ${wave.cluster_count} cluster(s), ${wave.invalidation_count} invalidation(s)`)
188
+ : ['- None']),
189
+ '',
190
+ '## Policy & Stale Audit',
191
+ ...(result.trust_audit?.length
192
+ ? result.trust_audit.map((entry) => `- ${entry.created_at}: [${entry.category}] ${entry.summary} -> ${entry.next_action}`)
193
+ : ['- No recent policy or stale-wave audit entries']),
194
+ '',
195
+ '## Queue State',
196
+ `- Status: ${result.queue_state?.status || 'not_queued'}`,
197
+ ...(result.queue_state?.item_id ? [`- Item: ${result.queue_state.item_id}`] : []),
198
+ `- Target branch: ${result.queue_state?.target_branch || 'main'}`,
199
+ ...(result.queue_state?.merged_commit ? [`- Merged commit: ${result.queue_state.merged_commit}`] : []),
200
+ ...(result.queue_state?.last_error_summary ? [`- Queue error: ${result.queue_state.last_error_summary}`] : []),
201
+ ...(result.queue_state?.policy_override_summary ? [`- Policy override: ${result.queue_state.policy_override_summary}`] : []),
202
+ '',
203
+ '## Next Action',
204
+ `- ${result.next_action}`,
205
+ ].join('\n');
206
+ }
207
+
208
+ export function writeGitHubPipelineLandingStatus({ result, stepSummaryPath = null, outputPath = null }) {
209
+ const markdown = formatPipelineLandingMarkdown(result);
210
+ const checkInfo = getPipelineLandingCheckInfo(result);
211
+
212
+ if (stepSummaryPath) {
213
+ writeFileSync(stepSummaryPath, `${markdown}\n`);
214
+ }
215
+
216
+ if (outputPath) {
217
+ const lines = [
218
+ `switchman_pipeline_id=${result.pipeline_id}`,
219
+ `switchman_pipeline_ready=${result.ready_to_queue ? 'true' : 'false'}`,
220
+ `switchman_landing_branch=${result.landing.branch}`,
221
+ `switchman_landing_strategy=${result.landing.strategy}`,
222
+ `switchman_landing_synthetic=${result.landing.synthetic ? 'true' : 'false'}`,
223
+ `switchman_queue_status=${result.queue_state?.status || 'not_queued'}`,
224
+ `switchman_queue_item_id=${result.queue_state?.item_id || ''}`,
225
+ `switchman_queue_target_branch=${result.queue_state?.target_branch || 'main'}`,
226
+ `switchman_queue_merged_commit=${result.queue_state?.merged_commit || ''}`,
227
+ `switchman_stale_cluster_count=${result.stale_clusters?.length || 0}`,
228
+ `switchman_stale_cluster_summary=${JSON.stringify((result.stale_clusters || []).map((cluster) => `${cluster.affected_pipeline_id || cluster.affected_task_ids[0]}:${cluster.invalidation_count}`).join(' | '))}`,
229
+ `switchman_stale_wave_count=${result.stale_causal_waves?.length || 0}`,
230
+ `switchman_stale_wave_summary=${JSON.stringify((result.stale_causal_waves || []).map((wave) => `${wave.summary}:${wave.affected_pipeline_ids.join(',')}`).join(' | '))}`,
231
+ `switchman_trust_audit_count=${result.trust_audit?.length || 0}`,
232
+ `switchman_trust_audit_summary=${JSON.stringify((result.trust_audit || []).slice(0, 3).map((entry) => `${entry.category}:${entry.summary}`).join(' | '))}`,
233
+ `switchman_policy_override_summary=${JSON.stringify(result.policy_override_summary || '')}`,
234
+ `switchman_landing_next_action=${JSON.stringify(result.next_action)}`,
235
+ `switchman_check_name=${JSON.stringify('Switchman Pipeline Landing')}`,
236
+ `switchman_check_status=${checkInfo.status}`,
237
+ `switchman_check_title=${JSON.stringify(checkInfo.title)}`,
238
+ `switchman_check_summary=${JSON.stringify(checkInfo.summary)}`,
239
+ ];
240
+ writeFileSync(outputPath, `${lines.join('\n')}\n`);
241
+ }
242
+
243
+ return {
244
+ markdown,
245
+ wrote_step_summary: Boolean(stepSummaryPath),
246
+ wrote_output: Boolean(outputPath),
247
+ };
248
+ }
249
+
70
250
  export function installGitHubActionsWorkflow(repoRoot, workflowName = 'switchman-gate.yml') {
71
251
  const workflowsDir = join(repoRoot, '.github', 'workflows');
72
252
  if (!existsSync(workflowsDir)) {
@@ -84,6 +264,7 @@ on:
84
264
 
85
265
  jobs:
86
266
  switchman-gate:
267
+ name: Switchman Gate
87
268
  runs-on: ubuntu-latest
88
269
  steps:
89
270
  - name: Checkout
@@ -92,13 +273,36 @@ jobs:
92
273
  - name: Setup Node
93
274
  uses: actions/setup-node@v4
94
275
  with:
95
- node-version: 20
276
+ node-version: 22
96
277
 
97
278
  - name: Install Switchman
98
279
  run: npm install -g switchman-dev
99
280
 
100
281
  - name: Run Switchman CI gate
282
+ id: switchman_gate
283
+ continue-on-error: true
101
284
  run: switchman gate ci --github
285
+
286
+ - name: Summarize Switchman result
287
+ if: always()
288
+ run: |
289
+ echo "Switchman status: \${{ steps.switchman_gate.outputs.switchman_ok }}"
290
+ echo "Switchman summary: \${{ steps.switchman_gate.outputs.switchman_summary }}"
291
+
292
+ - name: Enforce Switchman gate
293
+ if: always()
294
+ run: |
295
+ if [ "\${{ steps.switchman_gate.outputs.switchman_ok }}" != "true" ]; then
296
+ echo "Switchman blocked this change."
297
+ echo "\${{ steps.switchman_gate.outputs.switchman_summary }}"
298
+ exit 1
299
+ fi
300
+
301
+ - name: Sync Switchman PR state
302
+ if: github.event_name == 'pull_request'
303
+ continue-on-error: true
304
+ run: |
305
+ switchman pipeline sync-pr --pipeline-from-env --skip-missing-pipeline --pr-from-env --github
102
306
  `;
103
307
 
104
308
  writeFileSync(workflowPath, workflow);