From 3a519b475e244c6a4057891cc57fc165b96bf7bf Mon Sep 17 00:00:00 2001 From: David East Date: Wed, 4 Mar 2026 11:52:49 -0500 Subject: [PATCH] feat(fleet): expand label triggers and wire init wizard auth Item 1: Expand label workflow triggers from [opened] to [opened, edited, synchronize] to catch late-linked issues. Item 3: Wire fleet init wizard auth mode to template builders. - Add AuthModeSchema to InitInputSchema (TSC spec) - Pass input.auth through InitHandler to buildWorkflowTemplates - Pass wizardResult.authMethod in CLI init command Includes 5 new contract tests for AuthModeSchema and label triggers. --- .../src/__tests__/configure-init-spec.test.ts | 46 +++++++++++++++++++ .../src/__tests__/label-template.test.ts | 6 ++- packages/fleet/src/cli/init.command.ts | 3 +- packages/fleet/src/init/handler.ts | 2 +- packages/fleet/src/init/spec.ts | 7 +++ packages/fleet/src/init/templates/label.ts | 2 +- 6 files changed, 62 insertions(+), 4 deletions(-) diff --git a/packages/fleet/src/__tests__/configure-init-spec.test.ts b/packages/fleet/src/__tests__/configure-init-spec.test.ts index c7fa756..c6ea9a2 100644 --- a/packages/fleet/src/__tests__/configure-init-spec.test.ts +++ b/packages/fleet/src/__tests__/configure-init-spec.test.ts @@ -104,4 +104,50 @@ describe('InitInputSchema (Contract Tests)', () => { }); expect(result.success).toBe(false); }); + + // ── Auth mode contract tests ────────────────────────────── + + it('accepts auth=app', () => { + const result = InitInputSchema.safeParse({ + owner: 'google', + repoName: 'sdk', + auth: 'app', + }); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.auth).toBe('app'); + } + }); + + it('accepts auth=token', () => { + const result = InitInputSchema.safeParse({ + owner: 'google', + repoName: 'sdk', + auth: 'token', + }); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.auth).toBe('token'); + } + }); + + it('defaults auth to token when omitted', () => { + const result = InitInputSchema.safeParse({ + owner: 'google', + repoName: 'sdk', + }); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.auth).toBe('token'); + } + }); + + it('rejects unknown auth modes', () => { + const result = InitInputSchema.safeParse({ + owner: 'google', + repoName: 'sdk', + auth: 'oauth', + }); + expect(result.success).toBe(false); + }); }); diff --git a/packages/fleet/src/__tests__/label-template.test.ts b/packages/fleet/src/__tests__/label-template.test.ts index 6084c76..b2087b5 100644 --- a/packages/fleet/src/__tests__/label-template.test.ts +++ b/packages/fleet/src/__tests__/label-template.test.ts @@ -21,10 +21,14 @@ describe('FLEET_LABEL_TEMPLATE', () => { expect(content).toContain('name: Fleet Label PR'); expect(content).toContain('pull_request:'); - expect(content).toContain('types: [opened]'); expect(content).toContain('runs-on: ubuntu-latest'); expect(content).toContain('gh pr edit'); expect(content).toContain('fleet-merge-ready'); expect(content).toContain('gh issue view "$ISSUE_NUMBER"'); }); + + it('triggers on opened, edited, and synchronize', () => { + const content = FLEET_LABEL_TEMPLATE.content; + expect(content).toContain('types: [opened, edited, synchronize]'); + }); }); diff --git a/packages/fleet/src/cli/init.command.ts b/packages/fleet/src/cli/init.command.ts index 1e60704..444852d 100644 --- a/packages/fleet/src/cli/init.command.ts +++ b/packages/fleet/src/cli/init.command.ts @@ -126,7 +126,7 @@ export default defineCommand({ // ── Dry run: list files and exit ── if (dryRun) { - const files = buildWorkflowTemplates(intervalMinutes).map((t) => t.repoPath); + const files = buildWorkflowTemplates(intervalMinutes, wizardResult.authMethod).map((t) => t.repoPath); files.push('.fleet/goals/example.md'); emit({ type: 'init:dry-run', files }); renderer.end(`Dry run complete. ${files.length} files would be created.`); @@ -142,6 +142,7 @@ export default defineCommand({ overwrite, features, intervalMinutes, + auth: wizardResult.authMethod, }); const octokit = createFleetOctokit(); diff --git a/packages/fleet/src/init/handler.ts b/packages/fleet/src/init/handler.ts index 77f7ec8..e7cc0c2 100644 --- a/packages/fleet/src/init/handler.ts +++ b/packages/fleet/src/init/handler.ts @@ -89,7 +89,7 @@ export class InitHandler implements InitSpec { }; // 2. Resolve which templates to commit - let templates = buildWorkflowTemplates(input.intervalMinutes); + let templates = buildWorkflowTemplates(input.intervalMinutes, input.auth); if (input.features) { const reconciler = new FeatureReconcileHandler(this.octokit); const featureResult = await reconciler.execute({ diff --git a/packages/fleet/src/init/spec.ts b/packages/fleet/src/init/spec.ts index 40054fe..4f8a025 100644 --- a/packages/fleet/src/init/spec.ts +++ b/packages/fleet/src/init/spec.ts @@ -14,6 +14,11 @@ import { z } from 'zod'; +// ── AUTH MODE ─────────────────────────────────────────────────────── + +export const AuthModeSchema = z.enum(['token', 'app']).default('token'); +export type AuthMode = z.infer; + // ── INPUT ─────────────────────────────────────────────────────────── export const InitInputSchema = z.object({ @@ -34,6 +39,8 @@ export const InitInputSchema = z.object({ features: z.record(z.string(), z.boolean()).optional(), /** Pipeline cadence in minutes (min 5 per GitHub Actions, default 360 = 6h) */ intervalMinutes: z.number().min(5).default(360), + /** Auth mode: 'token' uses secrets.GITHUB_TOKEN, 'app' generates a GitHub App token */ + auth: AuthModeSchema, }); export type InitInput = z.infer; diff --git a/packages/fleet/src/init/templates/label.ts b/packages/fleet/src/init/templates/label.ts index ec26cb7..2bf23cd 100644 --- a/packages/fleet/src/init/templates/label.ts +++ b/packages/fleet/src/init/templates/label.ts @@ -21,7 +21,7 @@ export const FLEET_LABEL_TEMPLATE: WorkflowTemplate = { content: `name: Fleet Label PR on: pull_request: - types: [opened] + types: [opened, edited, synchronize] permissions: pull-requests: write