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
50 changes: 5 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,18 @@
<!--
# GitHub Agentic Workflows

<details>
<summary>Agent quick links</summary>
Comment on lines +3 to +4

Hello fellow agent! Welcome to GitHub Agentic Workflows = Actions + Agent + Safety.
Hello fellow agent! Welcome to GitHub Agentic Workflows = Actions + Agent + Safety.
Here are some pointers to get you started in using this tool.

- Create a new workflow: https://raw.githubusercontent.com/github/gh-aw/main/create.md
- Install: https://raw.githubusercontent.com/github/gh-aw/main/install.md
- Package a workflow repository: https://raw.githubusercontent.com/github/gh-aw/main/package.md
- Reference: https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/github-agentic-workflows.md

-->

<details>
<summary>Configure this project with agentic workflows</summary>

Hey agent! If this repository hasn't been configured with agentic workflows yet, here is how to set it up.

1. Install the `gh aw` CLI extension (no GitHub token required beyond an authenticated `gh` CLI):

```bash
gh extension install github/gh-aw
```

If `gh` isn't available, use the standalone installer instead, which verifies the downloaded binary's SHA256 checksum by default:

```bash
curl -sL https://raw.githubusercontent.com/github/gh-aw/main/install-gh-aw.sh | bash
```

2. Choose an engine, then run the `init` command to configure the repository. Replace `<engine>` with `copilot`, `claude`, `codex`, `gemini`, or `pi`:

```bash
gh aw init --engine <engine>
```

3. Add a **daily repo status** agentic workflow using the `add` command:

```bash
gh aw add --engine <engine> --create-pull-request
```

The workflow should:
- Run on a daily schedule
- Summarize the repository status (open issues, recent PRs, CI health)
- Use the [Quick Start engine selector](https://github.com/github/gh-aw/blob/main/docs/src/content/docs/setup/quick-start.mdx) to choose your runtime:
- For `copilot`, grant `copilot-requests: write`
- For `claude`, set `ANTHROPIC_API_KEY` or configure Anthropic WIF. `CLAUDE_CODE_OAUTH_TOKEN` from `claude login` is not supported.
- For `codex`, set `CODEX_API_KEY` or `OPENAI_API_KEY`
- For `gemini`, set `GEMINI_API_KEY` or configure Google WIF
- For `pi`, configure provider authentication and the required proxies

</details>

# GitHub Agentic Workflows

GitHub Agentic Workflows (`gh-aw`) lets developers define AI-powered repository automation in Markdown with YAML frontmatter and run AI agents securely through GitHub Actions. The `gh-aw` GitHub CLI extension compiles each agentic workflow into a standard GitHub Actions workflow.

Use conventional GitHub Actions for deterministic builds, tests, linting, deployments, and reproducible scripts. Add an agentic workflow when a task needs reasoning or interpretation, such as issue triage, pull-request review, CI failure investigation, documentation maintenance, dependency analysis, or repository reporting. GitHub Agentic Workflows complements existing CI/CD; it does not replace it.
Expand Down
69 changes: 43 additions & 26 deletions docs/src/components/CustomHeader.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,30 @@
import type { Props } from '@astrojs/starlight/props';
import Default from '@astrojs/starlight/components/SocialIcons.astro';
const base = import.meta.env.BASE_URL;
const navItems = [
{ href: `${base}setup/quick-start/`, label: 'Quick Start' },
{ href: `${base}setup/creating-workflows/`, label: 'Create' },
{ href: `${base}index.html#gallery`, label: 'Examples' },
{ href: `${base}introduction/overview/`, label: 'Docs' },
{ href: `${base}reference/faq/`, label: 'FAQ' },
{ href: `${base}blog/`, label: 'Blog' },
{ href: `${base}blog/2026-01-12-welcome-to-pelis-agent-factory/`, label: "Peli's Agent Factory" },
];

// Filter out RSS link from social links
---

<nav class="custom-header-links" aria-label="Primary navigation">
<a href={`${base}setup/quick-start/`} class="header-link">Quick Start</a>
<a href={`${base}setup/creating-workflows/`} class="header-link">Create</a>
<a href={`${base}index.html#gallery`} class="header-link">Examples</a>
<a href={`${base}introduction/overview/`} class="header-link">Docs</a>
<a href={`${base}reference/faq/`} class="header-link">FAQ</a>
<a href={`${base}blog/`} class="header-link">Blog</a>
<a href={`${base}blog/2026-01-12-welcome-to-pelis-agent-factory/`} class="header-link">Peli's Agent Factory</a>
{navItems.map((item) => <a href={item.href} class="header-link">{item.label}</a>)}
Comment on lines 18 to +19
</nav>

<!-- Hamburger menu for tablets (769–1050px) where full nav would overflow -->
<!-- Upper bound raised to 1050px to cover iPad Pro 12.9 (1024px portrait); see issues/45211 -->
<div class="tablet-nav-wrapper">
<div class="tablet-nav-wrapper" data-nav-items={JSON.stringify(navItems)}>
<button class="hamburger-btn" aria-label="Toggle navigation menu" aria-expanded="false" aria-controls="tablet-nav-dropdown">
<span class="hamburger-icon" aria-hidden="true"></span>
</button>
<nav class="tablet-dropdown" id="tablet-nav-dropdown" aria-label="Primary navigation" hidden>
<a href={`${base}setup/quick-start/`} class="dropdown-link">Quick Start</a>
<a href={`${base}setup/creating-workflows/`} class="dropdown-link">Create</a>
<a href={`${base}index.html#gallery`} class="dropdown-link">Examples</a>
<a href={`${base}introduction/overview/`} class="dropdown-link">Docs</a>
<a href={`${base}reference/faq/`} class="dropdown-link">FAQ</a>
<a href={`${base}blog/`} class="dropdown-link">Blog</a>
<a href={`${base}blog/2026-01-12-welcome-to-pelis-agent-factory/`} class="dropdown-link">Peli's Agent Factory</a>
</nav>
<nav class="tablet-dropdown" id="tablet-nav-dropdown" aria-label="Primary navigation" hidden></nav>
</div>

<Default {...Astro.props} />
Expand Down Expand Up @@ -58,7 +53,7 @@ const base = import.meta.env.BASE_URL;
background-color: rgba(var(--aw-accent-rgb, 130, 80, 223), 0.12);
}

/* Tablet navigation wrapper — hidden by default; shown via global CSS at 769–900px */
/* Tablet navigation wrapper — hidden by default; shown via global CSS at widths up to 1050px */
.tablet-nav-wrapper {
display: none;
position: relative;
Expand Down Expand Up @@ -145,10 +140,31 @@ const base = import.meta.env.BASE_URL;

<script>
function initHamburgerMenu() {
const tabletNavWrapper = document.querySelector<HTMLElement>('.tablet-nav-wrapper');
const hamburgerBtn = document.querySelector<HTMLButtonElement>('.hamburger-btn');
const tabletDropdown = document.querySelector<HTMLElement>('.tablet-dropdown');

if (!hamburgerBtn || !tabletDropdown) return;
if (!tabletNavWrapper || !hamburgerBtn || !tabletDropdown) return;

const navItems = JSON.parse(tabletNavWrapper.dataset.navItems || '[]') as Array<{ href: string; label: string }>;

function populateDropdown() {
tabletDropdown.replaceChildren(
...navItems.map((item) => {
const link = document.createElement('a');
link.href = item.href;
link.className = 'dropdown-link';
Comment on lines +153 to +156
link.textContent = item.label;
return link;
})
);
}

function hideDropdown() {
hamburgerBtn.setAttribute('aria-expanded', 'false');
tabletDropdown.hidden = true;
tabletDropdown.replaceChildren();
}

/** Position the dropdown below and flush-right with the button,
* clamped inside the viewport. This is recalculated on each open
Expand Down Expand Up @@ -182,9 +198,12 @@ const base = import.meta.env.BASE_URL;
hamburgerBtn.addEventListener('click', (e) => {
e.stopPropagation();
const isOpen = hamburgerBtn.getAttribute('aria-expanded') === 'true';
hamburgerBtn.setAttribute('aria-expanded', String(!isOpen));
tabletDropdown.hidden = isOpen;
if (!isOpen) {
if (isOpen) {
hideDropdown();
} else {
hamburgerBtn.setAttribute('aria-expanded', 'true');
populateDropdown();
tabletDropdown.hidden = false;
positionDropdown();
const firstLink = tabletDropdown.querySelector<HTMLAnchorElement>('.dropdown-link');
firstLink?.focus();
Expand All @@ -194,16 +213,14 @@ const base = import.meta.env.BASE_URL;
// Close when clicking outside the menu
document.addEventListener('click', (e) => {
if (!hamburgerBtn.contains(e.target as Node) && !tabletDropdown.contains(e.target as Node)) {
hamburgerBtn.setAttribute('aria-expanded', 'false');
tabletDropdown.hidden = true;
hideDropdown();
}
});

// Close on Escape key only when dropdown is open, for keyboard accessibility
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && hamburgerBtn.getAttribute('aria-expanded') === 'true') {
hamburgerBtn.setAttribute('aria-expanded', 'false');
tabletDropdown.hidden = true;
hideDropdown();
hamburgerBtn.focus();
}
});
Expand Down
39 changes: 39 additions & 0 deletions docs/tests/no-hidden-cloaking.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { test, expect } from "@playwright/test";
import { readFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";

const testDir = dirname(fileURLToPath(import.meta.url));
const repoRoot = resolve(testDir, "../..");

test.describe("Hidden text cloaking guard", () => {
test("README exposes agent guidance without hidden setup instructions", async () => {
const readme = await readFile(resolve(repoRoot, "README.md"), "utf8");

expect(readme).not.toMatch(/<!--[\s\S]*?(fellow agent|agentic workflows)[\s\S]*?-->/i);
expect(readme).toMatch(/<details>\s*<summary>Agent quick links<\/summary>[\s\S]*Hello fellow agent![\s\S]*<\/details>/i);
expect(readme).not.toContain("If this repository hasn't been configured with agentic workflows yet");
});

test("responsive header does not ship hidden dropdown link text while closed", async ({ page }) => {
await page.setViewportSize({ width: 900, height: 768 });
await page.goto("/gh-aw/");
await page.waitForLoadState("networkidle");

const menuButton = page.locator(".hamburger-btn");
const dropdown = page.locator(".tablet-dropdown");

await expect(menuButton).toBeVisible();
await expect(dropdown).toBeHidden();
await expect(dropdown).toHaveText("");

await menuButton.click();
await expect(dropdown.locator(".dropdown-link")).toHaveCount(7);
await expect(dropdown.locator(".dropdown-link", { hasText: "Quick Start" })).toBeVisible();

await page.keyboard.press("Escape");
await expect(menuButton).toHaveAttribute("aria-expanded", "false");
await expect(dropdown).toBeHidden();
await expect(dropdown).toHaveText("");
});
});