deckhearth/.github/workflows/agent-context-drift.yml
Randall Stillwell bb05ca731b bootstrap: agent pipeline v0.5.0 + ship-readiness review
Installs the three-layer agent-pipeline scaffold (https://github.com/varutasu/agent-pipeline @ v0.5.0):

L1 — Context (curated brain)
- AGENTS.md: orientation, conventions, 8 explicit gotchas
- .cursor/rules/: no-go-zones, api-routes, auth-and-permissions,
  db-and-schema, ui-and-theming, schema-map
- .cursor/skills/: add-api-route, add-page recipes
- docs/agent-context/README.md: layer explainer
- docs/SCHEMA_MAP.md: hand-curated Neon Postgres reference
  (replaces Prisma schema map since stack is raw SQL)

L2 — Subagent roles (copied verbatim from upstream templates)
- 9 .cursor/agents/role-*.md files: Conductor, IA-Architect,
  UX-Reviewer, Architect, Implementer, Reviewer,
  Design-System-Auditor, A11y-Auditor, Doc-Writer

L3 — Pipeline scaffolding (Vercel variant)
- CI: lint + schema-map-drift only (no duplicate build —
  Vercel handles it). Test job commented out until vitest lands.
- preview-smoke + visual-diff via wait-for-vercel-preview
- pr-health-rollup sticky comment aggregator
- agent-context-drift weekly cron
- PULL_REQUEST_TEMPLATE, CODEOWNERS (auth/admin paths tagged)
- .convoys/ folder + seed ship-readiness.md review
- lib/flags/index.js (JS — converted from TS template)
- scripts/wt.sh (Cursor 3.2 deprecation stub),
  scripts/log-convoy-event.sh
- tests/smoke/app.smoke.spec.ts (Playwright skeleton)

Manifest
- .agent-context-manifest.yml: tracks 31 artifacts by sha256
  for future sync-agent-context drift detection

Review
- .convoys/ship-readiness.md: 16 findings (7 P0 ship-blockers,
  5 P1 quality-bar, 4 P2 refactor, P3 UX/IA/a11y/docs) with
  proposed 13-convoy launch sequence.

No production code changed in this commit. All findings in
the ship-readiness review will be addressed in follow-up convoys
starting with fix-auth-bypass.

Structural brain: user-code-review-graph MCP has indexed the
codebase (122 files, 628 nodes, 5602 edges, 11 communities,
84 flows). Per-developer; not committed.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 23:16:08 -05:00

164 lines
6.6 KiB
YAML

# agent-context drift detection
#
# Weekly + on-demand check: does this repo's installed agent-pipeline
# artifacts match the latest upstream pipeline release?
#
# - Reads .agent-context-manifest.yml (committed at repo root)
# - Clones the pipeline repo at its latest tag
# - Compares each tracked artifact's installed_hash to the pipeline source hash
# - Compares manifest pipeline_version to pipeline version.txt
# - Opens (or updates) an issue titled "agent-context: N files behind v<X>"
# if drift is detected
#
# No auto-fix. The fix workflow is: a human runs `sync-agent-context` in
# Cursor and reviews per-file diffs.
name: agent-context-drift
on:
schedule:
# Mondays at 13:00 UTC. Adjust to taste.
- cron: "0 13 * * 1"
workflow_dispatch:
permissions:
contents: read
issues: write
jobs:
drift:
runs-on: ubuntu-latest
steps:
- name: Checkout consumer repo
uses: actions/checkout@v4
- name: Read manifest
id: manifest
run: |
if [ ! -f .agent-context-manifest.yml ]; then
echo "::warning::No .agent-context-manifest.yml — agent-pipeline not installed or pre-v0.3.0. Skipping drift check."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
INSTALLED=$(grep -E '^pipeline_version:' .agent-context-manifest.yml | head -1 | sed -E 's/.*"(.*)".*/\1/')
SOURCE=$(grep -E '^pipeline_source:' .agent-context-manifest.yml | head -1 | sed -E 's/.*"(.*)".*/\1/')
echo "installed_version=$INSTALLED" >> "$GITHUB_OUTPUT"
echo "pipeline_source=$SOURCE" >> "$GITHUB_OUTPUT"
echo "skip=false" >> "$GITHUB_OUTPUT"
- name: Clone pipeline at latest tag
if: steps.manifest.outputs.skip != 'true'
id: pipeline
run: |
PIPELINE_URL="${{ steps.manifest.outputs.pipeline_source }}"
# Convert HTTPS URL → clone target. Already in HTTPS form.
mkdir -p /tmp/pipeline
git clone --depth 50 "$PIPELINE_URL" /tmp/pipeline
cd /tmp/pipeline
LATEST_TAG=$(git tag --sort=-v:refname | head -1)
if [ -z "$LATEST_TAG" ]; then
echo "::warning::Pipeline repo has no tags. Comparing against main."
LATEST_TAG="main"
fi
git checkout "$LATEST_TAG"
PIPELINE_VER=$(cat version.txt | tr -d '[:space:]')
echo "tag=$LATEST_TAG" >> "$GITHUB_OUTPUT"
echo "version=$PIPELINE_VER" >> "$GITHUB_OUTPUT"
- name: Compute drift
if: steps.manifest.outputs.skip != 'true'
id: drift
run: |
INSTALLED="${{ steps.manifest.outputs.installed_version }}"
UPSTREAM="${{ steps.pipeline.outputs.version }}"
BEHIND=0
CUSTOMIZED=0
CONFLICT=0
# Walk manifest artifacts. For each, compare installed_hash to local
# current hash, and pipeline source hash to installed_hash.
# YAML parsing in bash is intentionally minimal — relies on the
# bootstrap skill emitting a predictable shape.
python3 - <<'PY' >> drift-report.md
import hashlib, sys, yaml, os
def sha(path):
if not os.path.exists(path):
return None
h = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
h.update(chunk)
return "sha256:" + h.hexdigest()
with open(".agent-context-manifest.yml") as f:
m = yaml.safe_load(f)
counts = {"behind": [], "customized": [], "conflict": [], "deleted": []}
for art in m.get("artifacts", []):
local_hash = sha(art["path"])
pipe_hash = sha(os.path.join("/tmp/pipeline", art["source"]))
if local_hash is None:
counts["deleted"].append(art["path"])
continue
local_matches = local_hash == art["installed_hash"]
pipe_changed = pipe_hash is not None and pipe_hash != art["installed_hash"]
if local_matches and pipe_changed:
counts["behind"].append(art["path"])
elif not local_matches and pipe_changed:
counts["conflict"].append(art["path"])
elif not local_matches:
counts["customized"].append(art["path"])
print("# agent-context drift report")
print(f"\nInstalled: `{m.get('pipeline_version')}` · Upstream: `${{ steps.pipeline.outputs.version }}` (`${{ steps.pipeline.outputs.tag }}`)")
for kind in ("behind", "conflict", "customized", "deleted"):
files = counts[kind]
if files:
print(f"\n## {kind} ({len(files)})")
for f in files:
print(f"- `{f}`")
PY
BEHIND=$(grep -c '^## behind' drift-report.md || echo 0)
CONFLICT=$(grep -c '^## conflict' drift-report.md || echo 0)
NEED_ISSUE="false"
if [ "$INSTALLED" != "$UPSTREAM" ] || [ "$BEHIND" -gt 0 ] || [ "$CONFLICT" -gt 0 ]; then
NEED_ISSUE="true"
fi
echo "need_issue=$NEED_ISSUE" >> "$GITHUB_OUTPUT"
echo "upstream_version=$UPSTREAM" >> "$GITHUB_OUTPUT"
- name: Open / update drift issue
if: steps.manifest.outputs.skip != 'true' && steps.drift.outputs.need_issue == 'true'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const body = fs.readFileSync('drift-report.md', 'utf8') +
'\n\n---\n\n_To resolve: open this repo in Cursor and ask **"Sync agent context for this repo"**. The sync skill walks the diff per file._';
const title = `agent-context: behind ${{ steps.drift.outputs.upstream_version }}`;
const existing = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'agent-context-drift',
});
const found = existing.data.find(i => i.title.startsWith('agent-context: behind'));
if (found) {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: found.number,
title,
body,
});
} else {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title,
body,
labels: ['agent-context-drift'],
});
}