Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4f1a718
feat: add workflow config and issue templates (#23)
jdelfino Feb 12, 2026
eea2f16
feat: add guardrail workflow checks (#18, #19, #20, #21, #22)
jdelfino Feb 12, 2026
b06ce0d
feat: add human review to issues Action (#30)
jdelfino Feb 12, 2026
bed9195
feat: add PR review workflow (#16)
jdelfino Feb 12, 2026
7daa7fc
feat: add orchestrator status check (#17)
jdelfino Feb 12, 2026
b6ecc77
test: add workflow validation tests
jdelfino Feb 12, 2026
9b2ee11
fix: scope guardrail checks child issues for allowed files
jdelfino Feb 12, 2026
d92927c
fix: replace js-yaml with manual parsing in guardrail workflows
jdelfino Feb 12, 2026
2d571a7
refactor: use claude-code-action for PR reviewers
jdelfino Feb 12, 2026
901c3f9
chore: switch from ANTHROPIC_API_KEY to CLAUDE_CODE_OAUTH_TOKEN
jdelfino Feb 12, 2026
ac16176
fix: add id-token:write permission for OAuth OIDC exchange
jdelfino Feb 12, 2026
27d8b18
feat: extract workflow scripts and shared libraries (#37, #38)
jdelfino Feb 13, 2026
f817352
refactor: complete workflow script extraction (#38)
jdelfino Feb 13, 2026
bdf16f0
test: add comprehensive test suite for workflow scripts (#39)
jdelfino Feb 13, 2026
e55ff1c
ci: add CI workflow to run test suite
jdelfino Feb 13, 2026
05f02de
fix: correct bugs in file-patterns and fixes-parser libs
jdelfino Feb 13, 2026
3dd6bf5
fix: add checkout step to pr-review resolve-context job
jdelfino Feb 13, 2026
34b7a3d
feat: add glob pattern support to scope matcher
jdelfino Feb 13, 2026
cf2a64c
fix: add checkout steps to api-surface and human-review workflows
jdelfino Feb 13, 2026
aa4fa22
refactor: move guardrail config from config.yaml into workflow env vars
jdelfino Feb 13, 2026
f6cb15a
refactor: replace custom validation with commitlint CLI
jdelfino Feb 13, 2026
32a6e2f
refactor: remove keyword bypass from dependency guardrail
jdelfino Feb 13, 2026
54b1bd1
refactor: remove human-review workflow
jdelfino Feb 13, 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
5 changes: 4 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{

Check warning on line 1 in .devcontainer/devcontainer.json

View workflow job for this annotation

GitHub Actions / guardrail/scope

.devcontainer/devcontainer.json#L1

This file is not listed in the task scope for issue #3 or its children. If this change is intentional, approve the PR to override.
"name": "agent-workflow",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"remoteUser": "vscode",
Expand All @@ -6,7 +6,10 @@
"source=${localEnv:HOME}/.claude,target=/home/vscode/.claude,type=bind,consistency=cached"
],
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {}
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/devcontainers/features/node:1": {
"version": "lts"
}
},
"postCreateCommand": "curl -fsSL https://claude.ai/install.sh | bash"
}
Empty file removed .github/ISSUE_TEMPLATE/.gitkeep
Empty file.
46 changes: 46 additions & 0 deletions .github/ISSUE_TEMPLATE/review-finding.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Review Finding
description: An issue found during automated or manual code review.
title: ""
labels: ["review-finding"]
body:
- type: textarea
id: finding-description
attributes:
label: Finding description
description: Describe the problem found during review.
placeholder: Explain what is wrong and why it matters.
validations:
required: true

- type: dropdown
id: severity
attributes:
label: Severity
description: How critical is this finding?
options:
- blocking
- should-fix
- suggestion
validations:
required: true

- type: textarea
id: affected-files
attributes:
label: Affected files
description: List the files and line numbers where the issue was found.
placeholder: |
- path/to/file1.ts:42
- path/to/file2.ts:15-23
render: markdown
validations:
required: true

- type: textarea
id: suggested-fix
attributes:
label: Suggested fix
description: How should this finding be addressed?
placeholder: Describe the recommended approach to fix this issue.
validations:
required: false
60 changes: 60 additions & 0 deletions .github/ISSUE_TEMPLATE/task.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Task
description: A structured task created during planning. Scoped to be completable in a single agent session.
title: ""
labels: ["task"]
body:
- type: textarea
id: summary
attributes:
label: Summary
description: What needs to be done and why.
placeholder: Describe the task in 1-3 sentences.
validations:
required: true

- type: textarea
id: files-to-modify
attributes:
label: Files to modify
description: List all files that should be created or changed for this task.
placeholder: |
- path/to/file1.ts
- path/to/file2.ts
render: markdown
validations:
required: true

- type: textarea
id: implementation-steps
attributes:
label: Implementation steps
description: Ordered steps to complete the task. Each step should be concrete and verifiable.
placeholder: |
1. First step
2. Second step
3. Third step
render: markdown
validations:
required: true

- type: textarea
id: acceptance-criteria
attributes:
label: Acceptance criteria
description: Conditions that must be true for this task to be considered complete.
placeholder: |
- [ ] Criterion 1
- [ ] Criterion 2
render: markdown
validations:
required: true

- type: textarea
id: dependencies
attributes:
label: Dependencies
description: Other issues that must be completed before this task can start. Use issue references.
placeholder: |
Blocked by #XX, #YY
validations:
required: false
Empty file removed .github/agent-workflow/.gitkeep
Empty file.
7 changes: 7 additions & 0 deletions .github/agent-workflow/commitlint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {

Check warning on line 1 in .github/agent-workflow/commitlint.config.js

View workflow job for this annotation

GitHub Actions / guardrail/test-ratio

.github/agent-workflow/commitlint.config.js#L1

This implementation file has 7 added lines. The overall test-to-code ratio (0.45) is below the threshold (0.5). Consider adding corresponding tests.

Check warning on line 1 in .github/agent-workflow/commitlint.config.js

View workflow job for this annotation

GitHub Actions / guardrail/scope

.github/agent-workflow/commitlint.config.js#L1

This file is not listed in the task scope for issue #3 or its children. If this change is intentional, approve the PR to override.
extends: ['@commitlint/config-conventional'],
rules: {
// Enforce 72 char limit on subject line
'header-max-length': [2, 'always', 72],
}
};
172 changes: 172 additions & 0 deletions .github/agent-workflow/scripts/guardrail-api-surface.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
const { hasNonStaleApproval } = require('./lib/approval.js');

Check warning on line 1 in .github/agent-workflow/scripts/guardrail-api-surface.js

View workflow job for this annotation

GitHub Actions / guardrail/test-ratio

.github/agent-workflow/scripts/guardrail-api-surface.js#L1

This implementation file has 172 added lines. The overall test-to-code ratio (0.45) is below the threshold (0.5). Consider adding corresponding tests.
const { detectAPIChanges } = require('./lib/api-patterns.js');

module.exports = async function({ github, context, core }) {
const owner = context.repo.owner;
const repo = context.repo.repo;
const prNumber = context.payload.pull_request.number;
const headSha = context.payload.pull_request.head.sha;
const checkName = 'guardrail/api-surface';
const configuredConclusion = process.env.CONCLUSION || 'action_required';

// Check for non-stale PR approval override
const reviews = await github.rest.pulls.listReviews({
owner,
repo,
pull_number: prNumber,
});
const hasValidApproval = hasNonStaleApproval(reviews.data, headSha);

if (hasValidApproval) {
await github.rest.checks.create({
owner,
repo,
head_sha: headSha,
name: checkName,
status: 'completed',
conclusion: 'neutral',
output: {
title: 'API surface check: approved by reviewer',
summary: 'A non-stale PR approval overrides this guardrail.',
},
});
return;
}

// Get PR files and scan for API surface changes
const files = await github.paginate(github.rest.pulls.listFiles, {
owner,
repo,
pull_number: prNumber,
});

// OpenAPI / Swagger file patterns
const openApiFilePatterns = [
/openapi\.(ya?ml|json)$/i,
/swagger\.(ya?ml|json)$/i,
/api-spec\.(ya?ml|json)$/i,
];

const annotations = [];
let totalApiChanges = 0;

for (const file of files) {
// Skip removed files
if (file.status === 'removed') continue;

// Check if this is an OpenAPI/Swagger spec file
const isOpenApiFile = openApiFilePatterns.some((p) => p.test(file.filename));
if (isOpenApiFile) {
totalApiChanges++;
annotations.push({
path: file.filename,
start_line: 1,
end_line: 1,
annotation_level: 'warning',
message: `OpenAPI/Swagger spec file modified: ${file.filename}. API contract changes require careful review.`,
});
continue;
}

// Use API pattern detection from shared library
if (!file.patch) continue;

const apiChanges = detectAPIChanges(file.patch, file.filename);
if (apiChanges.length > 0) {
totalApiChanges += apiChanges.length;

// Parse patch for line numbers
const lines = file.patch.split('\n');
let currentLine = 0;
let changeIndex = 0;

for (const line of lines) {
// Track line numbers from hunk headers
const hunkMatch = line.match(/^@@\s+-\d+(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@/);
if (hunkMatch) {
currentLine = parseInt(hunkMatch[1], 10);
continue;
}

// Only look at added lines
if (line.startsWith('+') && !line.startsWith('+++')) {
if (changeIndex < apiChanges.length) {
annotations.push({
path: file.filename,
start_line: currentLine,
end_line: currentLine,
annotation_level: 'warning',
message: `API surface change: ${apiChanges[changeIndex]} - ${line.substring(1).trim()}`,
});
changeIndex++;
}
}

// Advance line counter for added and context lines
if (!line.startsWith('-')) {
currentLine++;
}
}
}
}

// Report results
if (totalApiChanges === 0) {
await github.rest.checks.create({
owner,
repo,
head_sha: headSha,
name: checkName,
status: 'completed',
conclusion: 'success',
output: {
title: 'API surface check: no changes detected',
summary: 'No API surface changes found in this PR.',
},
});
} else {
// GitHub API limits annotations to 50 per call
const batchSize = 50;
const batches = [];
for (let i = 0; i < annotations.length; i += batchSize) {
batches.push(annotations.slice(i, i + batchSize));
}

const summary = [
`Found ${totalApiChanges} API surface change(s) across the PR.`,
'',
'API surface changes have outsized downstream impact. Review these changes carefully.',
'',
'To override: approve the PR to signal these changes are intentional.',
].join('\n');

// Create the check run with the first batch of annotations
const checkRun = await github.rest.checks.create({
owner,
repo,
head_sha: headSha,
name: checkName,
status: 'completed',
conclusion: configuredConclusion,
output: {
title: `API surface check: ${totalApiChanges} change(s) detected`,
summary,
annotations: batches[0] || [],
},
});

// If there are more annotations, update the check run with additional batches
for (let i = 1; i < batches.length; i++) {
await github.rest.checks.update({
owner,
repo,
check_run_id: checkRun.data.id,
output: {
title: `API surface check: ${totalApiChanges} change(s) detected`,
summary,
annotations: batches[i],
},
});
}
}
};
87 changes: 87 additions & 0 deletions .github/agent-workflow/scripts/guardrail-dependencies.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
const { hasNonStaleApproval } = require('./lib/approval.js');

Check warning on line 1 in .github/agent-workflow/scripts/guardrail-dependencies.js

View workflow job for this annotation

GitHub Actions / guardrail/test-ratio

.github/agent-workflow/scripts/guardrail-dependencies.js#L1

This implementation file has 87 added lines. The overall test-to-code ratio (0.45) is below the threshold (0.5). Consider adding corresponding tests.
const { isDependencyFile } = require('./lib/file-patterns.js');

module.exports = async function({ github, context, core }) {
const CHECK_NAME = 'guardrail/dependency-changes';
const configuredConclusion = process.env.CONCLUSION || 'action_required';

// Check for non-stale approving PR review (override mechanism)
const { data: reviews } = await github.rest.pulls.listReviews({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number
});

const lastCommitSha = context.payload.pull_request.head.sha;
const hasValidApproval = hasNonStaleApproval(reviews, lastCommitSha);

if (hasValidApproval) {
await github.rest.checks.create({
owner: context.repo.owner,
repo: context.repo.repo,
head_sha: context.sha,
name: CHECK_NAME,
status: 'completed',
conclusion: 'neutral',
output: {
title: 'Dependency changes: approved by reviewer',
summary: 'A non-stale PR approval overrides this guardrail check.'
}
});
return;
}

// Get PR changed files
const files = await github.paginate(
github.rest.pulls.listFiles,
{
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
per_page: 100
}
);

const changedDependencyFiles = files.filter(f => isDependencyFile(f.filename));

// No dependency files changed: success
if (changedDependencyFiles.length === 0) {
await github.rest.checks.create({
owner: context.repo.owner,
repo: context.repo.repo,
head_sha: context.sha,
name: CHECK_NAME,
status: 'completed',
conclusion: 'success',
output: {
title: 'Dependency changes: no dependency files modified',
summary: 'No dependency manifest or lock files were changed in this PR.'
}
});
return;
}

// Dependency files changed: requires human review
const fileList = changedDependencyFiles.map(f => '- `' + f.filename + '`').join('\n');
const annotations = changedDependencyFiles.map(f => ({
path: f.filename,
start_line: 1,
end_line: 1,
annotation_level: 'warning',
message: 'Dependency file modified. Human review required before merge.'
}));

await github.rest.checks.create({
owner: context.repo.owner,
repo: context.repo.repo,
head_sha: context.sha,
name: CHECK_NAME,
status: 'completed',
conclusion: configuredConclusion,
output: {
title: `Dependency changes: ${changedDependencyFiles.length} file(s) modified`,
summary: `Dependency files were modified and require human review before merge.\n\n**Changed dependency files:**\n${fileList}\n\n**To resolve:** A maintainer must submit an approving PR review. The approval must be on the current head commit (non-stale).`,
annotations: annotations
}
});
};
Loading
Loading