Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 20 additions & 15 deletions docs/PLUGIN-PHILOSOPHY.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,26 @@ statement does not settle it; `plugins/architecture/reference/topic-docs.md` is
this rule entirely, being neither skill, agent, nor schema content — identifying the source is what
the manifest is for.)

Like the setup contract below, **this is a normative target, not a description of the fleet**, and
enforcement reaches a strict subset of it.
`scripts/validate-plugin-contracts.mjs` gates the
marketplace id, `melodic-software/github-iac`, and `MELODIC_*` keys across every plugin's skill
content, and holds the `autonomy` plugin to a stricter token set;
`plugins/github/github.test.sh` sweeps a wider token set over a narrower scope — its own plugin's
prose only — as its "agnostic conformance" check, a sibling of that file's D4 zero-vendored-knowledge
checks, not one of them. The
bare organization name in skill prose is gated nowhere *fleet-wide* — only inside `autonomy` and
`github`, each by a sweep scoped to that one plugin — and agent content is gated nowhere at all, so
shipped skills predating this statement are nonconforming until brought into conformance rather than
absolved by a green build. A fleet-wide edit answers to two independent mechanisms, each running in
its own step of the same `plugin-gate` CI job and neither aware of the other; consolidating them
behind this statement, and
settling that conformance gap deliberately, is tracked in issue #3136.
Like the setup contract below, **this is a normative target, not a description of the fleet**.
Enforcement is the token classes in `scripts/org-agnosticism-tokens.txt` — one data file, every
site either reads it or is a documented narrowing/extension of it:

- **fleet-id / fleet-key** — marketplace id, `melodic-software/github-iac`, and `MELODIC_*` keys,
across every plugin skill `.md` (`scripts/validate-plugin-contracts.mjs`).
- **setup** — setup-skill files must not bind to a marketplace name (same validator).
- **autonomy** — stricter extension: bare organization name and fleet repo names, scoped to the
`autonomy` plugin (`plugin.json` `author` remains exempt).
- **github** — this plugin's markdown only, adding `melodic`, `medley`, and `pulumi`.
`plugins/github/github.test.sh`'s "agnostic conformance" check is that extension, a sibling of
that file's D4 zero-vendored-knowledge sweeps, not one of them. The validator fails if that
test file is missing while the plugin exists, or if its regex drifts from the `github` class.
An unknown class name in the token file is a hard error.

Agent content, schema files, and a fleet-wide bare organization name are **not gated**. That is a
deliberate narrowing of enforcement to the classes above, not an accident a green build absolves.
The independent `portability-lint` job stages a related publisher-token class in
`scripts/skill-portability-tokens.txt`; if that class activates it must consume or align with
`org-agnosticism-tokens.txt` rather than invent a third set.

Keep plugins horizontally decoupled:

Expand Down
41 changes: 41 additions & 0 deletions scripts/org-agnosticism-tokens.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Org-agnosticism token set — the single data file every enforcement site
# reads or is a documented narrowing/extension of.
#
# Doctrine: docs/PLUGIN-PHILOSOPHY.md § "Design boundary".
# Idiom: same as scripts/skill-portability-tokens.txt (data, not gate logic).
#
# Format: blank lines and lines beginning with `#` are ignored. Every other
# line is `<class> <ERE>` (one space). Classes:
#
# fleet-id — marketplace id / publisher repo path, every plugin skill .md
# fleet-key — publisher-prefixed configuration keys, every plugin skill .md
# setup — setup-skill files must not bind to a marketplace name
# autonomy — autonomy plugin (stricter extension: bare org + fleet repos)
# github — github plugin prose (extension: this plugin's markdown).
# plugins/github/github.test.sh still carries the same regex
# as a plugin-local check; validate-plugin-contracts.mjs fails
# if that regex drifts from this class.
#
# Enforcement is deliberately these classes, not the full normative target
# (agent content, schema files, and a fleet-wide bare organization name stay
# ungated). Widening those is a separate decision; do not add a class here
# without remediating the corpus it would fail. An unknown class name is a
# hard error — a typo such as `fleet-keey` must not silently drop tokens.
# If `plugins/github` exists, `plugins/github/github.test.sh` is required
# and its agnostic-conformance regex must match this file's `github` class.
#
# The independent portability-lint job stages a related publisher-token class
# in scripts/skill-portability-tokens.txt. If that class activates, it must
# consume or align with this file rather than invent a third set.

fleet-id @melodic-software\b
fleet-id melodic-software/github-iac
fleet-key \bMELODIC_[A-Z0-9_]+\b
setup @melodic-software\b
autonomy melodic-software
autonomy ci-workflows
autonomy github-iac
github melodic
github medley
github github-iac
github pulumi
5 changes: 5 additions & 0 deletions scripts/skill-portability-tokens.txt
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ origin/(main|master)
# Residue at c7b0af78 — 1 hit / 1 file. Also near-green.
# \*\*/\*\.cs([^a-zA-Z0-9_]|$)

# Publisher-org tokens (marketplace id, org name, fleet repo names, MELODIC_*
# keys) are NOT this file's job. They live in scripts/org-agnosticism-tokens.txt
# and are enforced by plugin-gate (#3136). If a publisher-token class is ever
# enabled here, consume or align with that file — do not invent a third set.
#
# Forge/marketplace-internal hardcode: a hardcoded raw GitHub content URL where a
# portable fetch or a declared scope belongs (#432). Enable when the forge-lock
# members land (e.g. #441 declares source-control's inherent GitHub scope).
Expand Down
95 changes: 90 additions & 5 deletions scripts/validate-plugin-contracts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,62 @@ import process from "node:process";
const root = process.cwd();
const failures = [];

// Org-agnosticism tokens live in scripts/org-agnosticism-tokens.txt — one
// data file, every site either reads it or is a documented extension (#3136).
// The class set is closed: a typo (`fleet-keey`) must fail, not drop tokens.
const ORG_AGNOSTICISM_CLASSES = Object.freeze([
"fleet-id",
"fleet-key",
"setup",
"autonomy",
"github",
]);

function loadOrgAgnosticismTokens() {
const byClass = Object.fromEntries(ORG_AGNOSTICISM_CLASSES.map((cls) => [cls, []]));
const path = join(root, "scripts", "org-agnosticism-tokens.txt");
if (!existsSync(path)) {
failures.push(`scripts/org-agnosticism-tokens.txt: missing (org-agnosticism SSOT)`);
return byClass;
}
for (const raw of read(path).split(/\r?\n/)) {
if (!raw || raw.startsWith("#")) continue;
const match = raw.match(/^(\S+)\s+(\S+)\s*$/);
if (!match) {
failures.push(`scripts/org-agnosticism-tokens.txt: malformed line: ${raw}`);
continue;
}
const [, cls, ere] = match;
if (!ORG_AGNOSTICISM_CLASSES.includes(cls)) {
failures.push(
`scripts/org-agnosticism-tokens.txt: unknown class ${cls} (want ${ORG_AGNOSTICISM_CLASSES.join(", ")})`,
);
continue;
}
byClass[cls].push(ere);
}
for (const cls of ORG_AGNOSTICISM_CLASSES) {
if (byClass[cls].length === 0) {
failures.push(`scripts/org-agnosticism-tokens.txt: no tokens for class ${cls}`);
}
}
return byClass;
}

function orgAgnosticismRegex(pats) {
if (!pats || pats.length === 0) return null;
return new RegExp(pats.join("|"), "i");
}

const orgAgnosticismPats = loadOrgAgnosticismTokens();
const orgTokens = {
fleetId: orgAgnosticismRegex(orgAgnosticismPats["fleet-id"]),
fleetKey: orgAgnosticismRegex(orgAgnosticismPats["fleet-key"]),
setup: orgAgnosticismRegex(orgAgnosticismPats.setup),
autonomy: orgAgnosticismRegex(orgAgnosticismPats.autonomy),
github: orgAgnosticismRegex(orgAgnosticismPats.github),
};

function filesUnder(directory) {
if (!existsSync(directory)) return [];
const files = [];
Expand Down Expand Up @@ -72,17 +128,17 @@ for (const path of setupContractFiles) {
if (/pluginConfigs\s*\[\s*["'][^"']+@/i.test(content)) {
fail(path, "must not write marketplace-qualified pluginConfigs keys");
}
if (/@melodic-software\b/i.test(content)) {
if (orgTokens.setup && orgTokens.setup.test(content)) {
fail(path, "must not bind setup behavior to a marketplace name");
}
}

for (const path of pluginFiles.filter((path) => /[\\/]skills[\\/].*\.md$/.test(path))) {
const content = read(path);
if (/@melodic-software\b|melodic-software\/github-iac/i.test(content)) {
if (orgTokens.fleetId && orgTokens.fleetId.test(content)) {
fail(path, "reusable skill content must not require publisher-specific runtime identifiers");
}
if (/\bMELODIC_[A-Z0-9_]+\b/.test(content)) {
if (orgTokens.fleetKey && orgTokens.fleetKey.test(content)) {
fail(path, "reusable skill content must not introduce publisher-prefixed configuration");
}
}
Expand Down Expand Up @@ -244,7 +300,7 @@ if (existsSync(aiBriefingBrandOverlay)) {
// replace them; tool-specific detail lives in SKILL.md/README.
const autonomyRoot = join(pluginRoot, "autonomy");
if (existsSync(autonomyRoot)) {
const fleetTokens = /melodic-software|ci-workflows|github-iac/i;
const fleetTokens = orgTokens.autonomy;
const vendorTokens = /github|gitlab|bitbucket|slack|anthropic|claude|openai|copilot|cursor|devin/i;
const autonomyReference = join(autonomyRoot, "reference") + sep;
for (const path of filesIn(autonomyRoot)) {
Expand All @@ -255,7 +311,7 @@ if (existsSync(autonomyRoot)) {
delete manifest.author;
content = JSON.stringify(manifest);
}
if (fleetTokens.test(content)) {
if (fleetTokens && fleetTokens.test(content)) {
fail(path, "autonomy plugin must not name the org or fleet repos (binding-seam owns instances)");
}
if (path.startsWith(autonomyReference) && vendorTokens.test(content)) {
Expand Down Expand Up @@ -319,6 +375,35 @@ if (existsSync(marketplacePath)) {
}
}

// github.test.sh's agnostic-conformance regex is a documented extension of
// this file's `github` class — same tokens, plugin-local reach. Drift here
// would recreate the two-set problem #3136 closed. If the plugin exists, the
// test file is required — a missing file must not skip the alignment check.
{
const githubPlugin = join(pluginRoot, "github");
const githubTest = join(pluginRoot, "github", "github.test.sh");
if (existsSync(githubPlugin) && statSync(githubPlugin).isDirectory()) {
if (!existsSync(githubTest)) {
fail(
githubPlugin,
"github.test.sh is missing; keep the agnostic-conformance grep aligned with scripts/org-agnosticism-tokens.txt class github",
);
} else if (orgTokens.github) {
const source = read(githubTest);
const found = source.match(/grep -riEn "([^"]+)" "\$PLUGIN_DIR" --include='\*\.md'/);
const expected = orgTokens.github.source;
if (!found) {
fail(githubTest, "agnostic-conformance grep not found; keep it aligned with scripts/org-agnosticism-tokens.txt class github");
} else if (found[1] !== expected) {
fail(
githubTest,
`agnostic-conformance regex drifted from scripts/org-agnosticism-tokens.txt class github (file has ${found[1]}; tokens file has ${expected})`,
);
}
}
}
}

if (failures.length > 0) {
console.error("Plugin contract validation failed:");
for (const failure of failures) console.error(`- ${failure}`);
Expand Down