diff --git a/.changeset/fix-yaml-escaping-skill.md b/.changeset/fix-yaml-escaping-skill.md new file mode 100644 index 000000000..3c2623fd9 --- /dev/null +++ b/.changeset/fix-yaml-escaping-skill.md @@ -0,0 +1,8 @@ +--- +"@bradygaster/squad-cli": patch +--- + +Fix YAML escaping in skill command apm.yml generation + +Use `JSON.stringify()` for skill descriptions in generated apm.yml files +to properly escape quotes and newlines, preventing invalid YAML output. diff --git a/.github/workflows/squad-impact.yml b/.github/workflows/squad-impact.yml index f7fd7c59a..a17c5abcf 100644 --- a/.github/workflows/squad-impact.yml +++ b/.github/workflows/squad-impact.yml @@ -11,6 +11,7 @@ on: permissions: contents: read pull-requests: write + issues: write concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} diff --git a/.github/workflows/squad-pr-nudge.yml b/.github/workflows/squad-pr-nudge.yml index 12f901e3e..1536d6d4d 100644 --- a/.github/workflows/squad-pr-nudge.yml +++ b/.github/workflows/squad-pr-nudge.yml @@ -8,7 +8,7 @@ permissions: contents: read pull-requests: write checks: read - issues: read + issues: write jobs: nudge-stale-prs: @@ -146,8 +146,8 @@ jobs: base: pr.base.ref, head: pr.head.sha }); - if (comparison.data.ahead_by > 10) { - actions.push(`⬇️ **${comparison.data.ahead_by} commits behind ${pr.base.ref}.** Rebase to pick up latest changes.`); + if (comparison.data.behind_by > 10) { + actions.push(`⬇️ **${comparison.data.behind_by} commits behind ${pr.base.ref}.** Rebase to pick up latest changes.`); } // 6. If everything looks good and approved — it's ready to merge diff --git a/.github/workflows/squad-pr-readiness.yml b/.github/workflows/squad-pr-readiness.yml index 7809ceda5..5218b5261 100644 --- a/.github/workflows/squad-pr-readiness.yml +++ b/.github/workflows/squad-pr-readiness.yml @@ -45,7 +45,7 @@ jobs: PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.workflow_run.head_sha }} PR_BASE_REF: ${{ github.event.pull_request.base.ref || github.event.workflow_run.pull_requests[0].base.ref }} PR_DRAFT: ${{ github.event.pull_request.draft || 'false' }} - PR_LABELS: ${{ toJson(github.event.pull_request.labels || '[]') }} + PR_LABELS: ${{ toJson(github.event.pull_request.labels) }} REPO_OWNER: ${{ github.repository_owner }} REPO_NAME: ${{ github.event.repository.name }} RUN_NAME: ${{ github.workflow }} diff --git a/.github/workflows/squad-repo-health.yml b/.github/workflows/squad-repo-health.yml index 4fd722927..4b57a50ac 100644 --- a/.github/workflows/squad-repo-health.yml +++ b/.github/workflows/squad-repo-health.yml @@ -11,6 +11,7 @@ on: permissions: contents: read pull-requests: write + issues: write concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} diff --git a/packages/squad-cli/src/cli/commands/skill.ts b/packages/squad-cli/src/cli/commands/skill.ts index 1acea5082..461bac059 100644 --- a/packages/squad-cli/src/cli/commands/skill.ts +++ b/packages/squad-cli/src/cli/commands/skill.ts @@ -144,15 +144,16 @@ async function publish(dest: string, skillName?: string): Promise { // Build the skill's own apm.yml inside its directory const apmSkillPath = join(skillsDir, skillName, 'apm.yml'); + const safeDesc = fm['description'] ? JSON.stringify(fm['description']) : null; const skillApm = [ `name: ${fm['name'] ?? skillName}`, `version: ${fm['version'] ?? '1.0.0'}`, - fm['description'] ? `description: "${fm['description']}"` : null, + safeDesc ? `description: ${safeDesc}` : null, ``, `skills:`, ` - name: ${fm['name'] ?? skillName}`, ` path: skill.md`, - fm['description'] ? ` description: "${fm['description']}"` : null, + safeDesc ? ` description: ${safeDesc}` : null, ] .filter(l => l !== null) .join('\n'); @@ -195,7 +196,7 @@ async function publish(dest: string, skillName?: string): Promise { ...skills.map(s => [ ` - name: ${s.name}`, - s.description ? ` description: "${s.description}"` : null, + s.description ? ` description: ${JSON.stringify(s.description)}` : null, ` path: ${s.path}`, s.version ? ` version: ${s.version}` : null, ] diff --git a/scripts/check-bootstrap-deps.mjs b/scripts/check-bootstrap-deps.mjs index cc89b1da8..7795dd88f 100644 --- a/scripts/check-bootstrap-deps.mjs +++ b/scripts/check-bootstrap-deps.mjs @@ -44,7 +44,10 @@ const NODE_BUILTINS = new Set([ * Accepts both `node:fs` and `fs` forms, as well as subpaths like `node:fs/promises`. */ function isNodeBuiltin(specifier) { - if (specifier.startsWith('node:')) return true; + if (specifier.startsWith('node:')) { + const base = specifier.slice(5).split('/')[0]; + return NODE_BUILTINS.has(base); + } const base = specifier.split('/')[0]; return NODE_BUILTINS.has(base); } diff --git a/scripts/pr-readiness.mjs b/scripts/pr-readiness.mjs index 7b49cbdd5..be3ea4b1c 100644 --- a/scripts/pr-readiness.mjs +++ b/scripts/pr-readiness.mjs @@ -243,6 +243,16 @@ export function checkCIStatus(checkRuns, statuses) { if (otherChecks.length === 0 && (statuses || []).length === 0) { return { pass: false, detail: 'No CI checks have run yet' }; } + // Check legacy combined statuses for failure/error states + const failedStatuses = (statuses || []).filter( + (s) => s.state === 'failure' || s.state === 'error', + ); + if (failedStatuses.length > 0) { + return { + pass: false, + detail: `${failedStatuses.length} status(es) failing: ${failedStatuses.map((s) => s.context).join(', ')}`, + }; + } return { pass: true, detail: 'All checks passing' }; } @@ -312,7 +322,7 @@ export function buildFileList(files) { if (truncated) { const remaining = files.length - MAX_FILE_LIST; - rows.push(`| ... | **+${remaining} more files** | |`); + rows.push(`| ... | **+${remaining} more files** |`); } return [ @@ -442,7 +452,8 @@ export async function run({ env = process.env, fetchFn = globalThis.fetch } = {} let prLabels = []; try { - prLabels = JSON.parse(prLabelsRaw); + const parsed = JSON.parse(prLabelsRaw); + prLabels = Array.isArray(parsed) ? parsed : []; } catch { prLabels = []; }