diff --git a/.changeset/repo-move-github-squad-package-metadata.md b/.changeset/repo-move-github-squad-package-metadata.md new file mode 100644 index 000000000..2d11c4634 --- /dev/null +++ b/.changeset/repo-move-github-squad-package-metadata.md @@ -0,0 +1,6 @@ +--- +'@bradygaster/squad-cli': patch +'@bradygaster/squad-sdk': patch +--- + +Point published package metadata and the SDK's build and distribution defaults at the repository's new home, `github/squad`, so npm provenance publishing keeps working after the move. diff --git a/.github/workflows/squad-agents-ai-release.yml b/.github/workflows/squad-agents-ai-release.yml index 0fd1e741e..efe6d8ae5 100644 --- a/.github/workflows/squad-agents-ai-release.yml +++ b/.github/workflows/squad-agents-ai-release.yml @@ -5,7 +5,7 @@ # short-lived (1 hour) NuGet API key via the NuGet/login@v1 action. A Trusted # Publishing policy must be configured at https://www.nuget.org/account/trusted-publishing # under the package owner (Squad organization) with: -# Repository Owner: bradygaster +# Repository Owner: github # Repository: squad # Workflow File: squad-agents-ai-release.yml # Environment: (leave empty) diff --git a/.github/workflows/squad-ci.yml b/.github/workflows/squad-ci.yml index 968ec1911..09e07c7f4 100644 --- a/.github/workflows/squad-ci.yml +++ b/.github/workflows/squad-ci.yml @@ -635,7 +635,7 @@ jobs: # # EXTERNAL ACTION REQUIRED after this PR merges to close the # merge-with-failed-checks gap that allowed #1873 and #1874 to merge: - # 1. https://github.com/bradygaster/squad/settings/rules/12703724 + # 1. https://github.com/github/squad/settings/rules/12703724 # 2. Set enforcement → Active # 3. Conditions → branch patterns: refs/heads/dev AND refs/heads/main # 4. Rules → Required status checks → add: diff --git a/.github/workflows/squad-standalone-release.yml b/.github/workflows/squad-standalone-release.yml index da4e75983..b85a0b311 100644 --- a/.github/workflows/squad-standalone-release.yml +++ b/.github/workflows/squad-standalone-release.yml @@ -1082,7 +1082,7 @@ jobs: --base master \ --head "tamirdresher:${BRANCH}" \ --title "New version: ${PACKAGE_IDENTIFIER} version ${VERSION}" \ - --body "Automated manifest update for [Squad ${TAG}](https://github.com/bradygaster/squad/releases/tag/${TAG}), using the release run's packaging-manifests artifact.")" + --body "Automated manifest update for [Squad ${TAG}](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${TAG}), using the release run's packaging-manifests artifact.")" pr_status="Opened" else pr_status="Reused" diff --git a/package.json b/package.json index 067687eb9..87cb0b6f2 100644 --- a/package.json +++ b/package.json @@ -69,12 +69,12 @@ "sdk" ], "license": "MIT", - "homepage": "https://github.com/bradygaster/squad#readme", + "homepage": "https://github.com/github/squad#readme", "bugs": { - "url": "https://github.com/bradygaster/squad/issues" + "url": "https://github.com/github/squad/issues" }, "repository": { "type": "git", - "url": "https://github.com/bradygaster/squad.git" + "url": "https://github.com/github/squad.git" } } diff --git a/packages/squad-cli/package.json b/packages/squad-cli/package.json index 0d1fd2d2e..9afd492f8 100644 --- a/packages/squad-cli/package.json +++ b/packages/squad-cli/package.json @@ -199,13 +199,13 @@ "cli" ], "license": "MIT", - "homepage": "https://github.com/bradygaster/squad#readme", + "homepage": "https://github.com/github/squad#readme", "bugs": { - "url": "https://github.com/bradygaster/squad/issues" + "url": "https://github.com/github/squad/issues" }, "repository": { "type": "git", - "url": "git+https://github.com/bradygaster/squad.git", + "url": "git+https://github.com/github/squad.git", "directory": "packages/squad-cli" }, "optionalDependencies": { diff --git a/packages/squad-sdk/package.json b/packages/squad-sdk/package.json index ca1ee8f3f..7cbe54362 100644 --- a/packages/squad-sdk/package.json +++ b/packages/squad-sdk/package.json @@ -280,13 +280,13 @@ "sdk" ], "license": "MIT", - "homepage": "https://github.com/bradygaster/squad#readme", + "homepage": "https://github.com/github/squad#readme", "bugs": { - "url": "https://github.com/bradygaster/squad/issues" + "url": "https://github.com/github/squad/issues" }, "repository": { "type": "git", - "url": "git+https://github.com/bradygaster/squad.git", + "url": "git+https://github.com/github/squad.git", "directory": "packages/squad-sdk" } } diff --git a/packages/squad-sdk/src/build/github-dist.ts b/packages/squad-sdk/src/build/github-dist.ts index feb71f796..4f64441a1 100644 --- a/packages/squad-sdk/src/build/github-dist.ts +++ b/packages/squad-sdk/src/build/github-dist.ts @@ -11,7 +11,7 @@ export interface GitHubDistConfig { } const DEFAULT_CONFIG: GitHubDistConfig = { - owner: 'bradygaster', + owner: 'github', repo: 'squad', binaryName: 'squad', installCommandTemplate: 'npx @bradygaster/squad-cli', diff --git a/packages/squad-sdk/src/build/npm-package.ts b/packages/squad-sdk/src/build/npm-package.ts index 6b6088e32..c82b29922 100644 --- a/packages/squad-sdk/src/build/npm-package.ts +++ b/packages/squad-sdk/src/build/npm-package.ts @@ -94,7 +94,7 @@ export function generatePackageJson(config: NpmPackageConfig): PackageJsonOutput : {}), engines: config.engines ? { ...config.engines } : { node: '>=20.0.0' }, license: 'MIT', - repository: { type: 'git', url: 'https://github.com/bradygaster/squad.git' }, + repository: { type: 'git', url: 'https://github.com/github/squad.git' }, keywords: ['copilot', 'multi-agent', 'squad', 'sdk'], }; } diff --git a/scripts/generate-packaging.mjs b/scripts/generate-packaging.mjs index e0ceb53d2..018d2dc66 100644 --- a/scripts/generate-packaging.mjs +++ b/scripts/generate-packaging.mjs @@ -31,9 +31,10 @@ import { fileURLToPath } from 'node:url'; const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); -const DEFAULT_REPO = 'bradygaster/squad'; +const DEFAULT_REPO = 'github/squad'; const PACKAGE_IDENTIFIER = 'bradygaster.Squad'; -const HOMEPAGE = 'https://github.com/bradygaster/squad'; +const HOMEPAGE = 'https://github.com/github/squad'; +const DOCS_URL = 'https://github.github.com/squad/'; /** Manifest schema version. Matches what winget-pkgs currently accepts. */ const WINGET_SCHEMA = '1.12.0'; @@ -309,7 +310,7 @@ Tags: ReleaseNotesUrl: https://github.com/${repo}/releases/tag/${version} Documentations: - DocumentLabel: Documentation - DocumentUrl: https://bradygaster.github.io/squad/ + DocumentUrl: ${DOCS_URL} ManifestType: defaultLocale ManifestVersion: ${WINGET_SCHEMA} `; diff --git a/scripts/install.sh b/scripts/install.sh index 4376697dd..a9d3d6933 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,7 +1,7 @@ #!/bin/sh # Squad standalone installer. # -# curl -fsSL https://raw.githubusercontent.com/bradygaster/squad/dev/scripts/install.sh | sh +# curl -fsSL https://raw.githubusercontent.com/github/squad/dev/scripts/install.sh | sh # # Downloads a self-contained Squad bundle from GitHub Releases. The bundle # vendors its own Node.js runtime, so neither Node nor npm needs to be present. @@ -10,13 +10,13 @@ # Environment: # VERSION Squad version to install (default: latest release) # PREFIX Install prefix (default: /usr/local as root, else $HOME/.local) -# REPO Source repository (default: bradygaster/squad) +# REPO Source repository (default: github/squad) # # Mirrors the shape of GitHub Copilot CLI's installer so the two feel the same. set -eu -REPO="${REPO:-bradygaster/squad}" +REPO="${REPO:-github/squad}" VERSION="${VERSION:-latest}" if [ -z "${PREFIX:-}" ]; then diff --git a/scripts/release/closure-items.json b/scripts/release/closure-items.json index c6bce005f..aff7cbd25 100644 --- a/scripts/release/closure-items.json +++ b/scripts/release/closure-items.json @@ -3,7 +3,7 @@ "source": "squad-2.0-closure-matrix.md (reviewer closure matrix for Stack #2281), carried from the f22847a9 re-verification", "stack": { "id": 2281, - "repo": "bradygaster/squad", + "repo": "github/squad", "base": "559e0f98e611b6006cf5da716dcb75fc3f5000b5", "mergedDev": "f22847a9898d5c90d70a679ee63d9d5f49cd5fed", "layers": [ @@ -38,7 +38,7 @@ ], "citedCiRun": { "id": 39907736729, - "url": "https://github.com/bradygaster/squad/actions/runs/39907736729", + "url": "https://github.com/github/squad/actions/runs/39907736729", "workflow": "Squad CI", "headSha": "f22847a9898d5c90d70a679ee63d9d5f49cd5fed", "consoleSummary": { diff --git a/src/Squad.Agents.AI/README.md b/src/Squad.Agents.AI/README.md index f18d59ef9..752e8b60e 100644 --- a/src/Squad.Agents.AI/README.md +++ b/src/Squad.Agents.AI/README.md @@ -13,7 +13,7 @@ Public surface: - `SquadConnectionFactory` — parses PATH or `squad://` connection strings into options. - `SquadServiceCollectionExtensions` — registers `SquadAgent` and base `AIAgent` in DI, with standard and keyed overloads. -Repository: +Repository: ## Install @@ -32,7 +32,7 @@ dotnet add package Squad.Agents.AI --version 1.0.0 --source ./nupkgs - .NET 8, 9, or 10 for consumers; .NET 10 SDK to build this repository and run the sample. - GitHub Copilot CLI available on `PATH` (`copilot --version`). -- Squad CLI and an initialized Squad team root; see the [Squad CLI repo](https://github.com/bradygaster/squad). +- Squad CLI and an initialized Squad team root; see the [Squad CLI repo](https://github.com/github/squad). - GitHub Copilot authentication through the signed-in user. The quickstart below does not require an app key or environment variable. For a bounded sample check without authentication, CLI startup, or model calls: @@ -272,7 +272,7 @@ the `ConfigureCopilotClient` delegate, streaming via `RunStreamingAsync`, and su |---|---| | [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) | `dotnet --version` should print `10.x.x` | | GitHub Copilot CLI on `PATH` | Install from [github.com/github/copilot-cli](https://github.com/github/copilot-cli); verify with `copilot --version` | -| Squad CLI installed | Follow the install guide in the [Squad repository README](https://github.com/bradygaster/squad#readme) | +| Squad CLI installed | Follow the install guide in the [Squad repository README](https://github.com/github/squad#readme) | | Initialized Squad team root | Run `squad init` in a directory; this becomes your team root | | GitHub Copilot authentication | Run `gh auth login` or `copilot auth login` once before running the sample | @@ -340,10 +340,10 @@ not prove that subagents were dispatched; inspect the captured events and spans. ## See also -- Root repo: +- Root repo: - Microsoft Agent Framework: - GitHub Copilot SDK authentication: - GitHub Copilot CLI: -- Changelog: +- Changelog: License: MIT. diff --git a/src/Squad.Agents.AI/Squad.Agents.AI.csproj b/src/Squad.Agents.AI/Squad.Agents.AI.csproj index 562c28b6e..0cf19228f 100644 --- a/src/Squad.Agents.AI/Squad.Agents.AI.csproj +++ b/src/Squad.Agents.AI/Squad.Agents.AI.csproj @@ -10,8 +10,8 @@ Community SquadAgent for Microsoft Agent Framework. Exposes the Squad multi-agent CLI as an AIAgent that can be composed into any MAF app or workflow. Tamir Dresher squad;agents;ai;copilot;maf;multi-agent - https://github.com/bradygaster/squad - https://github.com/bradygaster/squad.git + https://github.com/github/squad + https://github.com/github/squad.git git main MIT diff --git a/test/bundle.test.ts b/test/bundle.test.ts index 45f1e4243..0b158487a 100644 --- a/test/bundle.test.ts +++ b/test/bundle.test.ts @@ -256,6 +256,15 @@ describe('npm-package', () => { const entry = pkg.exports['.'] as Record; expect(entry.types).toBe('./dist/index.d.ts'); }); + + it('points repository at the canonical github/squad source repo', () => { + // npm --provenance rejects a publish whose repository.url names a different repo. + const pkg = generatePackageJson(baseConfig); + expect(pkg.repository, 'generatePackageJson().repository').toEqual({ + type: 'git', + url: 'https://github.com/github/squad.git', + }); + }); }); describe('validatePackageJson', () => { @@ -378,9 +387,9 @@ describe('github-dist', () => { expect(script).toContain('REPO="myrepo"'); }); - it('uses bradygaster/squad as default', () => { + it('uses github/squad as default', () => { const script = generateInstallScript(); - expect(script).toContain('OWNER="bradygaster"'); + expect(script).toContain('OWNER="github"'); expect(script).toContain('REPO="squad"'); }); @@ -398,7 +407,7 @@ describe('github-dist', () => { describe('validateGitHubRelease', () => { const config: GitHubDistConfig = { - owner: 'bradygaster', + owner: 'github', repo: 'squad', binaryName: 'squad', installCommandTemplate: 'npx @bradygaster/squad-cli', @@ -481,9 +490,9 @@ describe('github-dist', () => { }); describe('getDefaultDistConfig', () => { - it('returns config with bradygaster owner', () => { + it('returns config with github owner', () => { const config = getDefaultDistConfig(); - expect(config.owner).toBe('bradygaster'); + expect(config.owner).toBe('github'); }); it('returns config with squad repo', () => { @@ -495,7 +504,7 @@ describe('github-dist', () => { const a = getDefaultDistConfig(); a.owner = 'changed'; const b = getDefaultDistConfig(); - expect(b.owner).toBe('bradygaster'); + expect(b.owner).toBe('github'); }); }); }); diff --git a/test/packaging-manifests.test.ts b/test/packaging-manifests.test.ts index dcccd75e3..51fb4a697 100644 --- a/test/packaging-manifests.test.ts +++ b/test/packaging-manifests.test.ts @@ -8,6 +8,8 @@ */ import { describe, it, expect } from 'vitest'; +import { execFileSync } from 'node:child_process'; +import path from 'node:path'; import { parseChecksums, homebrewToken, @@ -19,6 +21,8 @@ import { wingetPackageIdentifier, } from '../scripts/generate-packaging.mjs'; +const GENERATOR = path.join(process.cwd(), 'scripts', 'generate-packaging.mjs'); + const SHA = { darwinArm: 'a'.repeat(64), darwinX64: 'b'.repeat(64), @@ -33,7 +37,7 @@ const CHECKSUMS = { 'squad-win32-arm64.zip': SHA.winArm, }; -const BASE = { version: 'v1.2.3', repo: 'bradygaster/squad', checksums: CHECKSUMS }; +const BASE = { version: 'v1.2.3', repo: 'github/squad', checksums: CHECKSUMS }; describe('parseChecksums', () => { it('parses GNU sha256sum output', () => { @@ -190,3 +194,39 @@ describe('release channel metadata', () => { expect(() => releaseChannel('v1.2.3-beta.1')).toThrow(/Unsupported release version/); }); }); + +describe('repository and documentation links', () => { + const rendered: Record = { + 'Homebrew cask': renderHomebrewCask(BASE), + 'winget version manifest': renderWingetVersion(BASE), + 'winget installer manifest': renderWingetInstaller(BASE), + 'winget locale manifest': renderWingetLocale(BASE), + }; + const lines = (manifest: string) => manifest.split(/\r?\n/).map((line) => line.trim()); + + it('points package homepages at github/squad and docs at github.github.com/squad', () => { + expect(lines(rendered['Homebrew cask'])).toContain('homepage "https://github.com/github/squad"'); + const locale = lines(rendered['winget locale manifest']); + expect(locale).toContain('PackageUrl: https://github.com/github/squad'); + expect(locale).toContain('DocumentUrl: https://github.github.com/squad/'); + }); + + it('never emits the pre-move repository or the retired docs host', () => { + for (const [name, manifest] of Object.entries(rendered)) { + const stale = lines(manifest).filter((line) => /bradygaster\/squad|bradygaster\.github\.io/.test(line)); + expect(stale, `${name} lines naming bradygaster/squad or bradygaster.github.io`).toEqual([]); + } + }); + + it('keeps PublisherUrl on the Publisher, which the repo move did not change', () => { + // Publisher is a person and matches the bradygaster.Squad identifier prefix; it is not the repo. + const locale = lines(rendered['winget locale manifest']); + expect(locale).toContain('Publisher: Brady Gaster'); + expect(locale).toContain('PublisherUrl: https://github.com/bradygaster'); + }); + + it('defaults --repo to github/squad', () => { + const help = execFileSync(process.execPath, [GENERATOR, '--help'], { encoding: 'utf8' }); + expect(help).toContain('--repo Source repository (default: github/squad)'); + }); +}); diff --git a/test/publish-repository-identity.test.ts b/test/publish-repository-identity.test.ts new file mode 100644 index 000000000..bc7997bb1 --- /dev/null +++ b/test/publish-repository-identity.test.ts @@ -0,0 +1,62 @@ +/** + * Published package metadata must name the repository that publishes it. + * + * squad-npm-publish.yml runs `npm publish --provenance`, and the npm registry + * rejects a provenance publish whose package.json repository.url names a + * different repository than the workflow that built it. These URLs went stale + * when the repo moved from bradygaster/squad to github/squad; this keeps the + * next move from silently breaking publication. + */ + +import { describe, expect, it } from 'vitest'; +import { readFileSync } from 'node:fs'; +import path from 'node:path'; + +const ROOT = process.cwd(); +const CANONICAL_REPO = 'github/squad'; +const CANONICAL_URL = `https://github.com/${CANONICAL_REPO}`; + +// The root manifest is private, but it is kept aligned so tooling sees one repository. +const MANIFESTS = [ + { file: 'package.json', directory: undefined }, + { file: 'packages/squad-cli/package.json', directory: 'packages/squad-cli' }, + { file: 'packages/squad-sdk/package.json', directory: 'packages/squad-sdk' }, +]; + +/** owner/repo from a GitHub https or git+https URL; null for anything else. */ +function githubSlug(url: unknown): string | null { + if (typeof url !== 'string') return null; + const match = url.match(/^(?:git\+)?https:\/\/github\.com\/([^/]+\/[^/#?]+?)(?:\.git)?\/?$/); + return match ? match[1] : null; +} + +describe.each(MANIFESTS)('$file', ({ file, directory }) => { + const pkg = JSON.parse(readFileSync(path.join(ROOT, file), 'utf8')); + + it(`names ${CANONICAL_REPO} in repository.url, which npm provenance checks`, () => { + const url = pkg.repository?.url; + expect(githubSlug(url), `${file} repository.url ${JSON.stringify(url)}`).toBe(CANONICAL_REPO); + expect(pkg.repository?.directory, `${file} repository.directory`).toBe(directory); + }); + + it(`points homepage and bugs.url at ${CANONICAL_REPO}`, () => { + expect(pkg.homepage, `${file} homepage`).toBe(`${CANONICAL_URL}#readme`); + expect(pkg.bugs?.url, `${file} bugs.url`).toBe(`${CANONICAL_URL}/issues`); + }); +}); + +describe('src/Squad.Agents.AI/Squad.Agents.AI.csproj', () => { + const csproj = readFileSync(path.join(ROOT, 'src', 'Squad.Agents.AI', 'Squad.Agents.AI.csproj'), 'utf8'); + const property = (name: string) => csproj.match(new RegExp(`<${name}>([^<]*)`))?.[1]; + + it(`points the NuGet project and repository URLs at ${CANONICAL_REPO}`, () => { + expect(property('PackageProjectUrl'), 'Squad.Agents.AI.csproj PackageProjectUrl').toBe(CANONICAL_URL); + expect(property('RepositoryUrl'), 'Squad.Agents.AI.csproj RepositoryUrl').toBe(`${CANONICAL_URL}.git`); + }); + + it('packs a README that links the current repository', () => { + const readme = readFileSync(path.join(ROOT, 'src', 'Squad.Agents.AI', 'README.md'), 'utf8'); + const stale = readme.split(/\r?\n/).filter((line) => line.includes('github.com/bradygaster/squad')); + expect(stale, 'src/Squad.Agents.AI/README.md lines linking github.com/bradygaster/squad').toEqual([]); + }); +}); diff --git a/test/standalone-build.test.ts b/test/standalone-build.test.ts index 0d98ad010..803058bad 100644 --- a/test/standalone-build.test.ts +++ b/test/standalone-build.test.ts @@ -184,11 +184,18 @@ describe('install.sh', () => { it('supports VERSION, PREFIX and REPO overrides', () => { // REPO is what lets an air-gapped org point at an internal mirror. - expect(install).toMatch(/REPO="\$\{REPO:-bradygaster\/squad\}"/); + expect(install).toMatch(/REPO="\$\{REPO:-github\/squad\}"/); expect(install).toMatch(/VERSION="\$\{VERSION:-latest\}"/); expect(install).toMatch(/PREFIX="\/usr\/local"/); }); + it('documents and defaults to the canonical github/squad repository', () => { + // Includes the header's curl one-liner, which users copy verbatim. + expect(install).toContain('raw.githubusercontent.com/github/squad/dev/scripts/install.sh'); + const stale = install.split('\n').filter((line) => line.includes('bradygaster/squad')); + expect(stale, 'install.sh lines still naming the pre-move bradygaster/squad repo').toEqual([]); + }); + it('maps uname output to the published asset names', () => { for (const target of ['linux', 'darwin', 'x64', 'arm64']) { expect(install).toContain(target); diff --git a/test/standalone-release-workflow.test.ts b/test/standalone-release-workflow.test.ts index 7f52c008b..ae3a3915a 100644 --- a/test/standalone-release-workflow.test.ts +++ b/test/standalone-release-workflow.test.ts @@ -1077,6 +1077,9 @@ describe('automated package publication', () => { expect(publish).toContain('--base master'); expect(publish).toContain('--head "tamirdresher:${BRANCH}"'); expect(publish).toContain('New version: ${PACKAGE_IDENTIFIER} version ${VERSION}'); + // The PR body links the release in whichever repository ran this workflow, so a repo move can't strand it. + expect(publish).toContain('[Squad ${TAG}](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${TAG})'); + expect(publish).not.toMatch(/github\.com\/[\w.-]+\/squad\/releases/); expect(publish).not.toMatch(/git add (?:\.|-A)|git push .*--force/); });