Skip to content
Closed
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
33 changes: 29 additions & 4 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,42 @@
## Summary
## Canonical issue

Describe the outcome and the evidence that supports it.
Closes #

## Linked issue
## Outcome

Fixes #
Describe the user or operational result this PR produces.

## Scope

- Included:
- Explicitly excluded:

## Risk

- Risk level: low / medium / high
- Failure mode:
- Rollback:

## Verification

List exact automated and manual checks, tied to the current head SHA.

- [ ] Focused tests
- [ ] Required CI
- [ ] Review threads resolved

## Production evidence

Provide the Vercel preview, production deployment, runtime evidence, or state why production evidence is not applicable.

## Agent handoff

- [ ] One canonical issue is linked
- [ ] No competing PR implements the same issue
- [ ] Acceptance criteria are satisfied
- [ ] Required checks pass on the current head
- [ ] Human decision is requested only for product, security, irreversible infrastructure, or production approval

## Agent provenance

Human-authored pull requests may delete this section. Agent-authored pull requests must replace agent-lock-example with agent-lock-manifest and fill the values. Scope and test paths remain authoritative in the linked issue.
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ concrete reason, verified against the actual repository tree.
| `mcp-optimization.yml` | **DELETE** | Entire workflow targets `mcp-servers/mcp-profiling/` (requirements.txt, investigator_client.py, profiling_server.py) which does not exist — every run fails. |
| `phase-goal-tracker.yml` | KEEP | Tracks markdown checklists on phase issues, keeps a single status comment updated, and auto-closes the issue when all checklist goals are complete. |
| `pr-checks.yml` | KEEP | Validates PR title/description; fork-safe comment handling. |
| `pr-governance.yml` | **ADD** | Enforces exactly one canonical closing issue, required delivery-evidence sections, and fails when another open PR closes the same issue. Draft PRs are intentionally non-blocking. |
| `real-processing.yml` | KEEP | Manual single-video processing; well-formed. |
| `repository-reconciliation.yml` | **ADD** | Scheduled/manual non-destructive reconciliation that updates one `[automation] Repository drift report` issue and proposes stale detached branches without deleting them. |
| `secret-scan.yml` | KEEP | gitleaks on the working tree; action pinned to SHA, checksum-verified install. |
| `security.yml` | KEEP | npm audit, safety, bandit, trivy; uploads SARIF. |
| `verify-litert-mcp.yml` | **DELETE** | Path-filtered smoke test of `mcp-servers/litert-mcp/server.py`; the `mcp-servers/` tree was removed in the dead-code cleanup, so the target no longer exists and every run fails. |
Expand Down Expand Up @@ -65,4 +67,4 @@ valid. Referenced paths were checked against the working tree:

| `agent-completion-enforcement.yml` | **ADD** | Protected-default-branch verifier that creates the independent **Agent completion enforcement** Check directly against the PR head SHA. It accepts only an exact-head machine-readable report from the configured dedicated GitHub App; missing/stale/mutable evidence, untrusted label provenance, and custom roles all fail closed. The existing `agent-completion/truth-gate` status stays advisory and must not be made required. |

The protected policy at `.github/agent-lock/trusted-publishers.json` starts with empty allowlists and therefore blocks until a repository administrator provisions the dedicated App and trusted actor identities through protected review. The repository ruleset must then require **Agent completion enforcement**, one independent approval, and resolved conversations.
The protected policy at `.github/agent-lock/trusted-publishers.json` starts with empty allowlists and therefore blocks until a repository administrator provisions the dedicated App and trusted actor identities through protected review. The repository ruleset must then require **Agent completion enforcement**, one independent approval, and resolved conversations.
8 changes: 7 additions & 1 deletion .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ workflow; this README is the index.
| Bulk Issue Processor | `bulk-issue-processor.yml` | manual | Bulk label / summarize / close-stale across many issues |
| Close stale issues | `stale.yml` | daily (00:00 UTC) | Mark and close stale issues and PRs |
| Branch Cleanup | `branch-cleanup.yml` | manual; push sentinel on `claude/branch-cleanup-*` | Gated archive-then-delete of branches (dry-run by default); push `[restore-branch:<branch>]` sentinel to restore a deleted branch from its archive tag |
| PR Governance | `pr-governance.yml` | PR opened/edited/synchronize/ready | Enforce one canonical closing issue, required governance sections, and competing-PR detection |
| Repository Reconciliation | `repository-reconciliation.yml` | daily; manual | Non-destructive drift report of PR/issue linkage and stale unattached branches |
| E2E Tests | `e2e-tests.yml` | push / PR to `main` | Run Vitest E2E pipeline tests against production or the PR's Vercel preview deployment and report results on the PR |
| Autonomous Video Processing | `autonomous-video-processing.yml` | manual | Batch-process YouTube videos by category (matrix) |
| Real Video Processing (Cloud) | `real-processing.yml` | manual | Process a single video: transcript and/or AI analysis |
Expand Down Expand Up @@ -111,6 +113,10 @@ A full audit of this directory was performed (see
- **Fixed** `auto-assign.yml` — replaced `gh issue edit` with the REST
assignees endpoint after run logs showed GitHub App installation tokens cannot
use the CLI's GraphQL assignable mutation for this assignment.
- **Added** `pr-governance.yml` — validates exactly one canonical closing issue,
required delivery evidence sections, and blocks competing open implementation PRs.
- **Added** `repository-reconciliation.yml` — publishes/updates a single
`[automation] Repository drift report` issue without deleting branches.

## Resources

Expand All @@ -126,4 +132,4 @@ A full audit of this directory was performed (see

`pr-checks.yml` retains the advisory `agent-completion/truth-gate/pr-<number>` status; it is never required. `agent-completion-enforcement.yml` runs protected default-branch code, does not execute PR code, and creates the separate **Agent completion enforcement** Check directly on the PR head SHA. It accepts only an exact-head, machine-readable report published by the configured dedicated GitHub App. Missing, stale, edited/deleted, ambiguous, or untrusted evidence fails closed.

Before enabling the rule, provision `.github/agent-lock/trusted-publishers.json` through protected review with the trusted App and actor allowlists. Empty lists intentionally block. Configure the repository ruleset to require **Agent completion enforcement**, one independent approval, and resolved conversations. Do not require `agent-completion/truth-gate`.
Before enabling the rule, provision `.github/agent-lock/trusted-publishers.json` through protected review with the trusted App and actor allowlists. Empty lists intentionally block. Configure the repository ruleset to require **Agent completion enforcement**, one independent approval, and resolved conversations. Do not require `agent-completion/truth-gate`.
80 changes: 80 additions & 0 deletions .github/workflows/pr-governance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: PR Governance

on:
pull_request_target:
types: [opened, edited, reopened, synchronize, ready_for_review]

permissions:
contents: read
issues: read
pull-requests: read

concurrency:
group: pr-governance-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
policy:
name: Canonical issue and evidence
runs-on: ubuntu-latest
steps:
- name: Validate delivery contract
uses: actions/github-script@v8
with:
script: |
const pr = context.payload.pull_request;
if (pr.draft) {
core.notice("Draft PR: governance enforcement begins when marked ready.");
return;
}

const body = pr.body || "";
const requiredSections = [
"## Canonical issue",
"## Outcome",
"## Risk",
"## Verification",
"## Production evidence"
];
const missing = requiredSections.filter(section => !body.includes(section));

const closingPattern = /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)/gi;
const issueNumbers = [...body.matchAll(closingPattern)].map(match => Number(match[1]));
const canonicalIssues = [...new Set(issueNumbers)];

if (canonicalIssues.length !== 1) {
missing.push("exactly one closing reference: Closes #<issue>");
}

if (canonicalIssues.length === 1) {
const canonical = canonicalIssues[0];
const pulls = await github.paginate(github.rest.pulls.list, {
owner: context.repo.owner,
repo: context.repo.repo,
state: "open",
per_page: 100
});

const competing = pulls.filter(candidate => {
if (candidate.number === pr.number) return false;
const matches = [...(candidate.body || "").matchAll(closingPattern)]
.map(match => Number(match[1]));
return matches.includes(canonical);
});

if (competing.length) {
const links = competing.map(candidate => `#${candidate.number}`).join(", ");
core.setFailed(
`Issue #${canonical} already has another open implementation PR: ${links}. ` +
"Supersede, close, or explicitly consolidate it before this PR becomes canonical."
);
return;
}
}

if (missing.length) {
core.setFailed(`PR delivery contract is incomplete: ${missing.join("; ")}`);
return;
}

core.notice("PR has one canonical issue and the required delivery evidence sections.");
115 changes: 115 additions & 0 deletions .github/workflows/repository-reconciliation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
name: Repository Reconciliation

on:
schedule:
- cron: "17 13 * * *"
workflow_dispatch:

permissions:
contents: read
issues: write
pull-requests: read

concurrency:
group: repository-reconciliation
cancel-in-progress: true

jobs:
report:
runs-on: ubuntu-latest
steps:
- name: Reconcile canonical delivery state
uses: actions/github-script@v8
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const now = Date.now();
const staleAfterMs = 14 * 24 * 60 * 60 * 1000;
const closingPattern = /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)/gi;

const pulls = await github.paginate(github.rest.pulls.list, {
owner, repo, state: "open", per_page: 100
});
const branches = await github.paginate(github.rest.repos.listBranches, {
owner, repo, protected: false, per_page: 100
});
const activeHeads = new Set(pulls.map(pr => pr.head.ref));

const untracked = [];
const issueToPulls = new Map();
for (const pr of pulls) {
const issues = [...(pr.body || "").matchAll(closingPattern)]
.map(match => Number(match[1]));
const unique = [...new Set(issues)];
if (unique.length !== 1) untracked.push(pr);
for (const issue of unique) {
const existing = issueToPulls.get(issue) || [];
existing.push(pr.number);
issueToPulls.set(issue, existing);
}
}

const duplicates = [...issueToPulls.entries()]
.filter(([, numbers]) => numbers.length > 1);

const staleBranches = [];
for (const branch of branches) {
if (branch.name === "main" || activeHeads.has(branch.name)) continue;
const commit = await github.rest.repos.getCommit({
owner, repo, ref: branch.commit.sha
});
const date = commit.data.commit.committer?.date || commit.data.commit.author?.date;
if (date && now - new Date(date).getTime() > staleAfterMs) {
staleBranches.push({ name: branch.name, date, sha: branch.commit.sha.slice(0, 8) });
}
}

const lines = [
"## Canonical delivery-state reconciliation",
"",
`Generated: ${new Date().toISOString()}`,
"",
`- Open PRs: **${pulls.length}**`,
`- Remote branches: **${branches.length}**`,
`- Ready PRs without exactly one canonical issue: **${untracked.length}**`,
`- Issues with competing implementation PRs: **${duplicates.length}**`,
`- Unattached branches older than 14 days: **${staleBranches.length}**`,
"",
"### PRs requiring canonical issue",
untracked.length
? untracked.map(pr => `- #${pr.number} — ${pr.title}`).join("\n")
: "- None",
"",
"### Competing PRs",
duplicates.length
? duplicates.map(([issue, numbers]) => `- Issue #${issue}: ${numbers.map(n => `#${n}`).join(", ")}`).join("\n")
: "- None",
"",
"### Stale unattached branches",
staleBranches.length
? staleBranches.slice(0, 100).map(branch =>
`- \`${branch.name}\` — ${branch.sha}, last commit ${branch.date}`
).join("\n")
: "- None",
"",
"> This report is intentionally non-destructive. Branch deletion requires a merged PR or an explicit retention decision.",
"",
"Canonical governance: #898"
];

const title = "[automation] Repository drift report";
const query = `repo:${owner}/${repo} is:issue is:open in:title "${title}"`;
const existing = await github.rest.search.issuesAndPullRequests({
q: query, per_page: 10
});
const report = existing.data.items.find(item => item.title === title);
const body = lines.join("\n");

if (report) {
await github.rest.issues.update({
owner, repo, issue_number: report.number, body
});
} else {
await github.rest.issues.create({ owner, repo, title, body });
}
1 change: 1 addition & 0 deletions apps/web/vercel.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"framework": "nextjs",
"installCommand": "cd ../.. && npm install --legacy-peer-deps",
"ignoreCommand": "bash ../../scripts/deployment/vercel-ignore-command.sh",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .vercelignore excludes the entire scripts/ directory, so the ignoreCommand script referenced in apps/web/vercel.json is removed during clone and the build fails with "No such file or directory".

Fix on Vercel

"buildCommand": "npm run build",
"outputDirectory": ".next"
}
56 changes: 56 additions & 0 deletions scripts/deployment/vercel-ignore-command.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
set -euo pipefail

# Vercel ignored-build command semantics:
# - exit 0 => skip build
# - exit 1 => continue build
#
# We skip only when the diff is strictly docs/workflow metadata.

head_sha="${VERCEL_GIT_COMMIT_SHA:-}"
base_sha="${VERCEL_GIT_PREVIOUS_SHA:-}"

if [[ -z "${head_sha}" || -z "${base_sha}" ]]; then
echo "preview-ignore: missing commit context; running build"
exit 1
fi

if ! git cat-file -e "${head_sha}^{commit}" 2>/dev/null; then
echo "preview-ignore: head commit not available locally; running build"
exit 1
fi

if ! git cat-file -e "${base_sha}^{commit}" 2>/dev/null; then
echo "preview-ignore: base commit not available locally; running build"
exit 1
fi

mapfile -t changed < <(git diff --name-only "${base_sha}" "${head_sha}")
if [[ ${#changed[@]} -eq 0 ]]; then
echo "preview-ignore: no changed files detected; running build"
exit 1
fi

for path in "${changed[@]}"; do
if [[ "${path}" == docs/* ]]; then
continue
fi
if [[ "${path}" == .github/workflows/* ]]; then
continue
fi
if [[ "${path}" == .github/ISSUE_TEMPLATE/* ]]; then
continue
fi
if [[ "${path}" == .github/pull_request_template.md ]]; then
continue
fi
if [[ "${path}" == *.md ]]; then
continue
fi

echo "preview-ignore: app-impacting change detected (${path}); running build"
exit 1
done

echo "preview-ignore: docs/workflow-only change; skipping preview build"
exit 0
Loading
Loading