Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9598298
fix: Prevent task decomposer from splitting compound words on unspace…
stranske Jan 12, 2026
2f4ab62
chore(autofix): formatting/lint
github-actions[bot] Jan 12, 2026
00e5b75
fix: Strip code blocks before parsing tasks in issue_scope_parser
stranske Jan 12, 2026
a62c1cc
fix: preserve code blocks in PR status summary without adding checkboxes
stranske Jan 12, 2026
dfd66e8
fix: apply same slash fix to _is_multi_action_task in capability_chec…
stranske Jan 12, 2026
96ef587
feat: add template sync validation to prevent consumer repo sync fail…
stranske Jan 12, 2026
79a9c94
chore(autofix): formatting/lint
github-actions[bot] Jan 12, 2026
53406d9
docs: add template sync guidance to SYNC_WORKFLOW and SETUP_CHECKLIST
stranske Jan 12, 2026
f05d1b6
fix: address Codex review - fail on missing templates, sync creates n…
stranske Jan 12, 2026
8a5df02
fix: add health-72-template-sync to workflow inventory and tests
stranske Jan 12, 2026
fbfe3c5
Merge branch 'main' into fix/template-sync-guard
stranske Jan 12, 2026
a76f6f1
test: Add comprehensive coverage for validate_template_sync.py
stranske Jan 12, 2026
16885b7
chore(autofix): formatting/lint
github-actions[bot] Jan 12, 2026
364f1f0
sync: Create 13 missing template files (FIX: actually commit them thi…
stranske Jan 12, 2026
761f41d
fix: Actually rename workflow file to health-72-template-sync.yml
stranske Jan 12, 2026
04f11b4
fix: Update workflow display name to match filename
stranske Jan 12, 2026
1a865ca
fix: Make test_validate_template_sync tests work properly
stranske Jan 12, 2026
de7d273
chore(autofix): formatting/lint
github-actions[bot] Jan 12, 2026
7725d8b
fix: Update test expectation for missing template directory
stranske Jan 12, 2026
77b9331
chore: retrigger CI checks
stranske Jan 12, 2026
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
37 changes: 37 additions & 0 deletions .github/workflows/health-72-template-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Health 72 Template Sync

on:
pull_request:
paths:
- '.github/scripts/**/*.js'
- 'templates/consumer-repo/.github/scripts/**/*.js'
push:
branches: [main]
paths:
- '.github/scripts/**/*.js'
- 'templates/consumer-repo/.github/scripts/**/*.js'

jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Validate template sync
run: |
if ! python scripts/validate_template_sync.py; then
echo ""
echo "❌ FAILED: Template files are out of sync!"
echo ""
echo "To fix this error:"
echo " 1. Run: ./scripts/sync_templates.sh"
echo " 2. Commit the changes to templates/consumer-repo/"
echo " 3. Push to your branch"
echo ""
exit 1
fi
19 changes: 19 additions & 0 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,27 @@ Before submitting changes, run the validation scripts:

# Comprehensive check (30-120 seconds)
./scripts/check_branch.sh

# Template sync validation (if you modified .github/scripts/)
python scripts/validate_template_sync.py
```

### ⚠️ CRITICAL: Template Sync Guard

**If you modify any file in `.github/scripts/`, you MUST also update the template:**

```bash
# After editing .github/scripts/*.js, run:
./scripts/sync_templates.sh

# Verify sync:
python scripts/validate_template_sync.py
```

**Why?** Consumer repos get workflow updates via `templates/consumer-repo/`. If you only update `.github/scripts/` but not the template, your changes won't sync to consumer repos and no sync PRs will be created.

The CI will fail if templates are out of sync with source files.

## Code Style

### Python
Expand Down
23 changes: 23 additions & 0 deletions docs/SYNC_WORKFLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,29 @@ Prevent propagating bugs to consumer repos by validating changes in the source (

## Before Any Sync

### 0. Verify Template Sync (CRITICAL)

**If you modified `.github/scripts/`, ensure templates are synced:**

```bash
# Check for out-of-sync templates
python scripts/validate_template_sync.py

# If validation fails:
./scripts/sync_templates.sh

# Verify fixed:
python scripts/validate_template_sync.py

# Commit template changes:
git add templates/consumer-repo/.github/scripts/
git commit -m "sync: update templates with latest script changes"
```

**Why?** Consumer repos sync from `templates/consumer-repo/`. If source scripts are changed but templates aren't updated, no sync PRs will be created.

The CI workflow `health-72-template-sync.yml` enforces this, but check manually before triggering sync.

### 1. Validate in Workflows Repo
```bash
# In /workspaces/Workflows
Expand Down
1 change: 1 addition & 0 deletions docs/ci/WORKFLOWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ Scheduled health jobs keep the automation ecosystem aligned:
* [`health-67-integration-sync-check.yml`](../../.github/workflows/health-67-integration-sync-check.yml) validates that Workflows-Integration-Tests repo stays in sync with templates (push, `repository_dispatch`, daily schedule).
* [`health-70-validate-sync-manifest.yml`](../../.github/workflows/health-70-validate-sync-manifest.yml) validates that sync-manifest.yml is complete - ensures all sync-able files are declared (PR, push).
* [`health-71-sync-health-check.yml`](../../.github/workflows/health-71-sync-health-check.yml) monitors sync workflow health daily - creates issues if all recent runs failed or sync is stale (daily schedule, manual dispatch).
* [`health-72-template-sync.yml`](../../.github/workflows/health-72-template-sync.yml) validates that template files are in sync with source scripts - fails if `.github/scripts/` changes but `templates/consumer-repo/` isn't updated (PR, push on script changes).
* [`maint-68-sync-consumer-repos.yml`](../../.github/workflows/maint-68-sync-consumer-repos.yml) pushes workflow template updates to registered consumer repos (release, template push, manual dispatch).
* [`maint-69-sync-integration-repo.yml`](../../.github/workflows/maint-69-sync-integration-repo.yml) syncs integration-repo templates to Workflows-Integration-Tests repository (template push, manual dispatch with dry-run support).
* [`maint-70-fix-integration-formatting.yml`](../../.github/workflows/maint-70-fix-integration-formatting.yml) applies Black and Ruff formatting fixes to Integration-Tests repository files (manual dispatch for CI formatting failures).
Expand Down
1 change: 1 addition & 0 deletions docs/ci/WORKFLOW_SYSTEM.md
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,7 @@ Keep this table handy when you are triaging automation: it confirms which workfl
| **Health 67 Integration Sync Check** (`health-67-integration-sync-check.yml`, maintenance bucket) | `push` (templates), `repository_dispatch`, `schedule` (daily) | Validate that Workflows-Integration-Tests repo stays in sync with templates. Creates issues when drift detected. | ⚪ Automatic/scheduled | [Integration sync runs](https://github.com/stranske/Workflows/actions/workflows/health-67-integration-sync-check.yml) |
| **Health 70 Validate Sync Manifest** (`health-70-validate-sync-manifest.yml`, maintenance bucket) | `pull_request`, `push` | Validate that sync-manifest.yml includes all sync-able files. Fails PRs that add workflows/prompts/scripts without updating manifest. | ⚪ Required on PRs | [Manifest validation runs](https://github.com/stranske/Workflows/actions/workflows/health-70-validate-sync-manifest.yml) |
| **Health 71 Sync Health Check** (`health-71-sync-health-check.yml`, maintenance bucket) | `schedule` (daily), `workflow_dispatch` | Monitor sync workflow health and create issues when all recent runs failed or sync is stale. | ⚪ Scheduled/manual | [Sync health check runs](https://github.com/stranske/Workflows/actions/workflows/health-71-sync-health-check.yml) |
| **Health 72 Template Sync** (`health-72-template-sync.yml`, maintenance bucket) | `pull_request`, `push` (`.github/scripts/`, `templates/`) | Validate that template files are in sync with source scripts. Fails if `.github/scripts/*.js` changes but `templates/consumer-repo/` isn't updated. | ⚪ Required on PRs | [Template sync validation runs](https://github.com/stranske/Workflows/actions/workflows/health-72-template-sync.yml) |
| **Maint 68 Sync Consumer Repos** (`maint-68-sync-consumer-repos.yml`, maintenance bucket) | `release`, `push` (templates), `workflow_dispatch` | Push workflow template updates to registered consumer repositories. Creates PRs in consumer repos when templates change. | ⚪ Automatic/manual | [Consumer sync runs](https://github.com/stranske/Workflows/actions/workflows/maint-68-sync-consumer-repos.yml) |
| **Maint 69 Sync Integration Repo** (`maint-69-sync-integration-repo.yml`, maintenance bucket) | `push` (templates), `workflow_dispatch` | Sync integration-repo templates to Workflows-Integration-Tests repository. Resolves drift detected by Health 67. Supports dry-run mode. | ⚪ Automatic/manual | [Integration sync runs](https://github.com/stranske/Workflows/actions/workflows/maint-69-sync-integration-repo.yml) |
| **Fix Integration Tests Formatting** (`maint-70-fix-integration-formatting.yml`, maintenance bucket) | `workflow_dispatch` | Manually triggered workflow to apply Black and Ruff formatting fixes to Python files in the Workflows-Integration-Tests repository when CI formatting checks fail. | ⚪ Manual only | [Formatting fix runs](https://github.com/stranske/Workflows/actions/workflows/maint-70-fix-integration-formatting.yml) |
Expand Down
37 changes: 37 additions & 0 deletions docs/keepalive/SETUP_CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,43 @@ Copy from:

---


---

## 📌 Important: Template Sync Process

Before configuring workflows, understand how this consumer repo receives updates:

### How Workflow Updates Work

1. **Source**: Workflow scripts live in `stranske/Workflows/templates/consumer-repo/`
2. **Sync**: The Workflows repo has a sync process that creates PRs to update consumer repos
3. **Trigger**: Sync happens when template files change or on manual trigger

### Template Sync Validation (For Workflows Repo Contributors)

If you're contributing to the Workflows repo and modifying `.github/scripts/`:

```bash
# After editing workflow scripts
./scripts/sync_templates.sh

# Verify templates are in sync
python scripts/validate_template_sync.py
```

**Why this matters**: Consumer repos only get updates when template files change. If you modify `.github/scripts/` but forget to update `templates/consumer-repo/.github/scripts/`, no sync PRs are created.

The CI enforces this with `.github/workflows/health-72-template-sync.yml`.

### As a Consumer Repo User

- Watch for sync PRs from the Workflows repo
- Review and merge them to get the latest workflow improvements
- Don't manually edit workflow files in `.github/workflows/` or `.github/scripts/` - changes will be overwritten on next sync

---

## Workflow Configuration

### Step 12: Configure Workflow Files
Expand Down
39 changes: 39 additions & 0 deletions scripts/sync_templates.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash
# Sync source scripts to template directory
set -e

SOURCE_DIR=".github/scripts"
TEMPLATE_DIR="templates/consumer-repo/.github/scripts"

echo "🔄 Syncing scripts to template directory..."

# Get list of files to sync (exclude tests)
FILES=$(find "$SOURCE_DIR" -name "*.js" -type f \
| grep -v "__tests__" \
| grep -v ".test.js" \
| sed "s|^$SOURCE_DIR/||")

synced=0
for file in $FILES; do
source_file="$SOURCE_DIR/$file"
template_file="$TEMPLATE_DIR/$file"

# Create parent directory if it doesn't exist
mkdir -p "$(dirname "$template_file")"

if [ ! -f "$template_file" ]; then
echo " ✓ Creating $file (new file)"
cp "$source_file" "$template_file"
synced=$((synced + 1)) || true
elif ! cmp -s "$source_file" "$template_file"; then
echo " ✓ Syncing $file"
cp "$source_file" "$template_file"
synced=$((synced + 1)) || true
fi
done

if [ $synced -eq 0 ]; then
echo "✅ All files already in sync"
else
echo "✅ Synced $synced file(s)"
fi
73 changes: 73 additions & 0 deletions scripts/validate_template_sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""
Validate that template files are in sync with source files.

This prevents the common mistake of updating .github/scripts/ without
updating templates/consumer-repo/.github/scripts/, which causes sync
PRs to consumer repos to not be triggered.
"""
import hashlib
import sys
from pathlib import Path


def hash_file(path: Path) -> str:
"""Compute SHA256 hash of a file."""
return hashlib.sha256(path.read_bytes()).hexdigest()


def main() -> int:
repo_root = Path(__file__).parent.parent
source_dir = repo_root / ".github" / "scripts"
template_dir = repo_root / "templates" / "consumer-repo" / ".github" / "scripts"

if not source_dir.exists():
print(f"❌ Source directory not found: {source_dir}")
return 1

if not template_dir.exists():
print(f"❌ Template directory not found: {template_dir}")
return 1

# Files that should be synced (exclude test files and some specific scripts)
exclude_patterns = ["__tests__", ".test.js", "deploy", "release"]

mismatches = []
source_files = [
f
for f in source_dir.rglob("*.js")
if not any(pattern in str(f) for pattern in exclude_patterns)
]

for source_file in source_files:
relative_path = source_file.relative_to(source_dir)
template_file = template_dir / relative_path

if not template_file.exists():
mismatches.append(relative_path)
continue

source_hash = hash_file(source_file)
template_hash = hash_file(template_file)

if source_hash != template_hash:
mismatches.append(relative_path)

if mismatches:
print("❌ Template files out of sync with source files:\n")
for path in mismatches:
template_file = template_dir / path
if not template_file.exists():
print(f" • {path} (MISSING - needs to be created)")
else:
print(f" • {path} (out of sync)")
print("\n💡 To fix: ./scripts/sync_templates.sh")
print(" Then: git add templates/consumer-repo/.github/scripts/")
return 1

print("✅ All template files in sync")
return 0


if __name__ == "__main__":
sys.exit(main())
9 changes: 8 additions & 1 deletion templates/consumer-repo/.github/scripts/agents-guard.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ const ALLOW_REMOVED_PATHS = new Set(
'.github/workflows/agents-pr-meta.yml',
'.github/workflows/agents-pr-meta-v2.yml',
'.github/workflows/agents-pr-meta-v3.yml',
// v1 verify-to-issue workflow deprecated; v2 is the active version.
// Archived to archives/deprecated-workflows/
'.github/workflows/agents-verify-to-issue.yml',
].map((entry) => entry.toLowerCase()),
);

Expand Down Expand Up @@ -447,7 +450,11 @@ function evaluateGuard({
const hasCodeownerApproval = hasExternalApproval || authorIsCodeowner;

const hasProtectedChanges = modifiedProtectedPaths.size > 0;
const needsApproval = hasProtectedChanges && !hasCodeownerApproval;
// Security note: Allow `agents:allow-change` label to bypass CODEOWNER approval
// ONLY for automated dependency PRs from known bots (dependabot, renovate).
// Human PRs or other bot PRs still require CODEOWNER approval even with label.
const isAutomatedPR = normalizedAuthor && (normalizedAuthor === 'dependabot[bot]' || normalizedAuthor === 'renovate[bot]');
const needsApproval = hasProtectedChanges && !hasCodeownerApproval && !(hasAllowLabel && isAutomatedPR);
const needsLabel = hasProtectedChanges && !hasAllowLabel && !hasCodeownerApproval;

const failureReasons = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,7 @@ async function detectKeepalive({ core, github, context, env = process.env }) {
if (!owner || !repo) {
outputs.reason = 'missing-repo';
core.info('Keepalive dispatch skipped: unable to resolve repository owner/name for PR lookup.');
if (typeof finalise === 'function') {
return finalise(false);
}
// Early exit before finalise is defined - just return false
return false;
}
const body = comment?.body || '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ async function fetchConnectorCheckboxStates(github, owner, repo, prNumber, core)
// Later comments override earlier ones (most recent state wins)
for (const comment of connectorComments) {
const commentStates = parseCheckboxStates(comment.body);
for (const [key, value] of commentStates) {
for (const [key] of commentStates) {
states.set(key, true);
}
}
Expand Down Expand Up @@ -866,18 +866,18 @@ async function run({github, context, core, inputs}) {
return;
}

const prInfo = await discoverPr({github, context, core, inputs});
if (!prInfo) {
core.info('No pull request context detected; skipping update.');
return;
}
const prInfo = await discoverPr({github, context, core, inputs});
if (!prInfo) {
core.info('No pull request context detected; skipping update.');
return;
}

const prResponse = await withRetries(
() => github.rest.pulls.get({owner, repo, pull_number: prInfo.number}),
{description: `pulls.get #${prInfo.number}`, core},
);
const pr = prResponse.data;

const prResponse = await withRetries(
() => github.rest.pulls.get({owner, repo, pull_number: prInfo.number}),
{description: `pulls.get #${prInfo.number}`, core},
);
const pr = prResponse.data;

if (pr.state === 'closed') {
core.info(`Pull request #${pr.number} is closed; skipping update.`);
return;
Expand Down
Loading
Loading