# Contributing Thanks for considering a contribution to `event4u/agent-config`. This file describes how to propose changes and what the package's conventions are. > **This project is currently single-maintainer (`matze4u`).** Contributions > are welcome; expect direct review and potentially slower response than > multi-maintainer projects. The process below describes the target workflow > as the contributor base grows. ## Status and scope The package is maintained by a small team at event4u: [@matze4u](https://github.com/matze4u) (lead), [@h3xa2](https://github.com/h3xa2), [@php-jesus](https://github.com/php-jesus), and [@phpjob](https://github.com/phpjob). External contributions — bug reports, typo fixes, skill improvements, documentation PRs, and new skills / rules — are welcome. Larger changes (breaking changes, new architectural layers, new tool adapters) should start with an [issue](https://github.com/event4u-app/agent-config/issues) or a [discussion](https://github.com/event4u-app/agent-config/discussions) so we can align on direction before code is written. If you are unsure whether a change is in scope: open a discussion first. ## Dependency direction — memory is file-first, with no external backend `agent-config` is the upstream, standalone package. Its memory layer is file-first and self-contained: curated YAML under `agents/memory//` plus agent-written JSONL under `agents/memory/intake/`, read by `retrieve()` in `src/scripts/memory_lookup.ts`. There is no external memory backend. [ADR-094](docs/decisions/ADR-094-agent-memory-layer-removal.md) removed the `@event4u/agent-memory` binding — the provider seam, the package detection, and the MCP routing that used it "when present" — and explicitly rejected leaving the integration surface inert: *"a dormant integration surface advertising a capability the suite no longer has is misleading residue."* Two rules survive that removal, and both are unconditional: - **Never hard-depend on a package that depends on `agent-config`.** That was the case for `agent-memory`, so the dependency could only ever run one way; reversing it would create a cycle and break installs in consumer projects that want the rule/skill layer alone. - **Adding any external memory backend is a Class-B decision** under [ADR-124](docs/decisions/ADR-124-embedded-engine-doctrine.md) § 5 — its own ADR, a named consumer demand signal, and a measured Class-A failure — never a dependency bump or a config entry. ## Quick start for contributors ```bash git clone https://github.com/event4u-app/agent-config.git cd agent-config task ci # full pipeline — must be green before PR ``` All checks must pass before a PR is merged: `sync-check`, `consistency`, `check-condensation`, `check-refs`, `check-portability`, `lint-skills`, `test`, `lint-readme`. ## Source of truth `src/` is the single source of truth — the authoring layer you edit: `src/skills/`, `src/rules/`, `src/agent-src/` (profiles, user-types, commands, contexts, personas, packs), `src/domains//`, `src/scripts/`. - **Edit** `src/` — verbose, authored content. - **Do not edit** `dist/agent-src/` directly — it is the **condensed output shipped in the package**, generated from `src/` by `task sync`. - **Do not edit** `.augment/` directly either — it is a generated projection of `dist/agent-src/` for Augment Code (gitignored in this repo; the installed runtime tree in a consumer project — a deployment role, not source privilege). - **Do not edit** `.claude/`, `.cursor/`, `.clinerules/`, `.windsurfrules`, `GEMINI.md` — generated projections for specific tools. **The build chain:** `src/` → `task sync` condenses → `dist/agent-src/` (the shipped, published artefact) → projected → `.augment/` + the per-tool trees. No tool is privileged; every projection is read-only output. The condensed output lands in `dist/agent-src/` (and, in a consumer install, in that project's `.augment/`). Authoritative: the `source-of-truth` rule. Helper commands: ```bash task sync # src/ → dist/agent-src/, then project → .augment/ task generate-tools # Regenerate .claude/, .cursor/, .clinerules/, .windsurfrules task test # pytest tests/ + installer integration tests task lint-skills # ./scripts-run src/scripts/skill_linter --all ``` ## Branches, commits, and PRs - Branch off `main`: `feat/short-description`, `fix/short-description`, `docs/short-description`, `chore/short-description`. - Use [Conventional Commits](https://www.conventionalcommits.org/) for commit messages. Scope is optional but helpful — common scopes are `skills`, `rules`, `commands`, `installer`, `ci`, `docs`. - Keep commits focused. Split mechanical renames from logic changes. - Open the PR against `main` with a short description of the change, motivation, and any notes for reviewers. - **Fixed a diagnosed, non-deterministic CI flake with a reusable root cause?** Add a `Flake-diagnosis:` trailer to the fixing commit's body naming the class in a few words, e.g. `Flake-diagnosis: gitignored-dir-watch-scope | parallel writes to an unrelated ignored dir trip a whole-worktree witness`. Scope it narrowly — a diagnosed, non-deterministic failure whose root cause could plausibly recur, never a deterministic bug or an unexplained one — so the convention stays a one-line note, not a registry. Search prior diagnoses with `git log --all --grep="Flake-diagnosis:"` before re-investigating a failure shape from scratch. ### Branch names, PR text and commit messages are a gated surface A branch name, a PR title, a PR body and a commit message are **public on push and effectively permanent once merged** — this repository does not rewrite trunk history, by a recorded decision, so a source name that lands there stays recoverable. None of them is a tracked file, so the tree-scanning gate (`check_no_external_sources`) cannot see any of them. Two checks cover that surface, and they run at different moments on purpose: - **`.github/workflows/pr-metadata-sources.yml`** — on `pull_request` (including `edited`, because a title changed after opening is a new disclosure). This is the backstop; by the time it runs the branch and commits are already on the remote. - **`src/scripts/hooks/prepush_metadata_sources.sh`** — a **pre-push** hook, and the only one of the two that runs while the metadata is still private. Opt-in, because this repository does not write to your `.git/hooks`: ```bash ln -s ../../src/scripts/hooks/prepush_metadata_sources.sh .git/hooks/pre-push ``` If you already have a pre-push hook, call it from there instead. It checks the branch name and the commit messages the push would add; the PR title and body do not exist locally, so those two stay CI-only — a local pass is not a full pass. Bypass with `AGENT_CONFIG_SKIP_METADATA_GATE=1`. Run it by hand at any time: ```bash ./scripts-run src/scripts/sweep_source_surfaces --gate-metadata \ --branch "$(git rev-parse --abbrev-ref HEAD)" \ --commit-range origin/main..HEAD ``` Use an opaque round identifier (`inbox-2026-08-h`, `round-a91f3c`, `S17`) where a name is needed. See [`source-confidentiality`](src/rules/source-confidentiality.md). ## Reviewability and the self-imposed gate This is a governance standard, so the maintainer holds themselves to the same gate as any contributor — the point is that even a solo merge goes **through** the review floor, not around it. - [`.github/CODEOWNERS`](.github/CODEOWNERS) routes review on the sensitive surfaces (kernel rules, router compiler, install, hooks, claims/proof generators, release pipeline, schemas). Branch protection is configured to require Code-Owner review on those paths (maintainer, in GitHub → Settings → Rules; source of truth: [`docs/contracts/branch-protection-policy.md`](docs/contracts/branch-protection-policy.md)). - **Honest bound:** an AI adversarial-review / security gate (when enabled) raises the floor and catches regressions and claim-drift, but it is **not** independent human review. The bus-factor is tracked truthfully in [`docs/succession.md`](docs/succession.md) — a bus-factor of 1 stated plainly beats one implied to be more. - A realistic second-reviewer on-ramp starts small (docs/claims or a single pack), never "co-maintain the kernel on day one". ## Adding or editing skills, rules, and commands - Skills and rules live in `src/skills/` and `src/rules/`; commands in `src/domains//` and `src/agent-src/commands/`. - Each skill must pass `task lint-skills` — frontmatter, structure, size budgets, and self-containment are enforced by the linter. - Size budgets are enforced by the `size-enforcement` rule and the linter. See [`size-enforcement`](dist/agent-src/rules/size-enforcement.md) for the current limits. - After editing content under `src/`, run `task sync` so `dist/agent-src/`, `.augment/`, and the tool-specific projections stay in sync. - Skills must remain project-agnostic. Nothing in `dist/agent-src/` may reference a specific consumer project, domain, or stack. The [`augment-portability`](dist/agent-src/rules/augment-portability.md) rule and `scripts/check_portability.py` enforce this in CI. - **External contributions get credited.** A merged community skill carries `provenance: community` in its frontmatter — the public catalog ([`docs/catalog.md`](docs/catalog.md)) renders it as `community` in the `source` column — and gets a row in [`CREDITS.md`](CREDITS.md) (§ Community contributions). Maintainers add both on merge; if you were missed, open an issue — attribution gaps are treated as bugs. - **Originality is CI-gated.** Every PR touching skills, personas, or domain commands runs `lint_originality --changed` (the anti-reskin gate, see the `originality-gate` job in `.github/workflows/skill-lint.yml`). Run it locally before pushing: `./scripts-run src/scripts/lint_originality --changed ` — or run the whole contributor precheck in one pass via the [`/contribution-precheck`](src/domains/meta/contribution-precheck/command.md) command (frontmatter schema + skill lint + originality, Node-only). ## Agent-assisted contribution workflow The package is **maintenance-assisted** and **learning-supported** — not self-maintaining or self-learning. Automation handles structural quality and proposes changes; humans own semantic judgment and approve every change. The distinction matters: drafting a skill is fast, but generalizing it correctly and writing a trigger phrase that actually fires for the right intents still takes human review. ### What the agent does for you Helper skills (`skill-writing`, `rule-writing`, `command-writing`, `description-assist`, `skill-reviewer`, `check-refs`, `agents-md-thin-root`) cover the mechanical work. You describe the intent — *"a skill that validates JWT tokens"*, *"a rule banning silent fallbacks"* — and the agent: - picks the right artefact type (skill vs. rule vs. command vs. guideline); - places it in the correct directory under `src/`; - writes frontmatter, trigger phrase (≤ 200 chars, multi-trigger coverage), required sections, and a size budget that fits the linter; - runs `task sync` so the condensed and projected copies stay aligned; - runs `task lint-skills` and `task ci` and proposes fixes for any failures. You do not need to memorise the directory layout, the kernel-rule budget, the size enforcement table, or the condensation-hash registry. You do need to know *what* you want, *why* it generalises beyond your project, and *when* the agent's draft is wrong. ### What CI catches and what it does not CI is structural, not semantic. `task ci` enforces frontmatter shape, description length, size budgets, condensation-hash drift, broken cross-references, missing language anchors, kernel-rule prominence, and roadmap-dashboard sync. It will reject a malformed skill before review. CI cannot tell you whether a trigger phrase actually fires when it should, whether an example generalises beyond your project, or whether a new rule contradicts an existing one in spirit. That judgment is yours. Plan on one or two CI feedback rounds on a first contribution — this is normal, not a sign the docs lied. ### Learning from real use — what it actually means The `learning-to-rule-or-skill` skill, the `capture-learnings` rule, and the `skill-improvement-pipeline` together let the agent **propose** rules or skills based on patterns it observed during a task. They never auto-promote anything. Every proposal lands as a draft for you to accept, reword, or discard. There is no cross-project training loop — each consumer project's learnings stay local unless you explicitly opt in to upstream contribution. Toggle: `pipelines.skill_improvement: true` in `.agent-settings.yml` (default true; set false for a silent agent). ### Optional upstream contribution A consumer project can opt in to feed learnings back as PRs against this repository. Set `project.upstream_repo: "event4u-app/agent-config"` in `.agent-settings.yml` (empty by default). With it set, the `upstream-contribute` skill and `upstream-proposal` rule become active and the agent may **propose** an upstream PR when a local learning generalises. The setting flip is the easy part. Before enabling it, confirm: - **You have the right to contribute.** Check your employment contract and your company's IP / open-source policy. Code and prose written during work hours often belong to your employer. - **No proprietary content leaks.** Generalisation must strip internal API names, customer identifiers, secrets, and project-specific domain language. The agent assists; you verify. - **MIT license is acceptable.** Contributions ship under the package's [MIT License](LICENSE). - **You review every PR before submission.** The skill's Iron Law is *never create a PR without explicit user consent*. Setting the config is not consent — clicking through the proposal is. ### Honest limits - The agent drafts; you decide. *"It compiled, ship it"* is not a review. - A structurally clean skill can still be semantically useless. Read the trigger phrase out loud and ask whether you would actually want the skill to fire on those words. - The package improves when contributors push back on the agent's drafts. Silent acceptance is the failure mode that produces noisy, redundant, or misfiring rules. ## Installer and Python tooling - Primary installer entry point: `scripts/install` (bash orchestrator). It chains `scripts/install.sh` (payload sync) and `scripts/install.ts` (bridge files; run via the bundled `dist/install/install.mjs`). - Each stage stays independently callable (`bash scripts/install.sh`, `npx tsx scripts/install.ts`) and has its own CLI. - The retired Python installer is gone (ADR-200); no Python runtime is required anywhere on the install path. - Add integration tests to `tests/test_install.sh` (payload sync) or `tests/test_install_orchestrator.sh` (orchestrator + wrappers), and Python unit tests under `tests/`. ### npm workspaces — scope discipline The root `package.json` declares no workspaces as of v4.0.0 (road-to-unified-setup § D1). The `@event4u/installer` workspace was removed when the TypeScript installer at `packages/core/installer/{src, tests}` was retired in favour of the unified setup engine at `src/install/`. Subsequent maintainers should avoid re-introducing workspaces unless a published sub-package genuinely needs root-level script orchestration — `packages/cloud/telemetry-worker` deliberately stays out because it is deployed via `wrangler`, not built through the root. Do **not** add `npm test --workspaces`, `npm run build --workspaces`, or monorepo-style fan-out scripts without architectural review. If the only need is "run an npm script inside a sub-package", prefer `npm run