Skip to content

Latest commit

 

History

History
439 lines (361 loc) · 26 KB

File metadata and controls

439 lines (361 loc) · 26 KB

Contributing to Agentweaver

Thanks for considering a contribution! This doc is the contribution process guide — how to make a change and get it merged. It has two parts:

Prerequisites

See Prerequisites in the getting started guide for the tools you'll need (git, Node.js 22+, .NET 10 SDK, and Azure CLI if you're touching the provisioning/deployment scripts) and per-platform install instructions (winget/brew/apt-get).

Getting set up

git clone https://github.com/sabbour/agentweaver.git
cd agentweaver
npm run setup   # checks/prepares local prerequisites (git, dotnet, node)
npm run dev     # starts the API (http://localhost:5000) and Web UI (http://localhost:5173)

See the Getting started guide for the full walkthrough, including configuring a GitHub OAuth App for local sign-in (the callback URL for local dev is http://localhost:5000/auth/github/callback — the API's own origin, no /api prefix, since that endpoint is mapped at the root, not under /api).

Repository layout

Path What it is
apps/Agentweaver.Api The .NET API (coordinator, runs, memory, auth, sandbox orchestration)
apps/Agentweaver.Mcp The MCP server surface
apps/web The React/Vite frontend
docs/ VitePress documentation site (published at sabbour.me/agentweaver)
packages/ Shared .NET libraries (agent runtime, squad model, etc.)
scripts/azure The Node.js provisioning/deployment/release toolchain (no bash/PowerShell)
tests/Agentweaver.Tests .NET test suite
tests/e2e End-to-end tests
k8s/ Kustomize-based Kubernetes manifests (AKS deployment): k8s/base/ (generic manifests), k8s/overlays/production/ (image tags, ConfigMap/replacements patches), k8s/reference/ (non-deployed examples/one-off jobs)

Making a change

  1. Use a short-lived branch and PR for every change. dev is the default, protected integration branch. Branch from current origin/dev with a descriptive conventional prefix, e.g. fix/123-short-desc, feat/short-desc, or docs/short-desc.
    • Direct pushes to dev are not allowed, including tiny and docs-only changes.
    • Before merge, the branch must be current with dev and all blocking CI must rerun successfully. GitHub enforces this through “require branches to be up to date before merging.”
    • Squash-merge so dev keeps one commit per logical change. GitHub automatically deletes the source branch after merge.
    • main is stable/published-only. Do not open ordinary PRs into it; it receives a soaked release promotion or an audited emergency hotfix only.
    • Do not create a long-lived local integration/staging branch as a private promotion pipeline. A disposable merge-test branch or worktree is fine, but delete it after validation.
  2. Keep changes focused. Scope one change to one concern — it's easier to review and to revert if something goes wrong.
  3. Add or update tests for any behavior change (see Testing below).
  4. Update docs if you changed user-facing behavior, npm scripts, or configuration — README.md and docs/guide/ are the two places most likely to need updates.
  5. Run the relevant test suite(s) locally before you push. CI re-runs the full suite on every pull request and push to dev or main, but running the affected suite locally first keeps the feedback loop short and avoids red PRs.
  6. Add a changeset for shipped user-facing behavior before opening the PR: run npm run changeset, use patch for compatible fixes and minor for features or breaking changes while Agentweaver is at 0.x, and write prose for users rather than restating a commit title. Do not edit VERSION, package versions, or CHANGELOG.md. Docs/tests/CI-only changes normally need no changeset; use the changeset:not-required label only with a Changeset exemption: rationale in the PR body. Infrastructure and local deployment commands never consume changesets; release:publish consumes only metadata already prepared by release:prepare. The Changeset advisory CI job blocks merge if it detects a release-relevant change with no changeset and no exemption, so this can't be silently skipped. For the full playbook, see the changelog skill.
  7. Verify live for anything with runtime/deploy impact, not just via unit tests.

Writing a changeset

Good: “Add exportable workflow bundles so operators can move a workflow between installations.”

Bad: “feat: add export.” It repeats a commit title without explaining the user impact.

Branch Topology

The active topology is dev → release/vX.Y.Z → main:

  • dev is the default, protected integration branch. Normal PRs target it and use required PRs, blocking CI, up-to-date-before-merge, squash merge, and automatic source branch deletion.
  • release/vX.Y.Z is an ephemeral release-candidate/soak branch cut from a green dev SHA. Stabilization fixes land there by PR and are immediately forward-ported to dev.
  • main is stable/published-only. It receives only a promotion PR from a soaked release branch or an audited emergency hotfix, which must be forward-ported to dev. Release tags are cut from the exact resulting main promotion SHA.

The complete operating flow is in RELEASING.md.

Testing

Prepare dependencies once per worktree. Every package root keeps a physical, private node_modules; developer worktrees share only a fingerprinted npm download cache under Git's common directory. A new worktree still runs reproducible npm ci, while an unchanged local tree is verified and reused. CI, npm workspaces/local links, and authenticated npm configuration use isolated npm ci:

npm run deps:ensure

For an ordinary branch or a layer in a stacked change, run only the affected areas:

npm run validate:layer

# .NET changes require a focused VSTest filter at layer level.
npm run validate:layer -- --area dotnet \
  --dotnet-filter "FullyQualifiedName~Agentweaver.Tests.Coordinator"

The layer profile detects Node toolchain, web, docs, and .NET paths relative to origin/dev. It runs web tests and lint after one dependency setup. A .NET layer must name a focused filter. The layer check is advisory; each PR still receives required CI. NuGet packages are shared by the user cache, while locked restore, build output, and tests remain worktree-local.

At the top of a completed stack, run the full profile against the exact integrated tree. PR 1 must independently pass the full suite against its current dev merge candidate before merge; a green stack top is not a substitute:

npm run validate:full

The underlying area commands remain available for focused troubleshooting:

# .NET API / packages: locked restore, one build, then test exact outputs
dotnet restore tests/Agentweaver.Tests/Agentweaver.Tests.csproj --locked-mode -p:CopilotSkipCliDownload=true
dotnet build tests/Agentweaver.Tests/Agentweaver.Tests.csproj --no-restore -p:CopilotSkipCliDownload=true
dotnet test tests/Agentweaver.Tests/Agentweaver.Tests.csproj --no-build --no-restore -p:CopilotSkipCliDownload=true

# Node.js provisioning/deployment/release toolchain and CI contracts
node --test scripts/azure/tests/*.test.mjs scripts/changesets/tests/*.test.mjs scripts/ci/tests/*.test.mjs

# UI harness fixture/regression suite
node scripts/ci/shared-deps.mjs ensure --project scripts/ui-harness
npm --prefix scripts/ui-harness test

# Web frontend (Vitest)
npm --prefix apps/web run test

# Web frontend lint
npm --prefix apps/web run lint

# Docs site build (only if you changed docs/)
npm run docs:build

See Validation workflow for cache keys, invalidation, fallback behavior, and timing output.

Continuous integration

Pull requests and pushes to dev and main are verified by the CI workflow. It runs the same commands documented under Testing above, split into one job per area so each gets a dedicated runner (several .NET and web tests are timing-sensitive and flake under CPU contention if crowded onto a single runner). A changes job classifies each diff by path first, and every suite job below except Changeset advisory only runs when its path group actually changed (any edit to .github/workflows/ci.yml itself always runs everything, so the pipeline is always fully verified when it changes; edits to other workflow files don't run these suites, since they don't drive them); a job that's skipped this way still counts as passing for required-status-checks purposes:

Job What it runs Gating Runs when
Seven .NET test shard (…) jobs Stable namespace shards plus isolated PostgreSQL/Testcontainers, process-global environment, and Kata runtime gates; every shard writes TRX results Blocking — must pass Every PR targeting dev, so all seven ruleset-required contexts are emitted even for metadata-only PRs
Node toolchain tests Full Node toolchain/CI-helper tests plus npm --prefix scripts/ui-harness test Blocking — must pass Node toolchain paths or UI harness/shared harness paths changed
Web tests Web tests and lint after one isolated npm ci Blocking — must pass apps/web/** changed
Docs build npm run docs:build Blocking — must pass docs/** changed
Changeset advisory npm run version:check && npm run changeset:check Blocking — must pass Always, on every PR

The repository policy requires the seven named .NET shard jobs plus the Node toolchain, web, docs, and changeset jobs on a branch that is up to date with dev. Path-conditional non-.NET jobs count as passing when skipped; the named .NET shard jobs intentionally run on every dev PR so GitHub emits each required context. The GitHub ruleset described in .github/dev-branch-protection.md is active, so admission is mechanical: direct pushes to dev are rejected and merges are blocked until the branch is current and the required checks are green. Changeset advisory now fails the build (not just a warning) when a release-relevant change has no changeset and no changeset:not-required exemption.

CI is the full-suite authority and deliberately keeps job filesystems isolated. Its npm steps call the same dependency helper with --isolated, preserving npm ci reproducibility. Local concurrent worktrees share only npm download content; writable dependency trees and build/test outputs never cross worktree boundaries.

Container image publishing

The Publish images workflow builds the four Agentweaver container images and publishes them to GitHub's container/artifact registry (ghcr.io/<owner>/agentweaver-{api,frontend,mcp,agent-host}). It never deploys anything — Azure/AKS deployment stays with the npm run azure:* toolchain.

The image list is not restated in the workflow: the plan job derives its build matrix from scripts/azure/image-spec.mjs (the deploy toolchain's single source of truth) through scripts/ci/ghcr-plan.mjs, which is unit-tested by the Node toolchain tests job. Triggers map to the branch topology above:

  • push to dev:sha-<short> and :dev
  • push to release/vX.Y.Z:sha-<short> and :rc-X.Y.Z
  • push to main:sha-<short> and :main
  • a published GitHub Release → :sha-<short>, :X.Y.Z, :vX.Y.Z, and :latest (:latest is skipped for prereleases)
  • manual workflow_dispatch on any ref (an arbitrary commit) → :sha-<short> only, with an optional build-only dry run that skips the push

Every build publishes the immutable sha-<short> tag, so any image is addressable by the exact commit it was built from — the same identifier model npm run azure:deploy-from-local and azure:deploy-from-commit use.

Opening a pull request

  • Keep the PR scoped to one concern and give it a conventional-commit-style title (see Commit messages) — the title is what shows up in the generated changelog and the GitHub Release notes.
  • Describe what changed and why, and how you verified it (which suite(s) you ran, and any live/deploy verification for runtime changes).
  • Make sure the blocking CI jobs are green and that you have not introduced new lint findings before asking for review.
  • Update, retest, then squash-merge. If another PR reaches dev first, GitHub marks yours out of date. Update from origin/dev, resolve conflicts, rerun relevant tests/CI, and merge only after all required checks are green on the updated branch.

Contributing from a fork

Fork the repository on GitHub, clone your fork, add the canonical repository as the upstream remote, and create your short-lived branch from an up-to-date upstream/dev. Open the PR from that branch to dev; it follows the same CI, up-to-date, review, and squash-merge rules as every other contribution.

Fork PRs do not receive repository secrets: CI uses the pull_request trigger (not pull_request_target) and its jobs do not use secrets.*. CODEOWNERS and a required approval for non-owner PRs are not active today. On the first real external fork PR, audit the fork workflow again, then add/activate those controls as a checklist item; do not assume they already exist.

Labels

.github/labels.json is the canonical taxonomy for new and relabeled issues. Use one type:*, priority:*, go:*, and release:* label where applicable; use squad:{member} for ownership and an optional area:* label for product scope. sync-squad-labels.yml reads that manifest for static labels and generates squad member labels from the roster. The legacy bug, enhancement, and workstream:* labels are deprecated in favor of type:bug, type:feature, and the smaller area:* vocabulary; existing issues are not being mass-relabelled.

Commit messages

Changesets generates new CHANGELOG.md sections from reviewed release-note fragments. Please use a conventional-commit-style prefix:

  • feat: ... — new functionality
  • fix: ... — bug fixes
  • docs: ... — documentation-only changes
  • chore: ... / refactor: ... — internal changes with no user-facing behavior change
  • test: ... — test-only changes

Code style

  • .NET: follow the existing conventions in the file/module you're editing. Don't introduce a new formatting style into an existing file.
  • .NET dependencies: every project restores with RestorePackagesWithLockFile=true (see Directory.Build.props) and commits a packages.lock.json, and CI restores with RestoreLockedMode=true so an unreviewed dependency version change fails the build instead of silently resolving a newer package. Use exact PackageReference versions (no floating 1.* or open ranges) for new/changed dependencies. After adding, removing, or changing a package version, regenerate the affected lock file(s) with dotnet restore --force-evaluate and commit the updated packages.lock.json alongside the .csproj change.
  • Node.js (scripts/azure/): ESM (.mjs), no bash/PowerShell — this toolchain is intentionally 100% cross-platform Node.js (it fully replaced the earlier bash/PowerShell scripts). Read the module header comment at the top of the relevant scripts/azure/*.mjs file before changing behavior — several non-obvious ordering/timing decisions are documented there specifically to avoid reintroducing past bugs.
  • Web: TypeScript + React, FluentUI components. Run npm --prefix apps/web run lint before submitting.

Where NOT to make changes

  • Do not hand-edit generated CHANGELOG.md release sections; add or correct the source changeset instead.
  • Do not add build/deploy logic outside scripts/azure/ — bash/PowerShell scripts were fully removed in favor of the Node.js toolchain; don't reintroduce a second toolchain.
  • Do not commit secrets (API keys, GitHub OAuth client secrets, connection strings) — appsettings.Development.json is git-ignored for local secrets; use .NET user-secrets or environment variables instead.
  • Do not weaken auth/security checks (or otherwise take shortcuts) just to make a test or a manual verification pass — fix the real blocker instead.

AI agent contributions

Some contributions to this repo are made by AI agents rather than people. This project is developed with Squad, a team of named agents (Trinity, Tank, Morpheus, Smith, Link, Seraph, Scribe, Ralph, Rai, and others), and can optionally route work to GitHub's @copilot coding agent when it is on the roster. This section documents how that agent-driven flow works. It does not replace the human workflow above — human contributors follow the same branch → up-to-date PR → squash-merge path in Making a change and can skip this section.

Issue-driven lifecycle. Agent work is anchored to a GitHub issue and follows issue -> branch -> PR -> review -> merge. The label-based automation in .github/workflows/ drives it:

  • sync-squad-labels.yml keeps the squad:{member} labels in sync with the roster in .squad/team.md.
  • squad-triage.yml reacts to the squad label: the Lead agent routes the issue to a member (or to @copilot when it is a good fit), applies the squad:{member} label, and adds a default go:needs-research verdict.
  • squad-issue-assign.yml reacts to a squad:{member} label by acknowledging the assignment (and, for squad:copilot, handing the issue to the @copilot coding agent).
  • squad-label-enforce.yml enforces mutual exclusivity within the go:, type:, priority:, and release: label namespaces.

Feature and bug issue templates add the squad label by default, so the triage workflow routes them when filed. For issues filed outside those templates, add squad manually to request Squad routing. Triage is a lightweight operating norm rather than a hard SLA: handle P0 reports the same business day and route other new Squad issues within a few business days.

The assigned agent branches as squad/{issue-number}-{slug}, commits with a conventional-commit message that references the issue (Closes #{number}, including the Co-authored-by: Copilot trailer), pushes, and opens a PR with gh pr create against dev. The full lifecycle, spawn context, and merge commands live in .squad/templates/issue-lifecycle.md; the orchestration rules live in .github/agents/squad.agent.md. Agent PRs are gated by the same CI as everyone else's.

Branches vs. worktrees. A locally run Squad agent (including a Copilot CLI agent) must use one dedicated git worktree per issue under .worktrees/, reusing it when collaborating on that issue. This prevents concurrent local agents from sharing a working tree or index. A hosted agent (such as GitHub's @copilot coding agent) uses the platform's isolated branch and environment instead — no local worktree applies. Human contributors may use a worktree as a convenience, but a plain short-lived branch in the main checkout is supported. The creation, reuse, dependency, team-root, and cleanup mechanics live in .squad/templates/worktree-reference.md; do not duplicate them here.

New feature workflow. Proposing a new feature or capability (agent or human):

  1. Open a GitHub issue first — no un-tracked feature work. Describe the user story/problem it solves.
  2. Add or update a spec under specs/ before or alongside the code. Specs are area-grouped, one file per user story, each linking its GitHub issue number — follow the existing files' format exactly (title; **Issue:** + **Area:** header; ## User story, ## Context / problem, ## Scope (In/Out), ## Acceptance criteria, ## Notable edge cases), and add the story to the matching area section of specs/README.md.
  3. Then follow the normal issue → branch → PR → review → merge lifecycle above, including updating user-facing docs (docs/guide/, README.md) in the same change as required by the Documenting your work guidance below.

New user-facing functionality that lands without a corresponding specs/ entry should be flagged in review. This is a convention, not an enforced gate: the docs-drift.yml nudge watches API/workflow/blueprint/ MCP code paths against docs/** only — it does not cover specs/, so nothing mechanically blocks a spec-less feature PR. Reviewers are responsible for catching it.

Bug-fix workflow. Fixing a bug (agent or human):

  1. Open (or reuse) a GitHub issue describing the bug: repro steps and expected vs. actual behavior. Don't file untracked fixes for anything beyond a trivial/obvious one-liner (typo, broken link, obviously-wrong constant) — anything with behavioral nuance or a risk of regression gets an issue.
  2. Reference the issue in the commit/PR with Closes #N (see Commit messages) so it auto-closes on merge.
  3. Include a regression test that fails before the fix and passes after, whenever the bug is in code with a test suite — this is the existing "add or update tests for any behavior change" rule applied to fixes, and it is what QA (Smith's charter) means by preventing regressions. A fix with no test should say why one isn't feasible.
  4. After merge, the same lifecycle applies: CI-gated, and the issue closes automatically via Closes #N (or close it manually if the fix only partially addresses the issue).

Peer review and the reviewer-rejection protocol. Changes requested is ordinary review feedback: the original author may revise the same PR normally, with no lockout. Lockout occurs only when a Reviewer (Tester, Code Reviewer, Lead, or Rai for Responsible AI) explicitly declares Rejected / independent rewrite required — for example, with the exact PR comment marker REJECTED — requires independent rewrite. Then the original author is locked out of the next revision, a different agent must produce it, and the Reviewer chooses whether to reassign or escalate. The Coordinator enforces that rule mechanically. The rejection marker must remain on the PR so the author rotation is auditable on GitHub without Coordinator session history; a status:locked-out PR label may additionally be used when the repository creates it. The full rules are in the "Reviewer Rejection Protocol" section of squad.agent.md.

Rubber-ducking. Before a non-trivial or risky change ships, the Coordinator can invoke a rubber-duck review pass — a dedicated critical-feedback agent whose only job is to hunt for bugs, logic errors, and design flaws before anything is committed. It is invoked at the Coordinator's discretion for higher-risk work, not automatically on every change.

Auditable decisions. Meaningful design decisions are recorded to the decisions inbox (.squad/decisions/inbox/); Scribe periodically merges routine operational decisions into the canonical .squad/decisions.md. Cross-cutting architecture or technical decisions that should survive that ledger's compaction are promoted to numbered ADRs. This keeps agent-driven changes traceable back to the reasoning behind them.

Documenting your work. Docs are part of the definition of done, not a follow-up. When a change affects user-facing behavior — npm scripts, CLI flags, setup/deploy steps, API routes or config, the OAuth flow — update the relevant docs in the same change: the VitePress guide under docs/guide/ (the source of truth for https://sabbour.me/agentweaver, built with npm run docs:build / docs:dev / docs:preview) and/or README.md (quick overview and links out). CONTRIBUTING.md and RELEASING.md are the process docs; add inline code comments only where they genuinely clarify intent (this repo's style avoids over-commenting). Some reference pages under docs/reference/ are generated by node scripts/gen-docs.mjs — regenerate and commit them, never hand-edit. The docs-drift.yml workflow backs this up: it hard-fails a PR when a committed generated reference (e.g. docs/reference/mcp-tools.md) is stale, and posts a non-blocking reminder when code in doc-relevant paths (API endpoints, workflows, blueprints, MCP tools) changes without any docs/** update.

Do not hand-edit generated CHANGELOG.md release sections. Add or correct the source changeset instead; the matching GitHub Release notes project that same section (see Releasing). Finally, keep decision records and docs distinct: a .squad/decisions/inbox/ entry captures why a choice was made (an internal audit trail) and never substitutes for updating docs/guide/ / README.md, which tell users how to use the feature.

Questions

Open a GitHub issue or start a discussion — we're happy to help you get oriented.