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
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @TGPSKI
26 changes: 26 additions & 0 deletions .github/ruleset-fork-only.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "fork-only-workflow",
"target": "branch",
"enforcement": "active",
"conditions": {
"ref_name": {
"include": ["~ALL"],
"exclude": ["refs/heads/main"]
}
},
"rules": [
{
"type": "creation"
},
{
"type": "update"
}
],
"bypass_actors": [
{
"actor_id": 5,
"actor_type": "RepositoryRole",
"bypass_mode": "always"
}
]
}
30 changes: 30 additions & 0 deletions .github/ruleset-main-reviews.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "main-pr-reviews",
"target": "branch",
"enforcement": "active",
"conditions": {
"ref_name": {
"include": ["refs/heads/main"],
"exclude": []
}
},
"rules": [
{
"type": "pull_request",
"parameters": {
"required_approving_review_count": 1,
"dismiss_stale_reviews_on_push": true,
"require_code_owner_review": true,
"require_last_push_approval": false,
"required_review_thread_resolution": true
}
}
],
"bypass_actors": [
{
"actor_id": 5,
"actor_type": "RepositoryRole",
"bypass_mode": "always"
}
]
}
32 changes: 32 additions & 0 deletions .github/ruleset-main.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "main-ci-and-integrity",
"target": "branch",
"enforcement": "active",
"conditions": {
"ref_name": {
"include": ["refs/heads/main"],
"exclude": []
}
},
"rules": [
{
"type": "required_status_checks",
"parameters": {
"strict_required_status_checks_policy": true,
"required_status_checks": [
{ "context": "validate" }
]
}
},
{
"type": "non_fast_forward"
}
],
"bypass_actors": [
{
"actor_id": 5,
"actor_type": "RepositoryRole",
"bypass_mode": "always"
}
]
}
45 changes: 45 additions & 0 deletions .github/workflows/auto-label.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Auto-label trusted PRs

on:
pull_request_target:
types: [opened, synchronize]

permissions:
pull-requests: write

jobs:
label:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
sparse-checkout: |
CODEOWNERS
OWNERS
sparse-checkout-cone-mode: false

- name: Apply ok-to-test if author is trusted
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
TRUSTED_USERS=""

if [ -f CODEOWNERS ]; then
TRUSTED_USERS="$TRUSTED_USERS $(grep -oP '@\K[\w-]+' CODEOWNERS | sort -u)"
fi

if [ -f OWNERS ]; then
TRUSTED_USERS="$TRUSTED_USERS $(grep -oP 'github:\s*\K[\w-]+' OWNERS | sort -u)"
fi

for user in $TRUSTED_USERS; do
if [ "$PR_AUTHOR" = "$user" ]; then
gh pr edit "$PR_NUMBER" --add-label "ok-to-test"
echo "Labeled PR #${PR_NUMBER} with ok-to-test (author: ${PR_AUTHOR})"
exit 0
fi
done

echo "Author ${PR_AUTHOR} is not in CODEOWNERS or OWNERS — skipping"
146 changes: 146 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]
types: [opened, synchronize, reopened, labeled]

permissions:
contents: read

jobs:
validate:
if: >-
github.event_name == 'push' ||
github.event.action == 'opened' ||
contains(github.event.pull_request.labels.*.name, 'ok-to-test')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Verify SKILL.md entry points exist
run: |
FAIL=0
for dir in examples/*/; do
for workflow in "$dir"*/; do
if [ ! -f "${workflow}SKILL.md" ]; then
echo "::error::Missing SKILL.md in ${workflow}"
FAIL=1
fi
done
done
if [ "$FAIL" -eq 1 ]; then exit 1; fi
echo "All example workflows have SKILL.md entry points"

# Agent Skills spec: https://github.com/agentskills/agentskills/blob/1eeb1aab054a20e9b8508887e82bd911a29235c8/docs/specification.mdx
- name: Validate SKILL.md frontmatter (Agent Skills spec)
run: |
FAIL=0
for skill in $(find . -name 'SKILL.md' -not -path './.git/*'); do
# Must start with YAML frontmatter delimiters
FIRST_LINE=$(head -1 "$skill")
if [ "$FIRST_LINE" != "---" ]; then
echo "::error::${skill}: missing YAML frontmatter (must start with ---)"
FAIL=1
continue
fi

# Extract frontmatter between first and second ---
FRONTMATTER=$(awk 'BEGIN{f=0} /^---$/{f++; if(f==2) exit; next} f==1{print}' "$skill")
if [ -z "$FRONTMATTER" ]; then
echo "::error::${skill}: empty or malformed frontmatter"
FAIL=1
continue
fi

# Required: name field
NAME=$(echo "$FRONTMATTER" | grep -E '^name:\s' | sed 's/^name:\s*//')
if [ -z "$NAME" ]; then
echo "::error::${skill}: missing required 'name' field in frontmatter"
FAIL=1
else
# name must be lowercase alphanumeric + hyphens, 1-64 chars
if ! echo "$NAME" | grep -qE '^[a-z0-9]([a-z0-9-]*[a-z0-9])?$'; then
echo "::error::${skill}: 'name' must be lowercase alphanumeric + hyphens, not start/end with hyphen: ${NAME}"
FAIL=1
fi
if [ ${#NAME} -gt 64 ]; then
echo "::error::${skill}: 'name' exceeds 64 character limit: ${NAME}"
FAIL=1
fi
# name must not contain consecutive hyphens
if echo "$NAME" | grep -qE '\-\-'; then
echo "::error::${skill}: 'name' must not contain consecutive hyphens: ${NAME}"
FAIL=1
fi
# name must match parent directory name
PARENT_DIR=$(basename "$(dirname "$skill")")
if [ "$NAME" != "$PARENT_DIR" ] && [ "$PARENT_DIR" != "." ]; then
echo "::warning::${skill}: 'name' (${NAME}) does not match parent directory (${PARENT_DIR})"
fi
fi

# Required: description field
DESC=$(echo "$FRONTMATTER" | grep -E '^description:\s' | sed 's/^description:\s*//')
if [ -z "$DESC" ]; then
echo "::error::${skill}: missing required 'description' field in frontmatter"
FAIL=1
else
if [ ${#DESC} -gt 1024 ]; then
echo "::error::${skill}: 'description' exceeds 1024 character limit"
FAIL=1
fi
fi
done
if [ "$FAIL" -eq 1 ]; then exit 1; fi
echo "All SKILL.md files have valid frontmatter per Agent Skills spec"

- name: Verify references/ directories
run: |
FAIL=0
for skill in examples/*/*/SKILL.md; do
DIR=$(dirname "$skill")
if [ ! -d "${DIR}/references" ]; then
echo "::error::Missing references/ directory for ${skill}"
FAIL=1
fi
done
if [ "$FAIL" -eq 1 ]; then exit 1; fi
echo "All multi-phase workflows have references/ directories"

- name: Verify phase file naming convention
run: |
FAIL=0
for ref_dir in examples/*/*/references/; do
if [ -d "$ref_dir" ]; then
for f in "$ref_dir"*.md; do
[ -f "$f" ] || continue
BASENAME=$(basename "$f")
if ! echo "$BASENAME" | grep -qE '^phase-[0-9]+-[a-z0-9-]+\.md$'; then
echo "::error::Phase file does not match phase-0N-name.md convention: ${f}"
FAIL=1
fi
done
fi
done
if [ "$FAIL" -eq 1 ]; then exit 1; fi
echo "All phase files follow naming convention"

- name: Check for broken internal references
run: |
FAIL=0
for md in $(find . -name '*.md' -not -path './.git/*'); do
while IFS= read -r ref; do
TARGET=$(echo "$ref" | sed 's/.*](//' | sed 's/).*//' | sed 's/#.*//')
[ -z "$TARGET" ] && continue
echo "$TARGET" | grep -qE '^https?://' && continue
echo "$TARGET" | grep -qE '^mailto:' && continue
DIR=$(dirname "$md")
if [ ! -e "${DIR}/${TARGET}" ] && [ ! -e "${TARGET}" ]; then
echo "::warning::Possibly broken link in ${md}: ${TARGET}"
fi
done < <(grep -oE '\[[^]]*\]\([^)]+\)' "$md" 2>/dev/null || true)
done
echo "Link check complete"
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @TGPSKI
76 changes: 76 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Contributing to directed-workflows

Thank you for your interest in contributing to directed-workflows.

## How to Contribute

### Reporting Issues

Open a GitHub issue describing the problem, including:
- What you expected to happen
- What actually happened
- Steps to reproduce

### Proposing Changes

1. Fork the repository
2. Create a feature branch from `main`
3. Make your changes
4. Submit a pull request against `main`

### Agent Skills Specification

All `SKILL.md` files must conform to the [Agent Skills specification](https://github.com/agentskills/agentskills/blob/1eeb1aab054a20e9b8508887e82bd911a29235c8/docs/specification.mdx).

Required frontmatter fields:

| Field | Constraints |
|-------|-------------|
| `name` | 1-64 chars, lowercase alphanumeric + hyphens, must match parent directory name |
| `description` | 1-1024 chars, describes what the skill does and when to use it |

Optional frontmatter fields: `license`, `compatibility`, `metadata`, `allowed-tools`.

### Workflow File Conventions

When adding or modifying workflow examples:

- Entry points are always `SKILL.md` with valid YAML frontmatter per the spec above
- Phase files live under `references/` and use `phase-0N-name.md` naming
- The `parent` field references the router's name, not its filename
- Follow the Inspect-Decide-Generate cycle in every step
- Use Status-Action tables for conditional logic, not prose

### Adding Examples

1. Create a directory under `examples/<domain>/<workflow-name>/`
2. Add a `SKILL.md` router as the entry point
3. Add phase files under `references/`
4. Update the examples table in `README.md`

### Adding Templates

1. Place new templates under `templates/`
2. Single-session workflows: one `.md` file
3. Multi-session workflows: a directory with `router.md` and `phase-template.md`

### Code of Conduct

Be respectful. Constructive feedback is welcome; personal attacks are not.

### Quality Gates

Every PR must:

1. Not break existing workflow file structure
2. Follow the naming conventions documented in `ROUTER_PATTERN.md`
3. Conform to the [Agent Skills specification](https://github.com/agentskills/agentskills/blob/1eeb1aab054a20e9b8508887e82bd911a29235c8/docs/specification.mdx) — all `SKILL.md` files must have valid frontmatter with required `name` and `description` fields
4. Include a clear description of what the change does and why

### Running Validation

There is no build step. Review your markdown renders correctly and all internal `@`-references resolve to existing files.

### License

By contributing, you agree that your contributions will be licensed under the GNU General Public License v3.0.
Loading