Implements the Phase 2a Echodo bridge described in agent-pipeline's v0.4 plan (.cursor/plans/pipeline_v0.4_design_+_echodo_54a3bdb7). Echodo becomes the projection layer over the local .convoys/ tree; local files remain source of truth per the local-first contract. MCP lifecycle tools (apps/mcp-server/src/tools/): - create-convoy.ts: registers create_convoy. Creates a Drizzle `project` object with status="draft" + appends initial "## Status log" to the description. Takes workspace_slug + slug + title + classification + skip_flags + success_metric + idea_markdown + repo. - create-brief.ts: registers create_brief. Creates a `task` child of the convoy project. Takes convoyId + briefNumber + title + files_allowlist + depends_on + acceptance_criteria + brief_markdown. - transition-convoy-status.ts: registers transition_convoy_status. Enforces the 9-status state machine from plan §7.3 with valid transitions + actor-permission gates. Appends an audit entry to the description's ## Status log per transition. - log-convoy-event.ts: registers log_convoy_event. Inserts events into the new convoy_events table (one row per role hand-off, with classification, skip-flags, duration, stack-class, outcome, multitask-group metadata). - query-manifest-status.ts: registers query_manifest_status (stub — depends on the Phase 4 pipeline_drift_reports table; documented). - reconcile-from-files.ts: registers reconcile_from_files implementing the local-first recovery path. Reads .convoys/.pending-mcp-sync.jsonl from the given repoPath, replays queued log_convoy_event + transition_convoy_status calls, updates the outbox file on success/failure. Resolves the SPOF risk: failed MCP calls during offline windows reconcile when the bridge returns. Database (packages/database/): - src/schema/convoy_events.ts: new Drizzle schema. Columns: id, workspaceId, convoyId, convoySlug, role, brief, classification, skipFlags, durationS, stackClass, repo, outcome, multitaskGroup, metadata, ts. 4 indexes for per-workspace + per-convoy + role-filtered reads. - src/schema/index.ts: re-exports the new table. - migrations/0010_wandering_the_professor.sql + meta snapshot: generated via drizzle-kit generate. Pure-additive (CREATE TABLE + indexes + FKs). - package.json: db:migrate / db:push / db:studio now use node --env-file to load ../../.env (consistent with the existing mcp-server tsx pattern). db:generate stays as-is (offline operation, no env needed). L1 + L3 agent-pipeline scaffolding installed per agent-pipeline/skills/bootstrap-agent-context v0.5.0: - .agent-context-manifest.yml: tracks 17 artifacts at pipeline version 0.5.0 with sha256 hashes. 4 artifacts flagged customized:true (no-go-zones, CODEOWNERS, pr-health-rollup.yml, echodo.config.json) — adapted from templates for Echodo's monorepo + Drizzle + Coolify + workspace_slug. - .convoys/README.md: explains convoy file convention. - .cursor/agents/echodo.config.json: workspace_slug=convoys-tasks (the workspace created in Echodo UI). Fallback policy: local-only. - .cursor/rules/no-go-zones.mdc: adapted for Drizzle migrations, Coolify deploy infra, mcp-server boundaries. - .github/CODEOWNERS: @rstillw as sole maintainer; targeted rules for apps/, packages/, auth, deploy infra, DB schema, MCP server, agent context. - .github/PULL_REQUEST_TEMPLATE.md: pipeline PR template. - .github/workflows/agent-context-drift.yml: drift monitor against upstream agent-pipeline. - .github/workflows/pr-health-rollup.yml: adapted for tasks' pnpm monorepo + Coolify deploy (no per-PR preview by default). - scripts/log-convoy-event.sh: convoy event logger shim. - scripts/wt.sh: worktree helper stub (deprecated — points at Cursor 3.2 native worktrees). - .gitignore: excludes .convoys/.metrics.jsonl + .convoys/.pending-mcp-sync.jsonl (local agent analytics + MCP outbox). Co-authored-by: Cursor <cursoragent@cursor.com>
87 lines
3.7 KiB
YAML
87 lines
3.7 KiB
YAML
name: PR Health rollup
|
|
|
|
# Adapted for tasks (Echodo): single-job CI (Lint, type-check, test),
|
|
# Coolify deploy (no per-PR preview), Drizzle (no Prisma schema-map drift).
|
|
#
|
|
# Coolify does NOT post a check to GitHub on deploy by default — there's no
|
|
# "build" row to read from a third-party check. The build itself happens AFTER
|
|
# merge, on Coolify's runner.
|
|
|
|
on:
|
|
pull_request:
|
|
branches: [main]
|
|
types: [opened, synchronize, reopened, labeled, unlabeled]
|
|
workflow_run:
|
|
workflows: [CI]
|
|
types: [completed]
|
|
|
|
permissions:
|
|
pull-requests: write
|
|
issues: write
|
|
checks: read
|
|
|
|
jobs:
|
|
rollup:
|
|
name: Aggregate gate status
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Compute status + post sticky comment
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const { owner, repo } = context.repo;
|
|
const pr_number = context.payload.pull_request?.number
|
|
?? context.payload.workflow_run?.pull_requests?.[0]?.number;
|
|
|
|
if (!pr_number) {
|
|
core.info('No PR context — skipping rollup.');
|
|
return;
|
|
}
|
|
|
|
const pr = (await github.rest.pulls.get({ owner, repo, pull_number: pr_number })).data;
|
|
const sha = pr.head.sha;
|
|
|
|
const checks = (await github.rest.checks.listForRef({ owner, repo, ref: sha, per_page: 100 })).data.check_runs;
|
|
const find = (name) => checks.find(c => c.name === name);
|
|
|
|
const skip = (flag) =>
|
|
new RegExp(`pipeline:.*skip[^\\n]*\\b${flag}\\b`).test(pr.body || '');
|
|
|
|
const row = (label, run, opt = false) => {
|
|
if (!run) return `| ${label} | ${opt ? '⏭ skipped or pending' : '⏳ pending'} |`;
|
|
if (run.status !== 'completed') return `| ${label} | ⏳ in progress |`;
|
|
const ok = run.conclusion === 'success';
|
|
return `| ${label} | ${ok ? '✅ pass' : '❌ ' + run.conclusion} |`;
|
|
};
|
|
|
|
const rows = [
|
|
row('CI: Lint, type-check, test', find('Lint, type-check, test')),
|
|
];
|
|
|
|
const comments = (await github.rest.issues.listComments({
|
|
owner, repo, issue_number: pr_number, per_page: 100,
|
|
})).data;
|
|
|
|
const reviewer_comment = comments.find(c => c.body?.startsWith('## Reviewer Report'));
|
|
const a11y_comment = comments.find(c => c.body?.startsWith('## A11y Audit'));
|
|
const ds_comment = comments.find(c => c.body?.startsWith('## Design System Audit'));
|
|
|
|
const role_row = (label, c, skipped) =>
|
|
skipped ? `| ${label} | ⏭ skipped |` : c ? `| ${label} | ✅ posted |` : `| ${label} | ⏳ pending |`;
|
|
|
|
const role_rows = [
|
|
role_row('Reviewer report', reviewer_comment, skip('review')),
|
|
role_row('A11y audit', a11y_comment, skip('a11y')),
|
|
role_row('Design system audit', ds_comment, skip('design')),
|
|
];
|
|
|
|
const marker = '<!-- pipeline-rollup -->';
|
|
const body = `${marker}\n## Pipeline Health\n\n### CI gates\n\n| Gate | Status |\n| --- | --- |\n${rows.join('\n')}\n\n_Build runs on Coolify post-merge; this CI runs lint + type-check + tests only. No per-PR preview URL by default._\n\n### Role reports\n\n| Role | Status |\n| --- | --- |\n${role_rows.join('\n')}\n\nSee individual comments above for details. This rollup updates automatically.`;
|
|
|
|
const existing = comments.find(c => c.body?.startsWith(marker));
|
|
|
|
if (existing) {
|
|
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
|
|
} else {
|
|
await github.rest.issues.createComment({ owner, repo, issue_number: pr_number, body });
|
|
}
|