Skip to content

Carried files must not reference the template repo; mechanize the check - #359

Merged
ptr727 merged 2 commits into
developfrom
no-hub-refs-in-carried-files
Jul 18, 2026
Merged

Carried files must not reference the template repo; mechanize the check#359
ptr727 merged 2 commits into
developfrom
no-hub-refs-in-carried-files

Conversation

@ptr727

@ptr727 ptr727 commented Jul 18, 2026

Copy link
Copy Markdown
Owner

The template is private, so any reference to it from a carried file 404s for that repo's readers and exposes template machinery a consumer should never have to see. Raised from downstream work where the references were being stripped by hand, repo by repo.

Scope of the leak

Swept every cataloged repo. 13 of 20 reference the template; 8 are public:

repo public files
Utilities yes copilot-instructions, HISTORY
LanguageTags yes copilot-instructions
aiopurpleair yes copilot-instructions
homeassistant-purpleair yes AGENTS, copilot-instructions
PlexCleaner yes copilot-instructions
VSCode-Server-DotNetCore yes copilot-instructions
NxWitness yes AGENTS, copilot-instructions
ESPHome-Config yes AGENTS
Financial-Modeling / HomeAutomation-Config / KiCadLibrary / HomeAssistant-Config / MediaTools no assorted

The dominant instance is a stale copilot-instructions.md paragraph telling agents to report drift upstream, which hard-links the private template twice - and one link targets an AGENTS.md#staying-in-sync-and-reporting-drift-upstream section the hub no longer has, so it 404s even for someone with access. The hub itself carries neither the paragraph nor that section: this is stale carried content that outlived its source (#305 territory, and exactly the "prose asserting the old behavior" rule from #356).

The rule

#344 barred sibling-repo references but arguably let the template through as "the subject". AGENTS.md now says explicitly:

This includes the template repo itself. A carried file names it in neither prose nor a link ... 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.

That preserves the agent-to-agent reporting flow (which is real and valuable) while keeping it out of a public file.

Mechanized, not left to review

This spread silently across 13 repos, so spec/audit.py now checks the two agent-instruction files for the template name and raises a drift finding.

The template name is derived from the git remote, not the checkout directory name - a clone into a differently-named folder would otherwise stop detecting with no signal, which is the fail-open case the check exists to prevent (AGENTS.md "Verification Discipline": gates fail loud, never narrow quietly).

PlexCleaner     -> DRIFT carried: .github/copilot-instructions.md references the template repo ...
ESPHome-Config  -> DRIFT carried: AGENTS.md references the template repo ...
Vantage-Config  -> clean

Downstream

Not fixed here - the 13 carried copies need the reference removed in their own repos. Raising the delivery shape (per-repo issues vs. folding into each repo's existing conformance issue) rather than filing 13 issues unilaterally.

spec/validate.py, markdownlint, editorconfig-checker: clean.

🤖 Generated with Claude Code

The private-template reference has spread to 13 of 20 downstream repos,
8 of them public. The dominant instance is a stale copilot-instructions
paragraph telling agents to report drift upstream, which hard-links the
private template twice - and links an AGENTS.md section the hub no
longer has, so it 404s even with access.

The #344 rule barred sibling-repo references but arguably let the hub
through as "the subject". It now says explicitly that a carried file
names the template in neither prose nor link: the template is private,
so the link 404s for that repo's readers, and the flow is machinery a
consumer should never see. Where a carried file must express a
template-level behavior ("report a rule discrepancy upstream"), it
states the behavior and not the destination; the maintainer supplies
the destination out of band.

Mechanized in spec/audit.py rather than left to review, since this
spread silently across the fleet: the two agent-instruction files are
checked for the template name. The name is derived from the git remote,
not the checkout directory - a differently-named clone would otherwise
stop detecting silently, the fail-open case the check exists to prevent.

Verified: flags PlexCleaner (copilot-instructions) and ESPHome-Config
(AGENTS.md); Vantage-Config clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 18, 2026 23:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an explicit governance rule that carried files must not reference the private template repo, and mechanizes enforcement by extending the fleet audit to detect template-name mentions in agent-instruction files.

Changes:

  • Update the “no cross-repo references” rule to explicitly include the template repo itself.
  • Extend spec/audit.py to flag template-repo references in AGENTS.md and .github/copilot-instructions.md across audited repos.
  • Align the machine-readable audit spec (spec/project-types.json) with the new rule and enforcement mechanism.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
spec/project-types.json Updates the recurring “no cross-repo references” rule text to explicitly include the template repo and mention the mechanized audit check.
spec/audit.py Adds template-repo name derivation and a new audit finding when agent-instruction files reference the template repo name/link.
AGENTS.md Clarifies documentation-style conventions to explicitly forbid references to the template repo in carried files.

Comment thread spec/audit.py Outdated
Comment thread spec/audit.py
…pilot #359)

Two ways the check could miss silently:

- The match was case-sensitive, so a lowercased URL
  (github.com/ptr727/projecttemplate) would pass. Compares casefolded
  now; GitHub treats repo names case-insensitively.
- hub_name()'s directory-name fallback was itself silent, so a fork,
  worktree, or remoteless checkout would degrade the check with no
  signal. It now reports whether the name came from the remote, and the
  run prints a stderr warning when it did not.

Both are the fail-open case this check exists to prevent, so neither
should be quiet about it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 18, 2026 23:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

spec/audit.py:53

  • hub_name() only reads remote.origin.url. If someone runs this audit from a fork (origin points to the fork), the template-reference check will scan for the fork repo name and can miss references to the upstream template repo with no warning (the docstring explicitly calls out forks as a fail-open scenario). Prefer upstream when available, then fall back to origin, and only then to ROOT.name (with the existing warning).
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

@ptr727
ptr727 merged commit 3f562c3 into develop Jul 18, 2026
7 checks passed
@ptr727
ptr727 deleted the no-hub-refs-in-carried-files branch July 18, 2026 23:51
ptr727 added a commit that referenced this pull request Jul 19, 2026
Addresses **item 1** of #357. Item 2 (the two prose/comment style
proposals) is deliberately not in this PR - it is a house-style decision
I am putting to the owner separately.

## The gap this closes

#359 fixed the **hard** leak: a carried file naming or linking the
private template, which 404s for that repo's readers. It also mechanized
a check for it.

A downstream agent caught the **soft** half, which that check cannot see
because it greps for the repo name: carried prose that attributes a rule
to *"the template"*.

> "A repo owner may widen their own CI file list, but **the template
ships** README + HISTORY as the default"
> "Guarantees say 'default branch' portably; **the template implements
it** as the literal `main`"

No repo is named, so nothing 404s - but it still exposes a coordinator
the reader cannot reach, and it frames the repo's own rule as someone
else's. Genuinely good catch; the durable fix belongs here, as the filer
said, or it returns on every reconvergence.

## What changed

Eleven attributions across `AGENTS.md`, `CODESTYLE.md`, and
`WORKFLOW.md` now state the rule directly:

| before | after |
|---|---|
| "the template ships README + HISTORY as the default" | "README +
HISTORY are the default" |
| "the template's default Python profile" | "the default Python profile"
|
| "the template uses `client-id: ...`" | "use `client-id: ...`" |
| "the template implements it as the literal `main`" | "it is
implemented as the literal `main`" |
| "the template constrains only that the output be input-deterministic"
| "the constraint is only that the output be input-deterministic" |
| "not when it matches the template byte for byte" | "not when it
matches a reference implementation byte for byte" |
| "the template ships the tracker but not this consumer wiring" | "the
tracker ships without this consumer wiring" |
| "by exporting the template's two rulesets" | "by importing the
committed `repo-config/*.json` ruleset payloads" |
| "Because the template has no such leaf" | "Because no such leaf ships"
|

Behavior is unchanged throughout; only the framing moves from *reported*
to *owned*.

**One occurrence remains by necessity:** the rule that bans the practice
(`AGENTS.md` "This includes the template repo itself"). It cannot be
stated without referring to the concept, and it names no repo and gives
no location.

Two later passes caught what the first regex missed - "this template"
(AGENTS.md, CODESTYLE.md) and "the `release` template" (WORKFLOW.md).
Remaining matches for the word are different meanings, not attributions:
Serilog structured message templates, and the `reports/_template.md`
filename.

## Note on the mechanical check

`spec/audit.py` greps for the repo **name**, so it catches the hard leak
but not this class - phrasing is a judgment call and a keyword sweep for
"the template" would false-positive on legitimate uses (this PR's own
rule statement, for one). Flagging that limitation rather than
pretending the check covers it.

`spec/validate.py`, markdownlint, editorconfig-checker: clean. CRLF
preserved.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ptr727 added a commit that referenced this pull request Jul 19, 2026
Reported from a downstream session: Copilot insisted on removing a
`README.md` link pointing to a related device-builder repo, citing the
no-cross-repo-link rule. That link was legitimate content. The rule I
wrote in #344/#359 made that misreading reasonable, so this fixes the
rule rather than the reviewer.

## Two defects in the rule as written

**1. It led with the mechanism and buried the carve-out.** The sentence
opened with "adds no link (inline URL or reference definition) to
another fleet repo" and only qualified it fifteen lines later with
"unless that repo is the subject". Read top-down, the ban looks
absolute.

**2. It claimed `README.md` is carried verbatim. It is not.**
`spec/files.json` gives `README.md` neither `whole: true` nor
`sections`, so it is a per-repo file governed only by
`readme-structure.md`. A repo's own README is its own content. The rule
never should have reached it, which is exactly where the
over-application landed.

## Restated by intent

Two things banned in carried files:

- **Any reference to the template repo**, prose or link. Private, so it
404s, and the coordination flow is machinery a consumer should not see.
- **A sibling fleet repo named as an illustrative example** ("repo X
does it this way", "see repo Y's adoption"). That couples repos and rots
as they diverge.

And explicitly permitted:

> **A contextually relevant link to a related project is not a
coordination reference, and is expected.** Where another repo is part of
this repo's subject matter - the image that consumes this config, the
builder that generates this hardware, a library this depends on - link
it normally. The test is whether the link serves a reader of *this*
repo's content, not whether the target happens to be in the fleet.

Plus the scope correction: the rule governs carried template content,
and a repo's own `README.md` and topical docs are outside it.

`spec/project-types.json`'s `recurring.norepoxref` is reworded to match.

## The mechanical check needed no change

`spec/audit.py` greps only for the **template name** in the two
agent-instruction files. It never looked for sibling links, so it was
not the source of the over-application and its behavior is unchanged -
re-verified against PlexCleaner (flags) and Vantage-Config (clean).

`spec/validate.py`, markdownlint, editorconfig-checker: clean.

*Downstream note: ESPHome-NonRoot is amending its local AGENTS.md for
the same reason. Once this lands, its next carry converges on this
wording.*

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants