feat(core): add AGENTS.md authoring/review tooling #80

Merged
Defame1297 merged 9 commits from feat/79-agentsmd-tooling into main 2026-07-23 17:46:09 +00:00
31 changed files with 1497 additions and 4 deletions

View File

@@ -8,7 +8,7 @@ description: Domain language and decisions for the global AI development config
## Principles ## Principles
### CLAUDE.md index model ### CLAUDE.md index model
`AGENTS.md` is the source of always-on universal rules (provider-agnostic). `providers/claude-code/CLAUDE.md` is a thin adapter: it imports `~/.agents/AGENTS.md` via `@~/.agents/AGENTS.md` and appends Claude Code-specific additions (`@import` for governance.md, content index). Deployed to `~/.claude/CLAUDE.md` via `install.sh`. Context size is kept minimal — only what is needed every session is loaded upfront; detailed content is pulled on demand. See ADR-0012. `AGENTS.md` is the source of always-on universal rules (provider-agnostic). `providers/claude-code/CLAUDE.md` is a thin adapter: it imports `~/.agents/AGENTS.md` via `@~/.agents/AGENTS.md` and appends Claude Code-specific additions (`@import` for governance.md, content index). Deployed to `~/.claude/CLAUDE.md` via `install.sh`. Context size is kept minimal — only what is needed every session is loaded upfront; detailed content is pulled on demand. See ADR-0003.
### Instruction file format ### Instruction file format
`core/instructions/<topic>.md` files are plain markdown — no frontmatter, no schema. The agent decides when to read each file based on task context and the content index label in `providers/claude-code/CLAUDE.md`. Frontmatter is deferred until there is evidence that agents are loading the wrong files in practice. `core/instructions/<topic>.md` files are plain markdown — no frontmatter, no schema. The agent decides when to read each file based on task context and the content index label in `providers/claude-code/CLAUDE.md`. Frontmatter is deferred until there is evidence that agents are loading the wrong files in practice.
@@ -46,7 +46,7 @@ The provider-agnostic always-on instruction entry point. Two files:
- **Repo-level `AGENTS.md`** — instructions for agents working inside this repo (structure, key rules); imported by repo `CLAUDE.md` via `@AGENTS.md`. - **Repo-level `AGENTS.md`** — instructions for agents working inside this repo (structure, key rules); imported by repo `CLAUDE.md` via `@AGENTS.md`.
- **Global `core/AGENTS.md`** — Communication and Behavior rules that apply across all projects; deployed to `~/.agents/AGENTS.md`; imported by `~/.claude/CLAUDE.md` via `@~/.agents/AGENTS.md`. - **Global `core/AGENTS.md`** — Communication and Behavior rules that apply across all projects; deployed to `~/.agents/AGENTS.md`; imported by `~/.claude/CLAUDE.md` via `@~/.agents/AGENTS.md`.
Contains always-on rules in plain markdown with no provider-specific syntax (no `@import`). Provider-specific files (`CLAUDE.md`) are thin adapters that import the relevant `AGENTS.md` and add only Claude Code-specific syntax. This pattern means a single source of truth can serve multiple providers without duplication. See ADR-0012. Contains always-on rules in plain markdown with no provider-specific syntax (no `@import`). Provider-specific files (`CLAUDE.md`) are thin adapters that import the relevant `AGENTS.md` and add only Claude Code-specific syntax. This pattern means a single source of truth can serve multiple providers without duplication. See ADR-0003.
### Skill composition ### Skill composition
A skill calling another skill by name to delegate a sub-task. The calling skill focuses on the orchestration decision ("when to do X"); the called skill owns the mechanics ("how to do X"). Established compositions: `grill-me` calls `write-adr` when a decision crystallises; `implement-feature` calls `tdd` as its implementation methodology; `forge` calls `grill-with-docs` to refine intent, classifies the target artifact type (skill / agent / plugin / marketplace entry), then routes to the matching `*-author` skill — which owns its own create/improve logic and, where applicable, its own inline audit closeout (`skill-author` runs `/skill-audit`, `agent-author` runs `kyberforge:agent-audit`, both in the same context as the authoring work). `forge` additionally runs its own independent recheck after a skill/agent route finishes: a clean-context subagent (not forked, no inherited context) re-runs the same audit skill against the finished artifact, as a distinct verification layer from the author skill's inline audit — the two can share blind spots since the inline audit runs in the same context as the work it checks. If the clean audit surfaces any unresolved finding, `forge` loops — re-invoke the author skill to resolve it, re-run the clean audit — until the clean audit comes back with nothing unresolved; only then is the route done. `plugin-author` and `marketplace-author` have no audit counterpart and get no recheck; their terminal check is `claude plugin validate`. A skill calling another skill by name to delegate a sub-task. The calling skill focuses on the orchestration decision ("when to do X"); the called skill owns the mechanics ("how to do X"). Established compositions: `grill-me` calls `write-adr` when a decision crystallises; `implement-feature` calls `tdd` as its implementation methodology; `forge` calls `grill-with-docs` to refine intent, classifies the target artifact type (skill / agent / plugin / marketplace entry), then routes to the matching `*-author` skill — which owns its own create/improve logic and, where applicable, its own inline audit closeout (`skill-author` runs `/skill-audit`, `agent-author` runs `kyberforge:agent-audit`, both in the same context as the authoring work). `forge` additionally runs its own independent recheck after a skill/agent route finishes: a clean-context subagent (not forked, no inherited context) re-runs the same audit skill against the finished artifact, as a distinct verification layer from the author skill's inline audit — the two can share blind spots since the inline audit runs in the same context as the work it checks. If the clean audit surfaces any unresolved finding, `forge` loops — re-invoke the author skill to resolve it, re-run the clean audit — until the clean audit comes back with nothing unresolved; only then is the route done. `plugin-author` and `marketplace-author` have no audit counterpart and get no recheck; their terminal check is `claude plugin validate`.
@@ -60,5 +60,11 @@ The three-stage traceability record linking a skill back to its research inputs:
### Bidirectional reference principle ### Bidirectional reference principle
Files that reference other files should declare those references explicitly. The referencing file carries the forward reference (e.g. content index in `CLAUDE.md`, `references:` in frontmatter). The referenced file carries a `when:` field describing when it is loaded. Both sides should agree — divergence signals staleness. The reverse map ("what files reference this file?") is derived by a reference scanner script, not maintained manually. This principle applies to instruction files, skills, and workflow documents. Files that reference other files should declare those references explicitly. The referencing file carries the forward reference (e.g. content index in `CLAUDE.md`, `references:` in frontmatter). The referenced file carries a `when:` field describing when it is loaded. Both sides should agree — divergence signals staleness. The reverse map ("what files reference this file?") is derived by a reference scanner script, not maintained manually. This principle applies to instruction files, skills, and workflow documents.
### agentsmd-author / agentsmd-audit
A skill pair in the `core` plugin for writing, updating, and reviewing a target repo's `AGENTS.md` file(s) (the generic open-standard file — see the `AGENTS.md` entry above — not this repo's own). `agentsmd-author` creates/updates AGENTS.md content, supports nested monorepo placement (per the standard's nearest-file-wins precedence), and closes out by invoking `agentsmd-audit` inline. `agentsmd-audit` runs a single combined pass checking three mandatory baselines: secrets/credentials (governance.md hard prohibition — AGENTS.md is committed content), structural completeness (common-sections checklist from the agents.md spec), and accuracy/drift (do referenced commands and paths actually resolve against the repo). `agentsmd-audit` never inspects provider adapter files (see `provider-adapter-author`) — its scope is AGENTS.md content only. Chosen over folding this into `kyberforge` because kyberforge's scope is meta-tooling for the holocron marketplace itself, not generic target-repo documentation; `core` is the intended home for cross-cutting, repo-agnostic utility skills.
### provider-adapter-author
A companion skill (`core` plugin) that detects a target repo's provider-specific instruction file (`CLAUDE.md`, `.cursor/rules/*.mdc`, `copilot-instructions.md`, etc.) and, where it duplicates content AGENTS.md should own, converts it into a thin adapter that imports AGENTS.md — mirroring this repo's own ADR-0002/ADR-0003 two-tier adapter pattern. Self-validates via its own bundled deterministic script (`scripts/validate-adapter.sh`: checks for an import reference, no duplicated headings, size threshold) rather than a separate paired audit skill — the check is mechanical, so a script suffices per governance.md's "prefer deterministic code for repeatable tasks." `agentsmd-author` calls this skill via skill composition when it detects an existing provider file with overlapping content.
### LESSONS.md ### LESSONS.md
Long-loop feedback log for patterns observed across sessions. Three or more entries on the same pattern graduate to the relevant standing file (e.g. a coding convention, a governance rule). Updated by the session-handoff skill or directly by the human. Lives at the repo root. Long-loop feedback log for patterns observed across sessions. Three or more entries on the same pattern graduate to the relevant standing file (e.g. a coding convention, a governance rule). Updated by the session-handoff skill or directly by the human. Lives at the repo root.

View File

@@ -0,0 +1,16 @@
# AGENTS.md tooling lives in `core`, split into three skills
`kyberforge` is scoped to meta-tooling for building and maintaining the holocron marketplace itself (skills, agents, plugins, marketplace entries) — not to generic capabilities for an arbitrary target repo. Authoring and reviewing a target repo's `AGENTS.md` file is repo-agnostic documentation tooling, closer in kind to `bin:write-docs` or `bin:init` than to `skill-author`/`plugin-author`. Research for this topic was initially placed under `plugins/kyberforge/docs/research/docs/agentsmd/` but has moved to `plugins/core/docs/research/docs/agentsmd/` to keep the provenance chain consistent with the plugin the resulting skills live in.
## Decision
Three skills in the `core` plugin (`core`'s first active skills):
- **`agentsmd-author`** — creates/updates a target repo's `AGENTS.md`, including nested monorepo placement (nearest-file-wins). Closes out by invoking `agentsmd-audit` inline, mirroring the `skill-author`/`skill-audit` pattern. When it detects an existing provider-specific file (`CLAUDE.md`, etc.) with content that duplicates what AGENTS.md should own, it calls `provider-adapter-author` via skill composition.
- **`agentsmd-audit`** — a single combined pass checking three mandatory baselines against `AGENTS.md` only: secrets/credentials (governance.md hard prohibition), structural completeness (common-sections checklist from the agents.md spec), and accuracy/drift (do referenced commands/paths resolve against the repo). Never inspects provider adapter files.
- **`provider-adapter-author`** — detects and converts a provider-specific instruction file into a thin adapter that imports `AGENTS.md` (mirroring this repo's own two-tier `CLAUDE.md` pattern). Self-validates via its own bundled deterministic script (`scripts/validate-adapter.sh`) rather than a separate paired audit skill, since the check (import present, no duplicated headings, size threshold) is mechanical.
## Consequences
- `core`'s plugin.json/README will list real skills for the first time.
- `plugins/kyberforge/docs/research/docs/agentsmd/` moves to `plugins/core/docs/research/docs/agentsmd/` before authoring begins.

View File

@@ -14,5 +14,5 @@
], ],
"license": "MIT", "license": "MIT",
"name": "core", "name": "core",
"version": "1.0.0" "version": "1.1.0"
} }

47
plugins/core/README.md Normal file
View File

@@ -0,0 +1,47 @@
# core
Cross-cutting utility skills for everyday AI-assisted coding — triage, diagnosis, architecture review, and session navigation.
## Install
**Claude Code:**
```bash
claude plugin marketplace add <owner>/<repo>
claude plugin install core@<marketplace-name>
```
**GitHub Copilot CLI:**
```bash
copilot plugin marketplace add <owner>/<repo>
copilot plugin install core
```
**Local (development):**
```bash
# Claude Code
claude --plugin-dir ./plugins/core
# GitHub Copilot CLI
copilot plugin install ./plugins/core
```
## Contents
| Component | Path | Description |
|---|---|---|
| Skills | `skills/` | Slash commands available after install |
## Skills
| Skill | Description |
|---|---|
| `agentsmd-author` | Create or update a repo's AGENTS.md by exploring real build/test/lint conventions; supports nested monorepo placement and hands off to agentsmd-audit and provider-adapter-author |
| `agentsmd-audit` | Audit a repo's AGENTS.md for embedded secrets, structural completeness, and drift; produces a findings report |
| `provider-adapter-author` | Convert a provider-specific instruction file (CLAUDE.md, `.cursor/rules/*.mdc`, copilot-instructions.md, etc.) into a thin adapter that defers to AGENTS.md |
## Author
Defame1297

View File

@@ -15,5 +15,8 @@
"license": "MIT", "license": "MIT",
"mcpServers": ".mcp.json", "mcpServers": ".mcp.json",
"name": "core", "name": "core",
"version": "1.0.0" "skills": [
"skills/"
],
"version": "1.1.0"
} }

View File

@@ -0,0 +1,30 @@
# agentsmd-audit
Audit a target repo's AGENTS.md file(s) for embedded secrets, structural completeness, and drift.
## What it does
Runs a single combined pass across every AGENTS.md file in a repo (root and any nested monorepo files): flags embedded secrets/credentials, checks structure against the agents.md common-sections checklist, and resolves referenced commands/paths against the actual repo to catch stale documentation. Outputs a compact findings report — findings only, grouped by dimension, each with Why and Fix. Never inspects provider-specific adapter files (CLAUDE.md, etc.) and never writes or fixes anything.
## Usage
```
/agentsmd-audit
```
Provide the path to the repo root to audit when invoking.
## Files
| File | Purpose |
|------|---------|
| `SKILL.md` | Skill instructions for agents |
| `scripts/validate-secrets.sh` | Scans AGENTS.md files for embedded secrets, API keys, tokens, connection strings |
| `scripts/validate-structure.sh` | Checks for empty/placeholder content, common-sections checklist, nested-vs-root duplication |
| `scripts/validate-drift.sh` | Resolves referenced npm/make commands and file paths against the repo |
| `references/sources.md` | Provenance record — sources that informed this skill and which files each contributed to |
| `scripts/README.md` | Directory documentation for `scripts/` |
| `tests/README.md` | Bats test dependency and run instructions |
| `tests/validate-secrets.bats` | Bats test suite for `scripts/validate-secrets.sh` |
| `tests/validate-structure.bats` | Bats test suite for `scripts/validate-structure.sh` |
| `tests/validate-drift.bats` | Bats test suite for `scripts/validate-drift.sh` |

View File

@@ -0,0 +1,68 @@
---
name: agentsmd-audit
description: >
Use when the user wants to review a repo's AGENTS.md file, says "audit this
AGENTS.md", "check my AGENTS.md", "is this AGENTS.md any good", or wants to
know if AGENTS.md is safe to commit — even if they don't use the word
"audit". Also invoke proactively after agentsmd-author creates or updates
AGENTS.md, or after a hand-edit made outside agentsmd-author. Audits a
target repo's AGENTS.md file(s) — root and any nested monorepo files — for
embedded secrets/credentials, structural completeness against the
agents.md common-sections checklist, and drift (referenced commands or
paths that no longer resolve against the repo). Produces a compact
findings report (findings only, no PASS noise) with Why and Fix per
finding. Do not use to audit CLAUDE.md, .cursor/rules, or other
provider-specific adapter files — that's provider-adapter-author's
self-contained concern. Do not use to fix or write AGENTS.md content — use
agentsmd-author instead.
allowed-tools: Bash Read
metadata:
category: docs
source_keys:
- agents-md-official
- context7-websites-agents-md
- context7-agentsmd-agents-md
- governance-secrets-hard-prohibition
version: "0.1.1"
---
## Gotchas
- Always run all three checks — this skill does a single combined pass, not staged/gated passes. Don't skip structure or drift checks just because a secrets FAIL was found.
- Never inspect or mention provider-specific adapter files (`CLAUDE.md`, `.cursor/rules/*.mdc`, `copilot-instructions.md`, etc.) — that's out of scope. If one exists and duplicates AGENTS.md content, that's `provider-adapter-author`'s concern, not this skill's.
- A missing common section (e.g. no "Security" heading) is informational, not a failure — not every repo needs every section from the checklist. Only flag a FAIL when the file is empty, entirely unfilled placeholder text, or contains a real embedded secret/stale reference.
- Gather findings internally; don't narrate PASS/FAIL per check as you go — surface them only in the final report.
## Step 1 — Run the validators
```bash
bash scripts/validate-secrets.sh <repo-root>
bash scripts/validate-structure.sh <repo-root>
bash scripts/validate-drift.sh <repo-root>
```
Each script walks the repo for every `AGENTS.md` file (root and nested, excluding `.git`, `node_modules`, `vendor`, and similar) and prints `FAIL`/`INFO`/`SUGGESTION` lines with `Why`/`Fix` (or `Note`) per finding. A nonzero exit means at least one FAIL was found in that dimension. If a script cannot execute (`python3` unavailable, Bash denied), fall back to manual review: scan for real-looking credentials, check common sections are present, and spot-check a few referenced commands/paths by hand.
## Step 2 — Report
Open with a coverage line:
```text
Checked: secrets · structure · drift
```
Then output only findings that were found, in this order within a repo: `### Secrets`, `### Structure`, `### Drift`. Omit a dimension heading entirely if it produced nothing — its absence confirms it passed. Report each finding verbatim as emitted by the scripts (they already carry file:line, Why/Fix or Note).
Close with a result block:
```text
## Result
PASS
PASS · P info
PASS (N suggestions) · P info
FAIL (N fails)
FAIL (N fails) · P info
```
INFO and SUGGESTION findings are observational — they never flip PASS to FAIL. Do not fix anything — this skill reports and proposes only. Point the user to `agentsmd-author` to apply fixes.

View File

@@ -0,0 +1,33 @@
# Sources
## agents-md-official
- **URL:** https://agents.md/
- **Description:** Official agents.md website — format spec, common-sections checklist, precedence rules (nearest-file-wins, no merge across files), monorepo nesting patterns
- **Research doc:** plugins/core/docs/research/docs/agentsmd/sources.md
- **Contributing files:** SKILL.md
- **Status:** `extracted`
## context7-websites-agents-md
- **URL:** context7:/websites/agents_md
- **Description:** Context7 index of the official agents.md website — overview, governance, cross-tool compatibility, configuration examples
- **Research doc:** plugins/core/docs/research/docs/agentsmd/sources.md
- **Contributing files:** SKILL.md
- **Status:** `extracted`
## context7-agentsmd-agents-md
- **URL:** context7:/agentsmd/agents.md
- **Description:** Context7 index of the agentsmd/agents.md repository — format spec, nested monorepo patterns, file structure examples
- **Research doc:** plugins/core/docs/research/docs/agentsmd/sources.md
- **Contributing files:** SKILL.md
- **Status:** `extracted`
## governance-secrets-hard-prohibition
- **URL:** (org convention — not a plugin research corpus entry)
- **Description:** Hard prohibition on placing secrets, API keys, tokens, or credentials in code, config, prompts, or any output. Grounds the secrets/credentials check in `scripts/validate-secrets.sh` and Step 1 of SKILL.md — AGENTS.md is committed content, so an embedded real secret is a hard-prohibition violation, not a style nit.
- **Research doc:** core/instructions/governance.md (org convention file, not a plugin research corpus entry; content is inlined here since plugins must be self-contained and this file may not exist wherever the plugin is installed)
- **Contributing files:** SKILL.md
- **Status:** `extracted`

View File

@@ -0,0 +1,11 @@
# scripts/
Deterministic validators this skill shells out to instead of relying on LLM judgment for mechanical checks.
| File | Purpose |
|------|---------|
| `validate-secrets.sh` | Scans every AGENTS.md file (root + nested) for embedded secrets, API keys, tokens, and connection strings |
| `validate-structure.sh` | Checks for empty/placeholder content, the common-sections checklist, and nested-vs-root duplication |
| `validate-drift.sh` | Resolves referenced npm/make commands and file paths against the actual repo state |
All three take a single `<repo-root>` argument, print `FAIL`/`INFO`/`SUGGESTION` findings to stdout, and exit non-zero only on FAIL.

View File

@@ -0,0 +1,137 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<EOF
Usage: validate-drift.sh <repo-root>
Check every AGENTS.md file in a repo (root and nested) for drift: package
manager scripts and file paths referenced in the text that no longer exist
in the repo. Catches the failure mode that matters most in practice — an
agent running a documented command that was renamed or deleted.
Arguments:
repo-root Path to the repository root to scan.
Exit codes:
0 No FAIL findings (INFO may still be printed, e.g. no package.json found)
1 One or more FAIL findings
EOF
}
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
usage
exit 0
fi
if [[ $# -lt 1 ]]; then
echo "Error: repo-root is required." >&2
echo "" >&2
usage >&2
exit 1
fi
python3 -u - "$1" <<'PYTHON'
import sys
import os
import re
import json
repo_root = os.path.abspath(sys.argv[1])
if not os.path.isdir(repo_root):
print(f"Error: '{repo_root}' is not a directory.", file=sys.stderr)
sys.exit(1)
EXCLUDE_DIRS = {".git", "node_modules", "vendor", ".venv", "venv", "dist", "build"}
def find_agents_md(root):
results = []
for dirpath, dirnames, filenames in os.walk(root):
dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS and not d.startswith(".")]
for fname in filenames:
if fname == "AGENTS.md":
results.append(os.path.join(dirpath, fname))
return sorted(results)
def load_package_scripts(root):
pkg_path = os.path.join(root, "package.json")
if not os.path.isfile(pkg_path):
return None
try:
with open(pkg_path, encoding="utf-8") as f:
data = json.load(f)
except (json.JSONDecodeError, OSError):
return None
return set(data.get("scripts", {}).keys())
def load_make_targets(root):
make_path = os.path.join(root, "Makefile")
if not os.path.isfile(make_path):
return None
with open(make_path, encoding="utf-8", errors="replace") as f:
content = f.read()
return set(re.findall(r'(?m)^([a-zA-Z0-9_-]+)\s*:(?!=)', content))
NPM_RUN_RE = re.compile(r'\b(?:npm|pnpm|yarn)\s+run\s+([a-zA-Z0-9:_-]+)')
MAKE_RE = re.compile(r'\bmake\s+([a-zA-Z0-9_-]+)')
# Backticked relative file paths, e.g. `scripts/bootstrap.sh`, `src/index.ts`.
# Requires a path separator and file extension to avoid matching bare commands/words.
PATH_RE = re.compile(r'`([A-Za-z0-9_.\-]+(?:/[A-Za-z0-9_.\-]+)+\.[A-Za-z0-9]+)`')
has_fail = False
package_scripts = load_package_scripts(repo_root)
make_targets = load_make_targets(repo_root)
for fpath in find_agents_md(repo_root):
rel = os.path.relpath(fpath, repo_root)
with open(fpath, encoding="utf-8", errors="replace") as f:
content = f.read()
for m in NPM_RUN_RE.finditer(content):
script_name = m.group(1)
if package_scripts is None:
print(f"INFO Cannot verify referenced script '{script_name}' — {rel}")
print(f" Note: AGENTS.md references an npm/pnpm/yarn script, but no package.json was found at the repo root to check it against.")
print()
elif script_name not in package_scripts:
has_fail = True
print(f"FAIL Referenced script '{script_name}' not found in package.json — {rel}")
print(f" Why: AGENTS.md tells agents to run '{script_name}', but package.json has no matching \"scripts\" entry — the command will fail.")
print(f" Fix: Update AGENTS.md to reference an existing script, or add '{script_name}' to package.json's scripts.")
print()
for m in MAKE_RE.finditer(content):
target_name = m.group(1)
if make_targets is None:
print(f"INFO Cannot verify referenced make target '{target_name}' — {rel}")
print(f" Note: AGENTS.md references a make target, but no Makefile was found at the repo root to check it against.")
print()
elif target_name not in make_targets:
has_fail = True
print(f"FAIL Referenced make target '{target_name}' not found in Makefile — {rel}")
print(f" Why: AGENTS.md tells agents to run 'make {target_name}', but the Makefile has no matching target — the command will fail.")
print(f" Fix: Update AGENTS.md to reference an existing target, or add '{target_name}' to the Makefile.")
print()
file_dir = os.path.dirname(fpath)
for m in PATH_RE.finditer(content):
candidate = m.group(1)
resolved = (
os.path.isfile(os.path.join(repo_root, candidate))
or os.path.isfile(os.path.join(file_dir, candidate))
or os.path.isdir(os.path.join(repo_root, candidate))
or os.path.isdir(os.path.join(file_dir, candidate))
)
if not resolved:
has_fail = True
print(f"FAIL Referenced path '{candidate}' does not exist — {rel}")
print(f" Why: AGENTS.md points agents to '{candidate}', but it isn't present in the repo (checked relative to repo root and to the AGENTS.md's own directory).")
print(f" Fix: Update AGENTS.md to reference the correct path, or restore/create '{candidate}'.")
print()
if has_fail:
sys.exit(1)
sys.exit(0)
PYTHON

View File

@@ -0,0 +1,120 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<EOF
Usage: validate-secrets.sh <repo-root>
Scan every AGENTS.md file in a repo (root and nested) for embedded secrets,
API keys, tokens, or connection strings. AGENTS.md is committed content —
real credentials in it are a hard-prohibition violation, not a style nit.
Placeholders (<your-key>, \$ENV_VAR, YOUR_TOKEN_HERE, example.com, etc.) are
not flagged.
Arguments:
repo-root Path to the repository root to scan.
Exit codes:
0 No findings
1 One or more FAIL findings
EOF
}
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
usage
exit 0
fi
if [[ $# -lt 1 ]]; then
echo "Error: repo-root is required." >&2
echo "" >&2
usage >&2
exit 1
fi
python3 -u - "$1" <<'PYTHON'
import sys
import os
import re
repo_root = os.path.abspath(sys.argv[1])
if not os.path.isdir(repo_root):
print(f"Error: '{repo_root}' is not a directory.", file=sys.stderr)
sys.exit(1)
EXCLUDE_DIRS = {".git", "node_modules", "vendor", ".venv", "venv", "dist", "build"}
def find_agents_md(root):
results = []
for dirpath, dirnames, filenames in os.walk(root):
dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS and not d.startswith(".")]
for fname in filenames:
if fname == "AGENTS.md":
results.append(os.path.join(dirpath, fname))
return sorted(results)
PLACEHOLDER_RE = re.compile(
r'(?i)(your[_-]|my[_-]|example|xxx+|placeholder|changeme|<[^>]+>|\$\{|\$[A-Z_][A-Z0-9_]*|\.\.\.|redacted)'
)
PATTERNS = [
("AWS access key ID", re.compile(r'AKIA[0-9A-Z]{16}')),
("Private key block", re.compile(r'-----BEGIN [A-Z ]*PRIVATE KEY-----')),
("GitHub token", re.compile(r'gh[pousr]_[A-Za-z0-9]{36,}')),
("Slack token", re.compile(r'xox[baprs]-[A-Za-z0-9-]{10,}')),
("GitLab token", re.compile(r'glpat-[A-Za-z0-9_-]{20,}')),
("Generic API-style secret token", re.compile(r'\bsk-[A-Za-z0-9]{20,}\b')),
(
"Credential-bearing connection string",
re.compile(r'[a-zA-Z][a-zA-Z0-9+.-]*://[^:@/\s]+:[^@/\s]+@[^\s\'"]+'),
),
(
"Assigned secret/password/token literal",
re.compile(
r'(?i)\b(api[_-]?key|secret|token|password|passwd|pwd|access[_-]?key)\b'
r'\s*[:=]\s*[\'"]?([A-Za-z0-9+/_.\-]{12,})[\'"]?'
),
),
]
findings = []
def emit_fail(desc, fpath, lineno, why, fix):
findings.append((desc, fpath, lineno, why, fix))
for fpath in find_agents_md(repo_root):
rel = os.path.relpath(fpath, repo_root)
with open(fpath, encoding="utf-8", errors="replace") as f:
lines = f.readlines()
for i, line in enumerate(lines, start=1):
if PLACEHOLDER_RE.search(line):
continue
for label, pattern in PATTERNS:
m = pattern.search(line)
if not m:
continue
# Re-check placeholder allowlist against just the matched value, in case
# the placeholder marker sits outside the regex's own match span.
value = m.group(0)
if PLACEHOLDER_RE.search(value):
continue
emit_fail(
f"Possible {label}",
f"{rel}:{i}",
i,
"AGENTS.md is committed content; this line matches a real-looking credential pattern rather than a placeholder.",
"Remove the embedded credential and replace it with an environment variable reference or placeholder (e.g. $API_KEY, <your-token>).",
)
break
if not findings:
sys.exit(0)
for desc, fpath, _lineno, why, fix in findings:
print(f"FAIL {desc} — {fpath}")
print(f" Why: {why}")
print(f" Fix: {fix}")
print()
sys.exit(1)
PYTHON

View File

@@ -0,0 +1,118 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<EOF
Usage: validate-structure.sh <repo-root>
Check every AGENTS.md file in a repo (root and nested) for structural
completeness against the agents.md spec's common-sections checklist
(setup/build, code style, testing, security, commit/PR conventions).
Missing individual sections are informational (not every repo needs every
section) — only an empty or entirely unfilled file is a hard failure.
Arguments:
repo-root Path to the repository root to scan.
Exit codes:
0 No FAIL findings (INFO/SUGGESTION may still be printed)
1 One or more FAIL findings
EOF
}
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
usage
exit 0
fi
if [[ $# -lt 1 ]]; then
echo "Error: repo-root is required." >&2
echo "" >&2
usage >&2
exit 1
fi
python3 -u - "$1" <<'PYTHON'
import sys
import os
import re
PLACEHOLDER_RE = re.compile(r'(?i)FILL IN:|TODO:\s*write|lorem ipsum')
COMMON_SECTIONS = [
("setup/build commands", re.compile(r'(?im)^#{1,3}\s*(setup|install|build|getting started)')),
("code style", re.compile(r'(?im)^#{1,3}\s*(code style|style guide|conventions)')),
("testing instructions", re.compile(r'(?im)^#{1,3}\s*(test|testing)')),
("security considerations", re.compile(r'(?im)^#{1,3}\s*security')),
("commit/PR conventions", re.compile(r'(?im)^#{1,3}\s*(commit|pr|pull request)')),
]
repo_root = os.path.abspath(sys.argv[1])
if not os.path.isdir(repo_root):
print(f"Error: '{repo_root}' is not a directory.", file=sys.stderr)
sys.exit(1)
EXCLUDE_DIRS = {".git", "node_modules", "vendor", ".venv", "venv", "dist", "build"}
def find_agents_md(root):
results = []
for dirpath, dirnames, filenames in os.walk(root):
dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS and not d.startswith(".")]
for fname in filenames:
if fname == "AGENTS.md":
results.append(os.path.join(dirpath, fname))
return sorted(results)
has_fail = False
file_contents = {} # rel path -> content, for the duplication pass below
for fpath in find_agents_md(repo_root):
rel = os.path.relpath(fpath, repo_root)
with open(fpath, encoding="utf-8", errors="replace") as f:
content = f.read()
file_contents[rel] = content
if not content.strip():
has_fail = True
print(f"FAIL AGENTS.md is empty — {rel}")
print(" Why: An empty file provides no instructions and gives agents nothing to act on.")
print(" Fix: Add at least a project overview and setup/test commands, per the agents.md common-sections checklist.")
print()
continue
if PLACEHOLDER_RE.search(content):
has_fail = True
print(f"FAIL Unfilled placeholder content — {rel}")
print(" Why: A 'FILL IN:' or template stub left in place means the file has no repo-specific instructions yet.")
print(" Fix: Replace the placeholder with real, repo-specific content.")
print()
continue
for label, pattern in COMMON_SECTIONS:
if not pattern.search(content):
print(f"INFO No {label} section — {rel}")
print(f" Note: The agents.md common-sections checklist includes {label}; not every repo needs every section, but confirm this omission is deliberate.")
print()
# --- Nested-vs-root duplication check ---
root_content = file_contents.get("AGENTS.md")
if root_content:
root_lines = {ln.strip() for ln in root_content.splitlines() if ln.strip()}
for rel, content in file_contents.items():
if rel == "AGENTS.md":
continue
nested_lines = [ln.strip() for ln in content.splitlines() if ln.strip()]
if not nested_lines:
continue
overlap = sum(1 for ln in nested_lines if ln in root_lines)
ratio = overlap / len(nested_lines)
if ratio >= 0.7:
print(f"SUGGESTION Nested AGENTS.md largely duplicates the root file — {rel}")
print(f" Why: {ratio:.0%} of this file's content lines already appear in the root AGENTS.md; per the spec's nearest-file-wins precedence, nested files don't inherit from the root, but they also shouldn't just restate it.")
print(f" Fix: Trim {rel} down to only what's specific to this package/directory.")
print()
if has_fail:
sys.exit(1)
sys.exit(0)
PYTHON

View File

@@ -0,0 +1,30 @@
# tests/
Test files for scripts bundled with this skill.
## Dependencies
Tests require [bats-support](https://github.com/bats-core/bats-support) and
[bats-assert](https://github.com/bats-core/bats-assert). The test files load
helpers from the repo root's `tests/test_helper/`.
From the repo root:
```bash
git clone https://github.com/bats-core/bats-support tests/test_helper/bats-support
git clone https://github.com/bats-core/bats-assert tests/test_helper/bats-assert
```
Run all tests for this skill (from the repo root):
```bash
bats plugins/core/skills/agentsmd-audit/tests/
```
## Files
| File | Purpose |
|------|---------|
| `validate-secrets.bats` | Bats test suite for `scripts/validate-secrets.sh` |
| `validate-structure.bats` | Bats test suite for `scripts/validate-structure.sh` |
| `validate-drift.bats` | Bats test suite for `scripts/validate-drift.sh` |

View File

@@ -0,0 +1,105 @@
#!/usr/bin/env bats
setup() {
REPO_ROOT="$(cd "$BATS_TEST_DIRNAME/../../../../../" && pwd)"
load "$REPO_ROOT/tests/test_helper/bats-support/load"
load "$REPO_ROOT/tests/test_helper/bats-assert/load"
SCRIPT="$(cd "$BATS_TEST_DIRNAME/../scripts" && pwd)/validate-drift.sh"
TMPDIR="$(mktemp -d)"
}
teardown() {
rm -rf "$TMPDIR"
}
@test "fails when AGENTS.md references a stale npm script" {
cat > "$TMPDIR/package.json" <<'EOF'
{
"scripts": {
"test": "jest"
}
}
EOF
cat > "$TMPDIR/AGENTS.md" <<'EOF'
# AGENTS.md
## Testing
- Run `pnpm run e2e` before committing.
EOF
run bash "$SCRIPT" "$TMPDIR"
assert_failure
assert_output --partial "e2e"
}
@test "passes when the referenced npm script exists" {
cat > "$TMPDIR/package.json" <<'EOF'
{
"scripts": {
"test": "jest"
}
}
EOF
cat > "$TMPDIR/AGENTS.md" <<'EOF'
# AGENTS.md
## Testing
- Run `npm run test` before committing.
EOF
run bash "$SCRIPT" "$TMPDIR"
assert_success
}
@test "fails when AGENTS.md references a stale make target" {
cat > "$TMPDIR/Makefile" <<'EOF'
build:
echo building
EOF
cat > "$TMPDIR/AGENTS.md" <<'EOF'
# AGENTS.md
## Setup
Run `make deploy` to ship.
EOF
run bash "$SCRIPT" "$TMPDIR"
assert_failure
assert_output --partial "deploy"
}
@test "emits INFO instead of FAIL when there is no package.json to verify an npm script against" {
cat > "$TMPDIR/AGENTS.md" <<'EOF'
# AGENTS.md
## Testing
Run `pnpm run e2e` before committing.
EOF
run bash "$SCRIPT" "$TMPDIR"
assert_success
assert_output --partial "INFO"
assert_output --partial "e2e"
}
@test "fails when a referenced file path does not exist" {
cat > "$TMPDIR/AGENTS.md" <<'EOF'
# AGENTS.md
## Setup
See `scripts/bootstrap.sh` for environment setup.
EOF
run bash "$SCRIPT" "$TMPDIR"
assert_failure
assert_output --partial "scripts/bootstrap.sh"
}
@test "passes when the referenced file path exists" {
mkdir -p "$TMPDIR/scripts"
: > "$TMPDIR/scripts/bootstrap.sh"
cat > "$TMPDIR/AGENTS.md" <<'EOF'
# AGENTS.md
## Setup
See `scripts/bootstrap.sh` for environment setup.
EOF
run bash "$SCRIPT" "$TMPDIR"
assert_success
}

View File

@@ -0,0 +1,68 @@
#!/usr/bin/env bats
setup() {
REPO_ROOT="$(cd "$BATS_TEST_DIRNAME/../../../../../" && pwd)"
load "$REPO_ROOT/tests/test_helper/bats-support/load"
load "$REPO_ROOT/tests/test_helper/bats-assert/load"
SCRIPT="$(cd "$BATS_TEST_DIRNAME/../scripts" && pwd)/validate-secrets.sh"
TMPDIR="$(mktemp -d)"
}
teardown() {
rm -rf "$TMPDIR"
}
@test "passes on AGENTS.md with no secrets, only placeholders" {
cat > "$TMPDIR/AGENTS.md" <<'EOF'
# AGENTS.md
## Setup
- Set `export API_KEY=$API_KEY`
- Token: <your-token-here>
- DB: postgres://user:changeme@localhost/db
EOF
run bash "$SCRIPT" "$TMPDIR"
assert_success
assert_output ""
}
@test "fails on a real-looking AWS access key" {
cat > "$TMPDIR/AGENTS.md" <<'EOF'
# AGENTS.md
## Setup
- AWS_ACCESS_KEY_ID=AKIAABCDEFGHIJKLMNOP # gitleaks:allow (synthetic fixture — this test verifies validate-secrets.sh catches exactly this pattern)
EOF
run bash "$SCRIPT" "$TMPDIR"
assert_failure
assert_output --partial "AWS access key ID"
assert_output --partial "AGENTS.md:4"
}
@test "fails on a credential-bearing connection string" {
cat > "$TMPDIR/AGENTS.md" <<'EOF'
# AGENTS.md
## Setup
- DB: postgres://svc_user:h8x2Klm9pQrT@db.internal:5432/prod
EOF
run bash "$SCRIPT" "$TMPDIR"
assert_failure
assert_output --partial "connection string"
}
@test "detects secrets in a nested AGENTS.md, not just root" {
mkdir -p "$TMPDIR/packages/api"
cat > "$TMPDIR/AGENTS.md" <<'EOF'
# AGENTS.md
Clean root file.
EOF
cat > "$TMPDIR/packages/api/AGENTS.md" <<'EOF'
# API package
- token: ghp_1234567890abcdefghijklmnopqrstuvwxyz01 # gitleaks:allow (synthetic fixture)
EOF
run bash "$SCRIPT" "$TMPDIR"
assert_failure
assert_output --partial "packages/api/AGENTS.md"
}

View File

@@ -0,0 +1,76 @@
#!/usr/bin/env bats
setup() {
REPO_ROOT="$(cd "$BATS_TEST_DIRNAME/../../../../../" && pwd)"
load "$REPO_ROOT/tests/test_helper/bats-support/load"
load "$REPO_ROOT/tests/test_helper/bats-assert/load"
SCRIPT="$(cd "$BATS_TEST_DIRNAME/../scripts" && pwd)/validate-structure.sh"
TMPDIR="$(mktemp -d)"
}
teardown() {
rm -rf "$TMPDIR"
}
@test "fails on an empty AGENTS.md" {
: > "$TMPDIR/AGENTS.md"
run bash "$SCRIPT" "$TMPDIR"
assert_failure
assert_output --partial "empty"
}
@test "fails on an unfilled placeholder AGENTS.md" {
cat > "$TMPDIR/AGENTS.md" <<'EOF'
# AGENTS.md
## Setup
FILL IN: describe setup commands here.
EOF
run bash "$SCRIPT" "$TMPDIR"
assert_failure
assert_output --partial "placeholder"
}
@test "passes with INFO on real content missing an optional section" {
cat > "$TMPDIR/AGENTS.md" <<'EOF'
# AGENTS.md
## Setup commands
- Install deps: `pnpm install`
- Run tests: `pnpm test`
## Code style
- TypeScript strict mode, single quotes, no semicolons.
EOF
run bash "$SCRIPT" "$TMPDIR"
assert_success
assert_output --partial "INFO"
assert_output --partial "security"
}
@test "suggests trimming a nested AGENTS.md that duplicates the root file" {
mkdir -p "$TMPDIR/packages/api"
cat > "$TMPDIR/AGENTS.md" <<'EOF'
# AGENTS.md
## Setup commands
- Install deps: `pnpm install`
- Run tests: `pnpm test`
- Lint: `pnpm lint`
- Build: `pnpm build`
EOF
cat > "$TMPDIR/packages/api/AGENTS.md" <<'EOF'
# AGENTS.md
## Setup commands
- Install deps: `pnpm install`
- Run tests: `pnpm test`
- Lint: `pnpm lint`
- Build: `pnpm build`
EOF
run bash "$SCRIPT" "$TMPDIR"
assert_success
assert_output --partial "SUGGESTION"
assert_output --partial "packages/api/AGENTS.md"
}

View File

@@ -0,0 +1,27 @@
# agentsmd-author
Create or update a target repo's AGENTS.md file(s) by exploring the repo for real conventions.
## What it does
Explores a target repo (package manager scripts, Makefile/task runner, CI config, linter config, existing docs) and writes or updates `AGENTS.md` with only verified commands and conventions — never invented ones. Supports nested monorepo placement, following the agents.md standard's nearest-file-wins precedence. Closes every run by invoking `agentsmd-audit` inline, and hands off to `provider-adapter-author` when an existing provider-specific file (CLAUDE.md, etc.) now duplicates content AGENTS.md owns.
## Before you start
The `agentsmd-audit` skill must be available (co-installed in the `core` plugin) — this skill invokes it as a mandatory closeout step.
## Usage
```
/agentsmd-author
```
Provide the target repo root (defaults to the current directory) and, if relevant, which subdirectory should get a nested AGENTS.md.
## Files
| File | Purpose |
|------|---------|
| `SKILL.md` | Skill instructions for agents |
| `references/content-guide.md` | Section-by-section AGENTS.md content guidance, a worked example, and monorepo/nested-file precedence rules |
| `references/sources.md` | Provenance record — sources that informed this skill and which files each contributed to |

View File

@@ -0,0 +1,56 @@
---
name: agentsmd-author
description: >
Use when the user wants to create or update a repo's AGENTS.md file
("write an AGENTS.md for this repo", "add setup/test instructions for
agents", "update AGENTS.md", "give this package its own AGENTS.md") — even
if they don't name the file explicitly, e.g. "document this for AI coding
tools" or "make sure agents know how to run tests here". Writes/updates
AGENTS.md by exploring the target repo for real build, test, lint, and
style conventions — never invents commands. Supports nested monorepo
placement (a subdirectory can get its own AGENTS.md following
nearest-file-wins precedence). Closes every run by invoking agentsmd-audit
inline, and calls provider-adapter-author when an existing provider file
(CLAUDE.md, etc.) now duplicates what AGENTS.md owns. Do not use to review
an existing AGENTS.md without changing it — use agentsmd-audit instead. Do
not use to convert CLAUDE.md/.cursor/rules into a thin adapter — use
provider-adapter-author instead.
allowed-tools: Bash Read Write Edit
metadata:
category: docs
source_keys:
- agents-md-official
- context7-websites-agents-md
- context7-agentsmd-agents-md
version: "0.1.1"
---
## Gotchas
- Never invent a command. Every line under a setup/test/build section must come from something you actually found in the repo (`package.json` scripts, a `Makefile` target, a CI workflow step, a README). If you can't verify a command, don't include it.
- AGENTS.md has no required schema — don't force every common-sections-checklist heading into every repo. Include only sections that reflect something real about this repo; a thin, accurate file beats a padded, generic one.
- Nested placement is for genuinely different conventions, not convenience. Only create a subdirectory AGENTS.md when that subtree has its own build tool, stack, or conventions distinct from the root — otherwise you're duplicating content the root already covers, which the nearest-file-wins rule doesn't merge back together.
- This skill never touches CLAUDE.md, `.cursor/rules/*.mdc`, `copilot-instructions.md`, or similar provider files directly — that's `provider-adapter-author`'s job. Detect and hand off; don't reconcile it yourself.
- This skill never audits on its own judgment — the closing `agentsmd-audit` invocation is mandatory, not optional, even when the change looks trivial.
## Step 1 — Explore the target repo
Before writing anything, gather real facts: package manager and scripts (`package.json`, `pyproject.toml`, `Cargo.toml`, etc.), a `Makefile` or task runner, CI config (`.github/workflows/`, etc.) for the commands it actually runs, linter/formatter config files, and any existing docs (`README.md`, existing `AGENTS.md`) describing conventions. Note whether any subdirectory looks like its own package with a different stack.
## Step 2 — Decide placement
- No `AGENTS.md` at the repo root yet → create one there first, covering whole-repo conventions.
- A subdirectory has materially different build/test tooling or conventions than the root → create or update a nested `AGENTS.md` there, scoped to what's different. Don't repeat root-level content — the nearest-file-wins rule means the nested file is read alone, not merged with the root.
- Otherwise → update the existing file(s) in place.
## Step 3 — Write or update
Use only sections that reflect something real about the repo — never fill in every common-sections-checklist heading just because it exists. Read `references/content-guide.md` for section-by-section guidance, a worked example, and what separates useful content from generic padding, before writing.
## Step 4 — Check for an existing provider file
Look for `CLAUDE.md`, `.cursor/rules/*.mdc`, `.github/copilot-instructions.md`, or similar in the target repo. If one exists and now duplicates content the AGENTS.md you just wrote/updated already owns, invoke the `provider-adapter-author` skill on it to reconcile — don't rewrite it yourself.
## Step 5 — Audit and report
Invoke the `agentsmd-audit` skill directly on the AGENTS.md file(s) you just wrote or updated. Resolve any FAIL findings before considering the work done — re-invoke this skill's own writing steps to fix them, then re-run the audit, same as any other close-the-loop check. Report what was created/changed, whether a provider file was reconciled, and the audit's final result.

View File

@@ -0,0 +1,118 @@
---
source_keys:
- agents-md-official
- context7-websites-agents-md
- context7-agentsmd-agents-md
---
# What good AGENTS.md content looks like
AGENTS.md has no required schema — there's no field to fill in, only sections that either
earn their place or don't. Agents treat this file as a set of live directives, not
documentation: they will actually run the commands it lists and fix failures before
finishing a task. That means a wrong or stale line is worse than a missing one. Verify
every command against something real in the repo before writing it down.
## Section-by-section guidance
**Setup / build commands** — the install and dev-server commands, exactly as they appear
in `package.json` scripts, a `Makefile`, or a `Cargo.toml`/`pyproject.toml` equivalent. One
line per command, each with a one-clause note on what it does if the name alone isn't
obvious. Skip this section if there's genuinely nothing beyond "clone and run" — don't pad
it with a restated `git clone`.
**Code style** — only conventions that aren't already enforced by a linter/formatter config
the agent will pick up on its own (a `.eslintrc`, `rustfmt.toml`, etc. speaks for itself).
Write down the conventions that live only in people's heads: naming patterns, module
boundaries, patterns to avoid, anything a linter can't catch. If the repo has no
undocumented conventions beyond what tooling enforces, skip this section.
**Testing instructions** — the exact command(s) to run the suite, where to find
per-package or per-workflow test configuration (e.g. `.github/workflows/`), and any
non-obvious requirement (a service that must be running, an env var that must be set).
State plainly that the agent should run tests before considering a change done and fix
failures — don't leave this implicit.
**Security considerations** — only repo-specific hazards: a data-handling boundary, a
credential pattern to never hardcode, a destructive command that needs a confirmation
step. Do not restate general security advice ("don't commit secrets") that any agent
already assumes — that's padding, not a directive.
**Commit / PR conventions** — the title/format convention if one exists (e.g. a
Conventional Commits type prefix, a ticket-number requirement), and any check that must
pass before a PR is opened (lint, test, type-check). Point at the real command, not
"make sure it passes."
**Dev environment tips** — the handful of things that save real time and are easy to miss:
how to jump to a specific package in a monorepo without `ls`-ing around, how to register a
new package so the toolchain sees it, where to look up a canonical name/id. This section
is for genuine friction points observed in this repo, not generic advice.
## What separates useful content from padding
A useful section names a real file, command, or path that exists in this repo right now.
A padded section could be pasted into any repo unchanged and still "make sense" — that's
the tell. If a sentence would read the same in a different codebase, it doesn't belong.
Prefer four accurate lines over twelve generic ones.
## Worked example (minimal project)
```markdown
# AGENTS.md
## Setup commands
- Install deps: `pnpm install`
- Start dev server: `pnpm dev`
- Run tests: `pnpm test`
## Code style
- TypeScript strict mode
- Single quotes, no semicolons
- Use functional patterns where possible
## Dev environment tips
- Use `pnpm dlx turbo run where <project_name>` to jump to a package instead of scanning with `ls`.
- Run `pnpm install --filter <project_name>` to add the package to your workspace so Vite, ESLint, and TypeScript can see it.
- Check the `name` field inside each package's `package.json` to confirm the right name.
## Testing instructions
- Find the CI plan in the `.github/workflows` folder.
- Run `pnpm turbo run test --filter <project_name>` to run every check defined for that package.
- From the package root you can just call `pnpm test`. The commit should pass all tests before you merge.
- Fix any test or type errors until the whole suite is green.
- Add or update tests for the code you change, even if nobody asked.
## PR instructions
- Title format: [<project_name>] <Title>
- Always run `pnpm lint` and `pnpm test` before committing.
```
Every line above names a real command or path — that's the standard to hold this repo's
version to, not the specific tooling shown (a Python/Cargo/Go repo's AGENTS.md should look
nothing like this one in its specifics, only in how concrete each line is).
## Monorepo / nested placement
```
my-monorepo/
├── AGENTS.md # Root-level: applies to the whole repo
├── packages/
│ ├── api/
│ │ └── AGENTS.md # API-specific instructions; overrides root for this package
│ ├── web/
│ │ └── AGENTS.md # Web app-specific instructions
│ └── shared/
│ └── AGENTS.md # Shared library instructions
```
Precedence rule: the file nearest the edited path wins. Nested files are **not** merged
with the root file — an agent editing inside `packages/api/` reads only
`packages/api/AGENTS.md`, never the root file in addition. Consequences:
- A nested file must stand alone. Don't write "also see the root file" — write what the
agent needs, full stop.
- Don't duplicate root content in a nested file "just in case." If a nested file repeats
root-level setup instructions verbatim, that's a sign it shouldn't exist as a separate
file at all — the subtree isn't actually different enough to warrant one.
- Only create a nested file when the subtree has a genuinely different stack, build tool,
or convention than the root (see `SKILL.md` Step 2 for the placement decision itself).

View File

@@ -0,0 +1,25 @@
# Sources
## agents-md-official
- **URL:** https://agents.md/
- **Description:** Official agents.md website — format spec, common-sections checklist, precedence rules (nearest-file-wins, no merge across files), monorepo nesting patterns
- **Research doc:** plugins/core/docs/research/docs/agentsmd/sources.md
- **Contributing files:** SKILL.md, references/content-guide.md
- **Status:** `extracted`
## context7-websites-agents-md
- **URL:** context7:/websites/agents_md
- **Description:** Context7 index of the official agents.md website — overview, governance, cross-tool compatibility, configuration examples
- **Research doc:** plugins/core/docs/research/docs/agentsmd/sources.md
- **Contributing files:** SKILL.md, references/content-guide.md
- **Status:** `extracted`
## context7-agentsmd-agents-md
- **URL:** context7:/agentsmd/agents.md
- **Description:** Context7 index of the agentsmd/agents.md repository — format spec, nested monorepo patterns, file structure examples
- **Research doc:** plugins/core/docs/research/docs/agentsmd/sources.md
- **Contributing files:** SKILL.md, references/content-guide.md
- **Status:** `extracted`

View File

@@ -0,0 +1,30 @@
# provider-adapter-author
Convert a target repo's provider-specific instruction file (CLAUDE.md, .cursor/rules, copilot-instructions.md, etc.) into a thin adapter over AGENTS.md.
## What it does
Detects a provider-specific AI instruction file in a target repo, diffs it against the repo's `AGENTS.md`, and rewrites it down to a minimal reference — an `@AGENTS.md`-style import for providers that support one, or a text pointer for those that don't — plus only genuinely provider-specific additions. Self-validates its own output with a bundled deterministic script (no LLM judgment, no separate audit skill) before finishing.
## Before you start
The target repo must already have an `AGENTS.md`. If it doesn't, run `agentsmd-author` first — this skill never creates or edits `AGENTS.md` itself.
## Usage
```
/provider-adapter-author
```
Provide the path to the provider-specific file to convert (and the target repo root, if not inferable). Can be invoked directly, or composed into by `agentsmd-author` when it detects an existing provider file with content overlapping AGENTS.md.
## Files
| File | Purpose |
|------|---------|
| `SKILL.md` | Skill instructions for agents |
| `references/sources.md` | Provenance record — the in-repo ADR precedent this skill's design is modeled on |
| `scripts/validate-adapter.sh` | Self-check gate: reference to AGENTS.md present, no excessive duplication, adapter stays thin |
| `scripts/README.md` | Directory documentation for `scripts/` |
| `tests/README.md` | Bats test dependency and run instructions |
| `tests/validate-adapter.bats` | Bats test suite for `scripts/validate-adapter.sh` |

View File

@@ -0,0 +1,54 @@
---
name: provider-adapter-author
description: >
Use when the user wants to convert a provider-specific AI instruction file
(CLAUDE.md, .cursor/rules/*.mdc, copilot-instructions.md, etc.) into a
thin adapter that defers to a repo's AGENTS.md — e.g. "reduce duplication
between CLAUDE.md and AGENTS.md", "make CLAUDE.md just import AGENTS.md"
— even if the pattern isn't named explicitly. Also invoke when
agentsmd-author detects an existing provider file overlapping with
AGENTS.md it just wrote. Detects redundant content in a provider file
relative to AGENTS.md and rewrites it down to a minimal reference (an
`@AGENTS.md`-style import where supported, or a text pointer otherwise)
plus genuinely provider-specific additions. Self-validates via a bundled
deterministic script before finishing. Do not use to write or audit
AGENTS.md itself — use agentsmd-author or agentsmd-audit.
allowed-tools: Bash Read Edit Write
metadata:
category: docs
source_keys:
- adr-0002-0003-two-tier-claude-md
version: "0.1.0"
---
## Gotchas
- Not every provider supports cross-file imports. Claude Code does — a `CLAUDE.md` can consist of nothing but one or more `@path` lines (e.g. `@AGENTS.md`), with no other content required. Cursor's `.cursor/rules/*.mdc` and GitHub Copilot's `copilot-instructions.md` have no native import mechanism as of current tooling — for those, "thin" means a short text pointer to AGENTS.md plus only what that tool actually needs, not a literal import line. Pass `--no-import-syntax` to `scripts/validate-adapter.sh` for these providers.
- This skill never creates or edits `AGENTS.md` itself. If the target repo has no `AGENTS.md` yet, stop and point the user to `agentsmd-author` first — there's nothing to adapt to.
- Only strip content from the provider file that's genuinely redundant with AGENTS.md. Provider-specific material (IDE settings, tool-only syntax, model-specific instructions) stays — the goal is thin, not empty.
- Works standalone or composed-into by `agentsmd-author` — behave identically either way; don't assume a caller skill exists.
## Step 1 — Detect
Look for known provider instruction files in the target repo: `CLAUDE.md` (repo root, and any deployed copies), `.cursor/rules/*.mdc`, `.github/copilot-instructions.md`, and similar tool-specific files. Confirm `AGENTS.md` exists at the repo root — if not, stop and tell the user to run `agentsmd-author` first.
## Step 2 — Diff and rewrite
Read the provider file and `AGENTS.md` side by side. Separate the provider file's content into two buckets: lines that restate what `AGENTS.md` already owns (universal rules, conventions, project overview) versus lines that are genuinely provider-specific (tool syntax, IDE behavior, model-specific instructions). Rewrite the provider file:
- **Providers with import syntax** (Claude Code): replace the redundant bucket with an `@AGENTS.md` (or correct relative path) import line, keep the provider-specific bucket below it.
- **Providers without import syntax** (Cursor, Copilot, etc.): replace the redundant bucket with a short pointer sentence mentioning `AGENTS.md`, keep the provider-specific bucket.
## Step 3 — Self-validate
Run the bundled check before finishing — this is the skill's own closeout gate; there is no separate paired audit skill for this concern:
```bash
bash scripts/validate-adapter.sh [--no-import-syntax] [--max-lines N] <adapter-file> <agents-md-file>
```
Fix any `FAIL` and re-run until it exits `0`.
## Step 4 — Report
State which file was converted, what was removed versus kept, and the validator's final result.

View File

@@ -0,0 +1,9 @@
# Sources
## adr-0002-0003-two-tier-claude-md
- **URL:** (in-repo precedent — not an external source or plugin research corpus entry)
- **Description:** This repo's own two-tier CLAUDE.md/AGENTS.md pattern: AGENTS.md is the provider-agnostic source of always-on rules; provider-specific files (CLAUDE.md) become thin adapters that import it (`@AGENTS.md` plus provider-specific additions). Grounds this skill's entire adapter-conversion design — the "thin adapter" shape, the `@`-import convention, and the size/duplication expectations enforced by `scripts/validate-adapter.sh`.
- **Research doc:** docs/adr/0002-two-tier-claude-md.md, docs/adr/0003-agents-md-provider-agnostic-entry-point.md, providers/claude-code/CLAUDE.md (in-repo ADRs and a live example, not a plugin research corpus entry; referenced here since this skill's design is modeled directly on an existing implementation rather than external research)
- **Contributing files:** SKILL.md
- **Status:** `extracted`

View File

@@ -0,0 +1,9 @@
# scripts/
Deterministic self-check this skill shells out to instead of relying on LLM judgment for a mechanical check.
| File | Purpose |
|------|---------|
| `validate-adapter.sh` | Checks a rewritten provider file (CLAUDE.md, etc.) has a reference to AGENTS.md, doesn't duplicate its content, and stays under a thin-file line threshold |
Takes `<adapter-file> <agents-md-file>`, with optional `--no-import-syntax` and `--max-lines N` flags. Prints `FAIL` findings to stdout and exits non-zero on any failure.

View File

@@ -0,0 +1,141 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<EOF
Usage: validate-adapter.sh [--no-import-syntax] [--max-lines N] <adapter-file> <agents-md-file>
Self-check gate for provider-adapter-author. Checks that a rewritten
provider-specific instruction file (CLAUDE.md, .cursor/rules/*.mdc,
copilot-instructions.md, etc.) is actually a thin adapter over AGENTS.md,
not a duplicate copy of it.
Arguments:
adapter-file Path to the provider-specific file to check.
agents-md-file Path to the AGENTS.md file it should defer to.
Options:
--no-import-syntax The target provider has no native cross-file import
mechanism. Accept a plain-text pointer mention of
"AGENTS.md" instead of requiring an @import-style line.
--max-lines N Max non-blank lines allowed in the adapter file before
it's considered no longer "thin". Default: 60.
--help, -h Show this help and exit 0.
Exit codes:
0 Adapter file passes all checks
1 One or more checks failed (empty file, no reference to AGENTS.md,
excessive duplication, or file too long)
EOF
}
NO_IMPORT_SYNTAX=0
MAX_LINES=60
ARGS=()
while [[ $# -gt 0 ]]; do
case "$1" in
--help|-h)
usage
exit 0
;;
--no-import-syntax)
NO_IMPORT_SYNTAX=1
shift
;;
--max-lines)
MAX_LINES="${2:-}"
shift 2
;;
*)
ARGS+=("$1")
shift
;;
esac
done
if [[ ${#ARGS[@]} -lt 2 ]]; then
echo "Error: adapter-file and agents-md-file are required." >&2
echo "" >&2
usage >&2
exit 1
fi
python3 -u - "${ARGS[0]}" "${ARGS[1]}" "$NO_IMPORT_SYNTAX" "$MAX_LINES" <<'PYTHON'
import sys
import os
import re
adapter_path, agents_md_path, no_import_syntax, max_lines = sys.argv[1:5]
no_import_syntax = no_import_syntax == "1"
max_lines = int(max_lines)
if not os.path.isfile(adapter_path):
print(f"Error: '{adapter_path}' is not a file.", file=sys.stderr)
sys.exit(1)
if not os.path.isfile(agents_md_path):
print(f"Error: '{agents_md_path}' is not a file.", file=sys.stderr)
sys.exit(1)
with open(adapter_path, encoding="utf-8", errors="replace") as f:
adapter_content = f.read()
with open(agents_md_path, encoding="utf-8", errors="replace") as f:
agents_md_content = f.read()
has_fail = False
if not adapter_content.strip():
print(f"FAIL Adapter file is empty — {adapter_path}")
print(" Why: An empty adapter carries no reference to AGENTS.md and no provider-specific content.")
print(" Fix: Add at least an import (or text pointer) to AGENTS.md.")
print()
sys.exit(1)
IMPORT_RE = re.compile(r'(?m)^\s*@\S*AGENTS\.md\s*$')
lines = adapter_content.splitlines()
import_lines = [ln for ln in lines if IMPORT_RE.match(ln)]
if no_import_syntax:
has_reference = "AGENTS.md" in adapter_content
else:
has_reference = bool(import_lines) or "AGENTS.md" in adapter_content
if not has_reference:
has_fail = True
print(f"FAIL Adapter has no reference to AGENTS.md — {adapter_path}")
if no_import_syntax:
print(" Why: This provider has no import syntax, so the adapter must at least mention AGENTS.md as a text pointer.")
print(" Fix: Add a sentence like \"See AGENTS.md at the repo root for shared conventions.\"")
else:
print(" Why: A thin adapter must import AGENTS.md (e.g. `@AGENTS.md`) rather than silently omitting it.")
print(" Fix: Add an `@AGENTS.md` (or equivalent relative path) import line.")
print()
# --- Duplication check ---
non_import_lines = [ln for ln in lines if not IMPORT_RE.match(ln)]
adapter_lines = [ln.strip() for ln in non_import_lines if ln.strip()]
agents_lines = {ln.strip() for ln in agents_md_content.splitlines() if ln.strip()}
if adapter_lines:
overlap = sum(1 for ln in adapter_lines if ln in agents_lines)
ratio = overlap / len(adapter_lines)
if ratio > 0.3:
has_fail = True
print(f"FAIL Adapter duplicates AGENTS.md content — {adapter_path}")
print(f" Why: {ratio:.0%} of the adapter's non-import lines already appear verbatim in AGENTS.md. A thin adapter should import shared content, not restate it.")
print(" Fix: Remove the duplicated lines and rely on the AGENTS.md import (or pointer) instead.")
print()
# --- Size check ---
non_blank_count = len([ln for ln in lines if ln.strip()])
if non_blank_count > max_lines:
has_fail = True
print(f"FAIL Adapter is not thin — {adapter_path}")
print(f" Why: {non_blank_count} non-blank lines exceeds the {max_lines}-line threshold for a thin adapter.")
print(" Fix: Move provider-agnostic content into AGENTS.md; keep only genuinely provider-specific additions here.")
print()
if has_fail:
sys.exit(1)
sys.exit(0)
PYTHON

View File

@@ -0,0 +1,28 @@
# tests/
Test files for scripts bundled with this skill.
## Dependencies
Tests require [bats-support](https://github.com/bats-core/bats-support) and
[bats-assert](https://github.com/bats-core/bats-assert). The test files load
helpers from the repo root's `tests/test_helper/`.
From the repo root:
```bash
git clone https://github.com/bats-core/bats-support tests/test_helper/bats-support
git clone https://github.com/bats-core/bats-assert tests/test_helper/bats-assert
```
Run all tests for this skill (from the repo root):
```bash
bats plugins/core/skills/provider-adapter-author/tests/
```
## Files
| File | Purpose |
|------|---------|
| `validate-adapter.bats` | Bats test suite for `scripts/validate-adapter.sh` |

View File

@@ -0,0 +1,128 @@
#!/usr/bin/env bats
setup() {
REPO_ROOT="$(cd "$BATS_TEST_DIRNAME/../../../../../" && pwd)"
load "$REPO_ROOT/tests/test_helper/bats-support/load"
load "$REPO_ROOT/tests/test_helper/bats-assert/load"
SCRIPT="$(cd "$BATS_TEST_DIRNAME/../scripts" && pwd)/validate-adapter.sh"
TMPDIR="$(mktemp -d)"
AGENTS_MD="$TMPDIR/AGENTS.md"
cat > "$AGENTS_MD" <<'EOF'
# AGENTS.md
## Setup commands
- Install deps: `pnpm install`
- Run tests: `pnpm test`
## Code style
- TypeScript strict mode, single quotes, no semicolons.
EOF
}
teardown() {
rm -rf "$TMPDIR"
}
@test "fails when the adapter file is empty" {
ADAPTER="$TMPDIR/CLAUDE.md"
: > "$ADAPTER"
run bash "$SCRIPT" "$ADAPTER" "$AGENTS_MD"
assert_failure
assert_output --partial "empty"
}
@test "fails when the adapter has no reference to AGENTS.md" {
ADAPTER="$TMPDIR/CLAUDE.md"
cat > "$ADAPTER" <<'EOF'
# Claude-specific notes
Use the internal linter before committing.
EOF
run bash "$SCRIPT" "$ADAPTER" "$AGENTS_MD"
assert_failure
assert_output --partial "no reference"
}
@test "passes a thin adapter with an @import line and provider-specific additions" {
ADAPTER="$TMPDIR/CLAUDE.md"
cat > "$ADAPTER" <<'EOF'
@AGENTS.md
@core/instructions/governance.md
EOF
run bash "$SCRIPT" "$ADAPTER" "$AGENTS_MD"
assert_success
}
@test "fails when the adapter duplicates most of AGENTS.md's content" {
ADAPTER="$TMPDIR/CLAUDE.md"
cat > "$ADAPTER" <<'EOF'
@AGENTS.md
## Setup commands
- Install deps: `pnpm install`
- Run tests: `pnpm test`
## Code style
- TypeScript strict mode, single quotes, no semicolons.
EOF
run bash "$SCRIPT" "$ADAPTER" "$AGENTS_MD"
assert_failure
assert_output --partial "duplicat"
}
@test "fails when the adapter exceeds the max line threshold" {
ADAPTER="$TMPDIR/CLAUDE.md"
{
echo "@AGENTS.md"
for i in $(seq 1 80); do echo "Provider-specific line $i unrelated to AGENTS.md content."; done
} > "$ADAPTER"
run bash "$SCRIPT" "$ADAPTER" "$AGENTS_MD"
assert_failure
assert_output --partial "thin"
}
@test "allows a custom --max-lines threshold" {
ADAPTER="$TMPDIR/CLAUDE.md"
{
echo "@AGENTS.md"
for i in $(seq 1 10); do echo "Provider-specific line $i unrelated to AGENTS.md content."; done
} > "$ADAPTER"
run bash "$SCRIPT" --max-lines 5 "$ADAPTER" "$AGENTS_MD"
assert_failure
assert_output --partial "thin"
}
@test "with --no-import-syntax, a text pointer to AGENTS.md is accepted instead of an @import line" {
ADAPTER="$TMPDIR/copilot-instructions.md"
cat > "$ADAPTER" <<'EOF'
See AGENTS.md at the repo root for setup, style, and testing conventions.
## Copilot-specific
Prefer inline suggestions over chat for one-line edits.
EOF
run bash "$SCRIPT" --no-import-syntax "$ADAPTER" "$AGENTS_MD"
assert_success
}
@test "with --no-import-syntax, still fails if there is no mention of AGENTS.md at all" {
ADAPTER="$TMPDIR/copilot-instructions.md"
cat > "$ADAPTER" <<'EOF'
## Copilot-specific
Prefer inline suggestions over chat for one-line edits.
EOF
run bash "$SCRIPT" --no-import-syntax "$ADAPTER" "$AGENTS_MD"
assert_failure
assert_output --partial "no reference"
}
@test "--help exits 0 and documents usage" {
run bash "$SCRIPT" --help
assert_success
assert_output --partial "Usage:"
}
@test "fails with a clear error when the adapter file argument is missing" {
run bash "$SCRIPT"
assert_failure
assert_output --partial "required"
}