diff --git a/AGENTS.md b/AGENTS.md index d4206249..954660a7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -105,7 +105,7 @@ Clarify devcontainer setup steps in README ## Documentation Style Conventions -- **Carried files reference no other fleet repo unless that repo is the subject.** Every file the fleet carries verbatim - `AGENTS.md`, `CODESTYLE.md`, `WORKFLOW.md`, `README.md`, `.github/copilot-instructions.md`, the `repo-config/` and `spec/` files, the carried `AUDIT.md` - is **repo-agnostic**: it describes the shape or pattern self-containedly, names no sibling fleet repo as an illustrative example, and adds no link (inline URL or reference definition) to another fleet repo. This bans only *cross-repo* references - ordinary links to files within the same repo, to anchors, and to external sites are unaffected. Two reasons: any fleet repo may be **private**, so a cross-repo link 404s in a public carrier; and a cross-repo reference **couples** the repos and rots as they diverge. A repo name is allowed only when that repo *is* the subject (the file is about it). To point at a current good example, name it in the **onboarding/conformance issue** (one-off, not carried) or the hub-only `reports/conformance-matrix.md` (a fleet tracking table, not carried) - never in a carried doc. This pairs with the present-tense rule below: state the current shape, not a history of which repo it came from. +- **Carried files reference no other fleet repo unless that repo is the subject.** Every file the fleet carries verbatim - `AGENTS.md`, `CODESTYLE.md`, `WORKFLOW.md`, `README.md`, `.github/copilot-instructions.md`, the `repo-config/` and `spec/` files, the carried `AUDIT.md` - is **repo-agnostic**: it describes the shape or pattern self-containedly, names no sibling fleet repo as an illustrative example, and adds no link (inline URL or reference definition) to another fleet repo. **This includes the template repo itself.** A carried file names it in neither prose nor a link: the template is private, so any reference 404s for a downstream repo's users, and the template flow is machinery a consumer of that repo should never have to see. Where a carried file needs to express a template-level behavior - "report a rule discrepancy upstream" - state the behavior, not the destination; the maintainer supplies the destination out of band. This bans only *cross-repo* references - ordinary links to files within the same repo, to anchors, and to external sites are unaffected. Two reasons: any fleet repo may be **private**, so a cross-repo link 404s in a public carrier; and a cross-repo reference **couples** the repos and rots as they diverge. A repo name is allowed only when that repo *is* the subject (the file is about it). To point at a current good example, name it in the **onboarding/conformance issue** (one-off, not carried) or the hub-only `reports/conformance-matrix.md` (a fleet tracking table, not carried) - never in a carried doc. This pairs with the present-tense rule below: state the current shape, not a history of which repo it came from. ### Markdown diff --git a/spec/audit.py b/spec/audit.py index e01f8861..829004a2 100644 --- a/spec/audit.py +++ b/spec/audit.py @@ -40,6 +40,22 @@ def load(rel): return json.loads((ROOT / rel).read_text(encoding="utf-8")) +def hub_name(): + """This repo's name on the remote, and whether it came from the remote. + + Read from origin rather than the checkout directory, which a differently-named clone, a fork, or a + worktree without an origin would silently break. The caller announces the directory-name fallback: + a silently degraded match is the fail-open case this check exists to prevent. + """ + r = subprocess.run(["git", "config", "--get", "remote.origin.url"], capture_output=True, text=True, cwd=ROOT) + if r.returncode == 0 and r.stdout.strip(): + return r.stdout.strip().rstrip("/").removesuffix(".git").split("/")[-1], True + return ROOT.name, False + + +HUB_NAME, HUB_NAME_FROM_REMOTE = hub_name() + + def gh(path, ok404=False): """GET a REST path via gh; parsed JSON, or None on 404 when ok404. @@ -183,6 +199,14 @@ def audit_repo(entry, spec): for name in sorted(present - claimed_names): findings.append(("DRIFT", f"secrets: {name} in the {store} store is claimed by no applicable mechanism (stale?)")) + # --- Carried files must not reference the template repo --- + # The template is private, so a reference 404s for this repo's users and exposes machinery they cannot + # follow. Checks the agent-instruction files, where a stale "report drift upstream" paragraph spread. + for path in ("AGENTS.md", ".github/copilot-instructions.md"): + doc = gh(f"repos/{slug}/contents/{path}?ref={ground}", ok404=True) + if doc and doc.get("content") and HUB_NAME.lower() in base64.b64decode(doc["content"]).decode("utf-8", "replace").lower(): + findings.append(("DRIFT", f"carried: {path} references the template repo by name or link (private - 404s for this repo's readers; state the behavior, not the destination)")) + # --- Dependabot ecosystem coverage --- # A repo's tree implies Dependabot ecosystems it must track: github-actions when it ships workflows # (the action versions they reference otherwise go stale, and a merge-bot then has no PRs to auto-merge), @@ -257,7 +281,10 @@ def main(): run_utc = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") hub = subprocess.run(["git", "rev-parse", "--short", "HEAD"], capture_output=True, text=True, cwd=ROOT) hub_sha = hub.stdout.strip() if hub.returncode == 0 else "unknown" - print(f"audit run {run_utc} | hub {hub_sha}\n") + print(f"audit run {run_utc} | hub {hub_sha}") + if not HUB_NAME_FROM_REMOTE: + print(f"warning: no git remote; template-reference check falls back to the directory name '{HUB_NAME}' and may miss", file=sys.stderr) + print() hard = 0 for entry in repos: diff --git a/spec/project-types.json b/spec/project-types.json index 6e3966bd..6cd13b8d 100644 --- a/spec/project-types.json +++ b/spec/project-types.json @@ -144,7 +144,7 @@ { "id": "recurring.charset", "verdict": "letter", "assert": "ASCII only in agent-authored text: no em-dash (use a spaced hyphen), no smart quotes, no stray non-ASCII.", "intentRef": "AGENTS.md#character-set" }, { "id": "recurring.spelling", "verdict": "letter", "assert": "US English spelling; the shared cspell.json sets language en-US (a bare en accepts British spellings too).", "intentRef": "CODESTYLE.md#markdown-and-spelling" }, { "id": "recurring.eol", "verdict": "letter", "assert": "Line endings follow .editorconfig, which carries a global [*] end_of_line default plus LF pins for execution-sensitive files (shell, Dockerfiles, shebang-executable .py by path), plus workflow YAML in .github/workflows/* enforced by editorconfig-checker in CI; a per-extension-only form lacking the global [*] default is a drift finding. The global default is CRLF for release repos, or the consuming application's native platform for an operational (config) repo as recorded in the registry lineEndings field (LF for Linux-native/container config e.g. ESPHome/Home Assistant, CRLF for a Windows-native editor e.g. Vantage/Design Center) - do not re-normalize such a repo to CRLF. Edits preserve the file's endings.", "intentRef": "AGENTS.md#line-endings" }, - { "id": "recurring.norepoxref", "verdict": "intent", "assert": "A carried file (AGENTS.md, CODESTYLE.md, WORKFLOW.md, README.md, .github/copilot-instructions.md, repo-config/README.md, repo-config/develop.json, repo-config/main.json, spec/secrets.json, the carried AUDIT.md) names no sibling fleet repo as an illustrative example and adds no link (inline URL or reference definition) to another fleet repo; only cross-repo references are banned - same-repo file/anchor links and external links are fine. A repo name appears only where that repo is the file's subject. Rationale: a cross-repo link 404s in a public carrier when the target is private, and couples the repos. A current example is named in the onboarding/conformance issue or the hub-only reports/conformance-matrix.md, never a carried doc.", "intentRef": "AGENTS.md#documentation-style-conventions" } + { "id": "recurring.norepoxref", "verdict": "intent", "assert": "A carried file (AGENTS.md, CODESTYLE.md, WORKFLOW.md, README.md, .github/copilot-instructions.md, repo-config/README.md, repo-config/develop.json, repo-config/main.json, spec/secrets.json, the carried AUDIT.md) names no other fleet repo as an illustrative example and adds no link (inline URL or reference definition) to one - including the template repo itself, which is private, so a reference 404s for the carrying repo's readers and exposes template machinery they cannot follow; state the behavior, not the destination. spec/audit.py mechanically checks the two agent-instruction files for the template name; only cross-repo references are banned - same-repo file/anchor links and external links are fine. A repo name appears only where that repo is the file's subject. Rationale: a cross-repo link 404s in a public carrier when the target is private, and couples the repos. A current example is named in the onboarding/conformance issue or the hub-only reports/conformance-matrix.md, never a carried doc.", "intentRef": "AGENTS.md#documentation-style-conventions" } ] }, "readme-structure": {