Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug-report.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Bug Report
description: Report a bug with apiops CLI
labels: ["bug"]
labels: ["type:bug"]
body:
- type: input
id: command
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/question.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Question
description: Ask a usage question or request clarification
labels: ["question"]
labels: ["type:question"]
body:
- type: textarea
id: question
Expand Down
81 changes: 77 additions & 4 deletions .github/workflows/issue-labels-sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ on:
paths:
- '.squad/team.md'
- '.ai-team/team.md'
- '.github/ISSUE_TEMPLATE/**'
- '.github/workflows/issue-labels-sync.yml'
workflow_dispatch:

permissions:
Expand All @@ -19,6 +21,19 @@ jobs:
steps:
- uses: actions/checkout@v6

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
cache: 'npm'

# Install production dependencies so the github-script step can `require('js-yaml')`.
# github-script resolves bare module IDs from the workspace root (process.cwd()).
# --ignore-scripts blocks dependency lifecycle scripts (supply-chain hardening);
# our runtime deps are pure JS and need no install scripts.
- name: Install dependencies
run: npm ci --omit=dev --ignore-scripts

- name: Parse roster and sync labels
uses: actions/github-script@v8
with:
Expand All @@ -29,12 +44,12 @@ jobs:
teamFile = '.ai-team/team.md';
}

if (!fs.existsSync(teamFile)) {
core.info('No .squad/team.md or .ai-team/team.md found — skipping label sync');
return;
const rosterExists = fs.existsSync(teamFile);
if (!rosterExists) {
core.info('No .squad/team.md or .ai-team/team.md found — skipping squad member labels (static labels still sync and issue templates are still validated)');
}

const content = fs.readFileSync(teamFile, 'utf8');
const content = rosterExists ? fs.readFileSync(teamFile, 'utf8') : '';
const lines = content.split('\n');

// Parse the Members table for agent names
Expand Down Expand Up @@ -195,3 +210,61 @@ jobs:
}

core.info(`Label sync complete: ${labels.length} labels synced`);

// Fail if any issue template references a label this workflow does not define.
const yaml = require('js-yaml');

const definedLabelNames = new Set(labels.map(l => l.name.toLowerCase()));
const templateDir = '.github/ISSUE_TEMPLATE';
const violations = [];
let templateCount = 0;

if (fs.existsSync(templateDir)) {
const templateFiles = fs.readdirSync(templateDir).filter(f => {
const lower = f.toLowerCase();
return /\.ya?ml$/.test(lower) && lower !== 'config.yml' && lower !== 'config.yaml';
});
templateCount = templateFiles.length;
for (const file of templateFiles) {
const templateContent = fs.readFileSync(`${templateDir}/${file}`, 'utf8');

let parsed;
try {
parsed = yaml.load(templateContent);
} catch (err) {
violations.push({ file, error: `could not be parsed as YAML: ${err.message}` });
continue;
}

// GitHub issue forms accept `labels` as a YAML sequence or a
// comma-delimited string.
const rawLabels = parsed && parsed.labels;
let templateLabels = [];
if (Array.isArray(rawLabels)) {
templateLabels = rawLabels.map(l => String(l).trim()).filter(Boolean);
} else if (typeof rawLabels === 'string') {
templateLabels = rawLabels.split(',').map(l => l.trim()).filter(Boolean);
}

for (const label of templateLabels) {
if (!definedLabelNames.has(label.toLowerCase())) {
violations.push({ file, label });
}
}
}
}

if (violations.length > 0) {
const details = violations
.map(v => v.error
? ` - ${v.file}: ${v.error}`
: ` - ${v.file}: "${v.label}" is not a defined label`)
.join('\n');
core.setFailed(
`Issue templates reference labels that are not defined by this workflow:\n${details}\n\n` +
`Fix by either adding the label to this workflow's definitions or updating the ` +
`template to use an existing label.`
);
} else {
core.info(`Validated ${templateCount} issue template(s): all referenced labels are defined`);
}
44 changes: 44 additions & 0 deletions .squad/agents/githubexpert/charter.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,57 @@
- **OIDC for Azure, no secrets.** Use `azure/login` action with OIDC — no client secrets to rotate.
- **Reusable workflows for DRY.** If two repos do the same thing, extract to a reusable workflow.
- **Environments for deployment gates.** Production deployments require environment protection rules.
- **Node.js 24 for all workflows (STANDING RULE).** Every GitHub Actions workflow this project authors must pin Node.js 24 — use `actions/setup-node` with `node-version: '24'`, and prefer Node 24 anywhere a workflow needs a Node runtime. See the Node.js Runtime Standard under Project-Specific Patterns.
- **Always `gh aw compile` agentic workflows (STANDING RULE).** After creating or editing ANY `.md` agentic workflow, always run `gh aw compile` to regenerate the `.lock.yml`, then commit BOTH files. An uncompiled `.md` edit does not change what runs. See GitHub Agentic Workflows (gh-aw) under Project-Specific Patterns.
- I use `gh api` for anything not covered by dedicated `gh` commands — raw API access is essential.
- I configure `gh auth login` with the right scopes upfront to avoid permission errors later.

### Project-Specific Patterns

These patterns are specific to the apiops-cli project.

#### Node.js Runtime Standard (Standing Rule)

- **Pin Node.js 24 in every workflow I author.** Any GitHub Actions workflow that needs a Node runtime must use `actions/setup-node` with `node-version: '24'`. Prefer Node 24 anywhere a workflow references a Node version (setup-node, container images, tool matrices).
- **Why it's compatible:** The repo's `package.json` `engines` requires Node `>=22`, so Node 24 satisfies the constraint.
- **Applies to future authoring.** New and regenerated workflows follow this by default; no need to re-open already-updated files unless touching them for other reasons.

```yaml
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
```

#### GitHub Agentic Workflows (gh-aw)

GitHub Agentic Workflows (gh-aw) are authored as natural-language **markdown with YAML frontmatter** at `.github/workflows/<name>.md`, and are **compiled** into runnable GitHub Actions YAML at `.github/workflows/<name>.lock.yml`. The `.lock.yml` is what actually runs in Actions.

- **⚠️ STANDING RULE — always compile.** After creating or editing ANY `.md` agentic workflow, **always run `gh aw compile`** to (re)generate the `.lock.yml`, then commit **BOTH** the `.md` and the `.lock.yml`. An uncompiled `.md` edit has no effect on what runs.
- **`.gitattributes` must contain:** `.github/workflows/*.lock.yml linguist-generated=true merge=ours`

**Setup (one-time):**

```bash
# Install the extension
curl -sL https://raw.githubusercontent.com/github/gh-aw/main/install-gh-aw.sh | bash
gh aw version # verify install
gh extension upgrade aw # upgrade later
```

**Key commands:**

```bash
gh aw new <workflow-name> # scaffold a new workflow
gh aw compile [workflow-name] # compile all workflows, or one by name (regenerates .lock.yml)
gh aw compile --validate # compile with validation
gh aw logs [workflow-name] # inspect run logs
gh aw audit <run-id> # debug a specific run
gh aw fix --write # auto-fix/upgrade deprecated fields
```

**Full docs:** https://github.github.com/gh-aw/ · **Repo:** https://github.com/github/gh-aw

#### Repository Workflow Files
| File | Purpose |
|------|---------|
Expand Down
Loading