fix(core): make --no-import-syntax actually change the adapter validation

Both branches of the flag reduced to the same expression, so the option was
inert: a caller who asked for the no-import form got the import-form check
anyway and a passing result that meant nothing.

Two further defects in the same validator: --max-lines failed silently when
given a value it could not use, and the Fix text told the agent to edit
AGENTS.md when the offending content is the provider adapter's. The boundary
clauses now name the operation being routed rather than the file type, which
was ambiguous where both skills touch the same file.

Addresses #115.
This commit is contained in:
2026-08-31 08:01:45 +00:00
parent 14af50bc07
commit 1c3af75642
13 changed files with 131 additions and 76 deletions

View File

@@ -33,6 +33,6 @@ hand-edit made outside `agentsmd-author` — the audit is what confirms the resu
| `tests/validate-drift.bats` | (source-only) Bats test suite for `scripts/validate-drift.sh` | | `tests/validate-drift.bats` | (source-only) Bats test suite for `scripts/validate-drift.sh` |
Rows marked **(source-only)** exist in the authoring source (`.apm/skills/agentsmd-audit/`) but are Rows marked **(source-only)** exist in the authoring source (`.apm/skills/agentsmd-audit/`) but are
not present in an installed plugin: `scripts/sync-plugin-content.sh` strips `<category>/<name>/tests` not present in an installed plugin: the repo's `scripts/sync-plugin-content.sh` strips
when it generates the flat mirror, because these are dev-time fixtures no plugin host needs to `<category>/<name>/tests` when it generates the flat mirror, because these are dev-time fixtures no
discover (ADR-0017). Run them from a repo checkout, not from an install. plugin host needs to discover (ADR-0017). Run them from a repo checkout, not from an install.

View File

@@ -1,10 +1,10 @@
--- ---
name: agentsmd-audit name: agentsmd-audit
description: > description: >
Use when the user wants a repo's AGENTS.md audited — "audit this AGENTS.md", Use when the user wants a repo's AGENTS.md audited for secrets, structure
"is this AGENTS.md safe to commit" — or after a hand-edit outside and drift — "is this AGENTS.md safe to commit" — or after a hand-edit
`agentsmd-author`. Reports secrets, structure and drift; never edits. outside `agentsmd-author`.
Not for CLAUDE.md or provider files -> `provider-adapter-author`. Not converting a provider file -> `provider-adapter-author`.
Not writing AGENTS.md -> `agentsmd-author`. Not writing AGENTS.md -> `agentsmd-author`.
allowed-tools: Bash Read allowed-tools: Bash Read
metadata: metadata:
@@ -31,7 +31,7 @@ bash scripts/validate-structure.sh <repo-root>
bash scripts/validate-drift.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. Grade a manual finding the way the scripts grade theirs: 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. Each script walks the repo for every `AGENTS.md` file (root and nested, excluding `.git`, `node_modules`, `vendor`, and similar) and prints `FAIL` lines, plus `INFO`/`SUGGESTION` where applicable, 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. Grade a manual finding the way the scripts grade theirs: 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.
## Step 2 — Report ## Step 2 — Report
@@ -43,16 +43,4 @@ 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). 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: Close with a `## Result` block holding one line: `PASS`, `PASS (N suggestions)`, or `FAIL (N fails · M suggestions)`, each optionally followed by ` · P info`. Omit the suggestion count when there are none, and omit `· P info` when there are none. 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.
```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

@@ -3,7 +3,7 @@ name: agentsmd-author
description: > description: >
Use when the user wants a repo's AGENTS.md written or updated, root or Use when the user wants a repo's AGENTS.md written or updated, root or
nested, including "document this for AI coding tools". Writes only verified nested, including "document this for AI coding tools". Writes only verified
conventions. Not review-only -> `agentsmd-audit`. Not for CLAUDE.md -> conventions. Not review-only -> `agentsmd-audit`. Not converting CLAUDE.md ->
`provider-adapter-author`. `provider-adapter-author`.
allowed-tools: Bash Read Write Edit allowed-tools: Bash Read Write Edit
metadata: metadata:
@@ -36,8 +36,8 @@ AGENTS.md has no required schema. Use only sections that reflect something real
## Step 4 — Check for an existing provider file ## 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. Look for `CLAUDE.md`, `.cursor/rules/*.mdc`, `.github/copilot-instructions.md`, or similar in the target repo. If one exists, invoke the `provider-adapter-author` skill on it to reconcile — whether it duplicates content the AGENTS.md you just wrote/updated now owns, or is merely stale or missing a pointer to it. Never edit it yourself in either case.
## Step 5 — Audit and report ## Step 5 — Audit and report
Invoke the `agentsmd-audit` skill directly on the AGENTS.md file(s) you just wrote or updated. This closeout is mandatory, not optional, even when the change looks trivial — never sign the work off on your own judgment. 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. Invoke the `agentsmd-audit` skill on the target repo root — its validators take a `<repo-root>` and walk the tree for every AGENTS.md themselves; there is no per-file entry point. This closeout is mandatory, not optional, even when the change looks trivial — never sign the work off on your own judgment. 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

@@ -31,6 +31,6 @@ Provide the path to the provider-specific file to convert (and the target repo r
| `tests/validate-adapter.bats` | (source-only) Bats test suite for `scripts/validate-adapter.sh` | | `tests/validate-adapter.bats` | (source-only) Bats test suite for `scripts/validate-adapter.sh` |
Rows marked **(source-only)** exist in the authoring source (`.apm/skills/provider-adapter-author/`) Rows marked **(source-only)** exist in the authoring source (`.apm/skills/provider-adapter-author/`)
but are not present in an installed plugin: `scripts/sync-plugin-content.sh` strips but are not present in an installed plugin: the repo's `scripts/sync-plugin-content.sh` strips
`<category>/<name>/tests` when it generates the flat mirror, because these are dev-time fixtures no `<category>/<name>/tests` when it generates the flat mirror, because these are dev-time fixtures no
plugin host needs to discover (ADR-0017). Run them from a repo checkout, not from an install. plugin host needs to discover (ADR-0017). Run them from a repo checkout, not from an install.

View File

@@ -2,7 +2,8 @@
name: provider-adapter-author name: provider-adapter-author
description: > description: >
Use when a provider file (CLAUDE.md, .cursor rules, copilot-instructions) Use when a provider file (CLAUDE.md, .cursor rules, copilot-instructions)
duplicating the repo's AGENTS.md should be cut to a thin adapter. duplicating the repo's AGENTS.md should be cut to a thin adapter — "make
CLAUDE.md just import AGENTS.md".
Not writing the AGENTS file -> `agentsmd-author`. Not writing the AGENTS file -> `agentsmd-author`.
Not auditing the AGENTS file -> `agentsmd-audit`. Not auditing the AGENTS file -> `agentsmd-audit`.
allowed-tools: Bash Read Edit Write allowed-tools: Bash Read Edit Write
@@ -16,7 +17,6 @@ metadata:
## Gotchas ## Gotchas
- Assume a provider has no cross-file import mechanism until you have confirmed it has one. Claude Code is the exception, not the rule: a `CLAUDE.md` may consist of nothing but `@path` lines, while the same `@AGENTS.md` line in a Cursor rule or a Copilot instructions file is inert text no tool resolves. Pass `--no-import-syntax` to `scripts/validate-adapter.sh` for those providers. - Assume a provider has no cross-file import mechanism until you have confirmed it has one. Claude Code is the exception, not the rule: a `CLAUDE.md` may consist of nothing but `@path` lines, while the same `@AGENTS.md` line in a Cursor rule or a Copilot instructions file is inert text no tool resolves. Pass `--no-import-syntax` to `scripts/validate-adapter.sh` for those providers.
- Works standalone or composed-into by `agentsmd-author` — behave identically either way; don't assume a caller skill exists.
## Step 1 — Detect ## Step 1 — Detect
@@ -43,7 +43,7 @@ Run the bundled check before finishing — this is the skill's own closeout gate
bash scripts/validate-adapter.sh [--no-import-syntax] [--max-lines N] <adapter-file> <agents-md-file> bash scripts/validate-adapter.sh [--no-import-syntax] [--max-lines N] <adapter-file> <agents-md-file>
``` ```
Fix any `FAIL` by editing the provider file, and re-run until it exits `0`. The size `FAIL` advises moving provider-agnostic content into `AGENTS.md`; disregard that half of its wording and delete the redundant lines instead. Fix any `FAIL` by editing the provider file, and re-run until it exits `0`.
## Step 4 — Report ## Step 4 — Report

View File

@@ -16,10 +16,15 @@ Arguments:
Options: Options:
--no-import-syntax The target provider has no native cross-file import --no-import-syntax The target provider has no native cross-file import
mechanism. Accept a plain-text pointer mention of mechanism. Require a plain-text pointer line naming
"AGENTS.md" instead of requiring an @import-style line. "AGENTS.md" instead of an @import-style line; an
@AGENTS.md line alone does not satisfy it, because
such a provider never resolves it. Without this flag
an actual @import line is required, and naming
AGENTS.md in prose alone does not satisfy it.
--max-lines N Max non-blank lines allowed in the adapter file before --max-lines N Max non-blank lines allowed in the adapter file before
it's considered no longer "thin". Default: 60. it's considered no longer "thin". Must be a
non-negative integer. Default: 60.
--help, -h Show this help and exit 0. --help, -h Show this help and exit 0.
Exit codes: Exit codes:
@@ -44,7 +49,15 @@ while [[ $# -gt 0 ]]; do
shift shift
;; ;;
--max-lines) --max-lines)
MAX_LINES="${2:-}" if [[ $# -lt 2 ]]; then
echo "Error: --max-lines requires a value (a non-negative integer)." >&2
exit 1
fi
MAX_LINES="$2"
if [[ ! "$MAX_LINES" =~ ^[0-9]+$ ]]; then
echo "Error: --max-lines expects a non-negative integer, got '$MAX_LINES'." >&2
exit 1
fi
shift 2 shift 2
;; ;;
*) *)
@@ -94,21 +107,25 @@ if not adapter_content.strip():
IMPORT_RE = re.compile(r'(?m)^\s*@\S*AGENTS\.md\s*$') IMPORT_RE = re.compile(r'(?m)^\s*@\S*AGENTS\.md\s*$')
lines = adapter_content.splitlines() lines = adapter_content.splitlines()
import_lines = [ln for ln in lines if IMPORT_RE.match(ln)] import_lines = [ln for ln in lines if IMPORT_RE.match(ln)]
# A prose pointer is any line naming AGENTS.md that is not itself an import
# line — an inert `@AGENTS.md` in a provider that resolves no imports points
# a reader at nothing.
pointer_lines = [ln for ln in lines if not IMPORT_RE.match(ln) and "AGENTS.md" in ln]
if no_import_syntax: if no_import_syntax:
has_reference = "AGENTS.md" in adapter_content has_reference = bool(pointer_lines)
else: else:
has_reference = bool(import_lines) or "AGENTS.md" in adapter_content has_reference = bool(import_lines)
if not has_reference: if not has_reference:
has_fail = True has_fail = True
print(f"FAIL Adapter has no reference to AGENTS.md — {adapter_path}") print(f"FAIL Adapter has no reference to AGENTS.md — {adapter_path}")
if no_import_syntax: 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(" Why: This provider resolves no cross-file import, so the adapter must point at AGENTS.md in prose; an `@AGENTS.md` line here is inert text.")
print(" Fix: Add a sentence like \"See AGENTS.md at the repo root for shared conventions.\"") print(" Fix: Add a sentence like \"See AGENTS.md at the repo root for shared conventions.\"")
else: else:
print(" Why: A thin adapter must import AGENTS.md (e.g. `@AGENTS.md`) rather than silently omitting it.") print(" Why: A thin adapter must import AGENTS.md with an `@AGENTS.md` line; merely naming the file in prose defers nothing.")
print(" Fix: Add an `@AGENTS.md` (or equivalent relative path) import line.") print(" Fix: Add an `@AGENTS.md` (or equivalent relative path) import line, or pass --no-import-syntax if this provider resolves no imports.")
print() print()
# --- Duplication check --- # --- Duplication check ---
@@ -132,7 +149,7 @@ if non_blank_count > max_lines:
has_fail = True has_fail = True
print(f"FAIL Adapter is not thin — {adapter_path}") 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(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(" Fix: Delete the lines already covered by AGENTS.md; keep only genuinely provider-specific additions here.")
print() print()
if has_fail: if has_fail:

View File

@@ -115,6 +115,51 @@ EOF
assert_output --partial "no reference" assert_output --partial "no reference"
} }
@test "the two --no-import-syntax branches disagree: a text-pointer-only adapter fails in default mode" {
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" "$ADAPTER" "$AGENTS_MD"
assert_failure
assert_output --partial "no reference"
run bash "$SCRIPT" --no-import-syntax "$ADAPTER" "$AGENTS_MD"
assert_success
}
@test "with --no-import-syntax, an inert @AGENTS.md line alone is not a prose pointer" {
ADAPTER="$TMPDIR/copilot-instructions.md"
cat > "$ADAPTER" <<'EOF'
@AGENTS.md
## 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 "--max-lines as the final argument reports a real error instead of failing silently" {
ADAPTER="$TMPDIR/CLAUDE.md"
echo "@AGENTS.md" > "$ADAPTER"
run bash "$SCRIPT" "$ADAPTER" "$AGENTS_MD" --max-lines
assert_failure
assert_output --partial "--max-lines requires a value"
}
@test "--max-lines rejects a non-numeric value with a real error" {
ADAPTER="$TMPDIR/CLAUDE.md"
echo "@AGENTS.md" > "$ADAPTER"
run bash "$SCRIPT" --max-lines abc "$ADAPTER" "$AGENTS_MD"
assert_failure
assert_output --partial "non-negative integer"
}
@test "--help exits 0 and documents usage" { @test "--help exits 0 and documents usage" {
run bash "$SCRIPT" --help run bash "$SCRIPT" --help
assert_success assert_success

View File

@@ -33,6 +33,6 @@ hand-edit made outside `agentsmd-author` — the audit is what confirms the resu
| `tests/validate-drift.bats` | (source-only) Bats test suite for `scripts/validate-drift.sh` | | `tests/validate-drift.bats` | (source-only) Bats test suite for `scripts/validate-drift.sh` |
Rows marked **(source-only)** exist in the authoring source (`.apm/skills/agentsmd-audit/`) but are Rows marked **(source-only)** exist in the authoring source (`.apm/skills/agentsmd-audit/`) but are
not present in an installed plugin: `scripts/sync-plugin-content.sh` strips `<category>/<name>/tests` not present in an installed plugin: the repo's `scripts/sync-plugin-content.sh` strips
when it generates the flat mirror, because these are dev-time fixtures no plugin host needs to `<category>/<name>/tests` when it generates the flat mirror, because these are dev-time fixtures no
discover (ADR-0017). Run them from a repo checkout, not from an install. plugin host needs to discover (ADR-0017). Run them from a repo checkout, not from an install.

View File

@@ -1,10 +1,10 @@
--- ---
name: agentsmd-audit name: agentsmd-audit
description: > description: >
Use when the user wants a repo's AGENTS.md audited — "audit this AGENTS.md", Use when the user wants a repo's AGENTS.md audited for secrets, structure
"is this AGENTS.md safe to commit" — or after a hand-edit outside and drift — "is this AGENTS.md safe to commit" — or after a hand-edit
`agentsmd-author`. Reports secrets, structure and drift; never edits. outside `agentsmd-author`.
Not for CLAUDE.md or provider files -> `provider-adapter-author`. Not converting a provider file -> `provider-adapter-author`.
Not writing AGENTS.md -> `agentsmd-author`. Not writing AGENTS.md -> `agentsmd-author`.
allowed-tools: Bash Read allowed-tools: Bash Read
metadata: metadata:
@@ -31,7 +31,7 @@ bash scripts/validate-structure.sh <repo-root>
bash scripts/validate-drift.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. Grade a manual finding the way the scripts grade theirs: 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. Each script walks the repo for every `AGENTS.md` file (root and nested, excluding `.git`, `node_modules`, `vendor`, and similar) and prints `FAIL` lines, plus `INFO`/`SUGGESTION` where applicable, 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. Grade a manual finding the way the scripts grade theirs: 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.
## Step 2 — Report ## Step 2 — Report
@@ -43,16 +43,4 @@ 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). 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: Close with a `## Result` block holding one line: `PASS`, `PASS (N suggestions)`, or `FAIL (N fails · M suggestions)`, each optionally followed by ` · P info`. Omit the suggestion count when there are none, and omit `· P info` when there are none. 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.
```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

@@ -3,7 +3,7 @@ name: agentsmd-author
description: > description: >
Use when the user wants a repo's AGENTS.md written or updated, root or Use when the user wants a repo's AGENTS.md written or updated, root or
nested, including "document this for AI coding tools". Writes only verified nested, including "document this for AI coding tools". Writes only verified
conventions. Not review-only -> `agentsmd-audit`. Not for CLAUDE.md -> conventions. Not review-only -> `agentsmd-audit`. Not converting CLAUDE.md ->
`provider-adapter-author`. `provider-adapter-author`.
allowed-tools: Bash Read Write Edit allowed-tools: Bash Read Write Edit
metadata: metadata:
@@ -36,8 +36,8 @@ AGENTS.md has no required schema. Use only sections that reflect something real
## Step 4 — Check for an existing provider file ## 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. Look for `CLAUDE.md`, `.cursor/rules/*.mdc`, `.github/copilot-instructions.md`, or similar in the target repo. If one exists, invoke the `provider-adapter-author` skill on it to reconcile — whether it duplicates content the AGENTS.md you just wrote/updated now owns, or is merely stale or missing a pointer to it. Never edit it yourself in either case.
## Step 5 — Audit and report ## Step 5 — Audit and report
Invoke the `agentsmd-audit` skill directly on the AGENTS.md file(s) you just wrote or updated. This closeout is mandatory, not optional, even when the change looks trivial — never sign the work off on your own judgment. 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. Invoke the `agentsmd-audit` skill on the target repo root — its validators take a `<repo-root>` and walk the tree for every AGENTS.md themselves; there is no per-file entry point. This closeout is mandatory, not optional, even when the change looks trivial — never sign the work off on your own judgment. 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

@@ -31,6 +31,6 @@ Provide the path to the provider-specific file to convert (and the target repo r
| `tests/validate-adapter.bats` | (source-only) Bats test suite for `scripts/validate-adapter.sh` | | `tests/validate-adapter.bats` | (source-only) Bats test suite for `scripts/validate-adapter.sh` |
Rows marked **(source-only)** exist in the authoring source (`.apm/skills/provider-adapter-author/`) Rows marked **(source-only)** exist in the authoring source (`.apm/skills/provider-adapter-author/`)
but are not present in an installed plugin: `scripts/sync-plugin-content.sh` strips but are not present in an installed plugin: the repo's `scripts/sync-plugin-content.sh` strips
`<category>/<name>/tests` when it generates the flat mirror, because these are dev-time fixtures no `<category>/<name>/tests` when it generates the flat mirror, because these are dev-time fixtures no
plugin host needs to discover (ADR-0017). Run them from a repo checkout, not from an install. plugin host needs to discover (ADR-0017). Run them from a repo checkout, not from an install.

View File

@@ -2,7 +2,8 @@
name: provider-adapter-author name: provider-adapter-author
description: > description: >
Use when a provider file (CLAUDE.md, .cursor rules, copilot-instructions) Use when a provider file (CLAUDE.md, .cursor rules, copilot-instructions)
duplicating the repo's AGENTS.md should be cut to a thin adapter. duplicating the repo's AGENTS.md should be cut to a thin adapter — "make
CLAUDE.md just import AGENTS.md".
Not writing the AGENTS file -> `agentsmd-author`. Not writing the AGENTS file -> `agentsmd-author`.
Not auditing the AGENTS file -> `agentsmd-audit`. Not auditing the AGENTS file -> `agentsmd-audit`.
allowed-tools: Bash Read Edit Write allowed-tools: Bash Read Edit Write
@@ -16,7 +17,6 @@ metadata:
## Gotchas ## Gotchas
- Assume a provider has no cross-file import mechanism until you have confirmed it has one. Claude Code is the exception, not the rule: a `CLAUDE.md` may consist of nothing but `@path` lines, while the same `@AGENTS.md` line in a Cursor rule or a Copilot instructions file is inert text no tool resolves. Pass `--no-import-syntax` to `scripts/validate-adapter.sh` for those providers. - Assume a provider has no cross-file import mechanism until you have confirmed it has one. Claude Code is the exception, not the rule: a `CLAUDE.md` may consist of nothing but `@path` lines, while the same `@AGENTS.md` line in a Cursor rule or a Copilot instructions file is inert text no tool resolves. Pass `--no-import-syntax` to `scripts/validate-adapter.sh` for those providers.
- Works standalone or composed-into by `agentsmd-author` — behave identically either way; don't assume a caller skill exists.
## Step 1 — Detect ## Step 1 — Detect
@@ -43,7 +43,7 @@ Run the bundled check before finishing — this is the skill's own closeout gate
bash scripts/validate-adapter.sh [--no-import-syntax] [--max-lines N] <adapter-file> <agents-md-file> bash scripts/validate-adapter.sh [--no-import-syntax] [--max-lines N] <adapter-file> <agents-md-file>
``` ```
Fix any `FAIL` by editing the provider file, and re-run until it exits `0`. The size `FAIL` advises moving provider-agnostic content into `AGENTS.md`; disregard that half of its wording and delete the redundant lines instead. Fix any `FAIL` by editing the provider file, and re-run until it exits `0`.
## Step 4 — Report ## Step 4 — Report

View File

@@ -16,10 +16,15 @@ Arguments:
Options: Options:
--no-import-syntax The target provider has no native cross-file import --no-import-syntax The target provider has no native cross-file import
mechanism. Accept a plain-text pointer mention of mechanism. Require a plain-text pointer line naming
"AGENTS.md" instead of requiring an @import-style line. "AGENTS.md" instead of an @import-style line; an
@AGENTS.md line alone does not satisfy it, because
such a provider never resolves it. Without this flag
an actual @import line is required, and naming
AGENTS.md in prose alone does not satisfy it.
--max-lines N Max non-blank lines allowed in the adapter file before --max-lines N Max non-blank lines allowed in the adapter file before
it's considered no longer "thin". Default: 60. it's considered no longer "thin". Must be a
non-negative integer. Default: 60.
--help, -h Show this help and exit 0. --help, -h Show this help and exit 0.
Exit codes: Exit codes:
@@ -44,7 +49,15 @@ while [[ $# -gt 0 ]]; do
shift shift
;; ;;
--max-lines) --max-lines)
MAX_LINES="${2:-}" if [[ $# -lt 2 ]]; then
echo "Error: --max-lines requires a value (a non-negative integer)." >&2
exit 1
fi
MAX_LINES="$2"
if [[ ! "$MAX_LINES" =~ ^[0-9]+$ ]]; then
echo "Error: --max-lines expects a non-negative integer, got '$MAX_LINES'." >&2
exit 1
fi
shift 2 shift 2
;; ;;
*) *)
@@ -94,21 +107,25 @@ if not adapter_content.strip():
IMPORT_RE = re.compile(r'(?m)^\s*@\S*AGENTS\.md\s*$') IMPORT_RE = re.compile(r'(?m)^\s*@\S*AGENTS\.md\s*$')
lines = adapter_content.splitlines() lines = adapter_content.splitlines()
import_lines = [ln for ln in lines if IMPORT_RE.match(ln)] import_lines = [ln for ln in lines if IMPORT_RE.match(ln)]
# A prose pointer is any line naming AGENTS.md that is not itself an import
# line — an inert `@AGENTS.md` in a provider that resolves no imports points
# a reader at nothing.
pointer_lines = [ln for ln in lines if not IMPORT_RE.match(ln) and "AGENTS.md" in ln]
if no_import_syntax: if no_import_syntax:
has_reference = "AGENTS.md" in adapter_content has_reference = bool(pointer_lines)
else: else:
has_reference = bool(import_lines) or "AGENTS.md" in adapter_content has_reference = bool(import_lines)
if not has_reference: if not has_reference:
has_fail = True has_fail = True
print(f"FAIL Adapter has no reference to AGENTS.md — {adapter_path}") print(f"FAIL Adapter has no reference to AGENTS.md — {adapter_path}")
if no_import_syntax: 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(" Why: This provider resolves no cross-file import, so the adapter must point at AGENTS.md in prose; an `@AGENTS.md` line here is inert text.")
print(" Fix: Add a sentence like \"See AGENTS.md at the repo root for shared conventions.\"") print(" Fix: Add a sentence like \"See AGENTS.md at the repo root for shared conventions.\"")
else: else:
print(" Why: A thin adapter must import AGENTS.md (e.g. `@AGENTS.md`) rather than silently omitting it.") print(" Why: A thin adapter must import AGENTS.md with an `@AGENTS.md` line; merely naming the file in prose defers nothing.")
print(" Fix: Add an `@AGENTS.md` (or equivalent relative path) import line.") print(" Fix: Add an `@AGENTS.md` (or equivalent relative path) import line, or pass --no-import-syntax if this provider resolves no imports.")
print() print()
# --- Duplication check --- # --- Duplication check ---
@@ -132,7 +149,7 @@ if non_blank_count > max_lines:
has_fail = True has_fail = True
print(f"FAIL Adapter is not thin — {adapter_path}") 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(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(" Fix: Delete the lines already covered by AGENTS.md; keep only genuinely provider-specific additions here.")
print() print()
if has_fail: if has_fail: