diff --git a/.github/labeler.yml b/.github/labeler.yml index 7ece498..2eb3673 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -4,13 +4,13 @@ - any-glob-to-any-file: ["src/**"] "scope:packaging": - changed-files: - - any-glob-to-any-file: ["scripts/**", ".forgejo/workflows/release.yml"] + - any-glob-to-any-file: ["scripts/**", ".forgejo/workflows/release.yml", "package.json", "package-lock.json", "CHANGELOG.md", "changelog.d/**"] "scope:manifests": - changed-files: - any-glob-to-any-file: ["manifests/**"] "scope:ci": - changed-files: - - any-glob-to-any-file: [".forgejo/workflows/**"] + - any-glob-to-any-file: [".forgejo/workflows/**", ".github/**", ".ceremony/**"] "scope:docs": - changed-files: - - any-glob-to-any-file: ["README.md", "docs/**"] + - any-glob-to-any-file: ["*.md", "docs/**"] diff --git a/.github/labels.conf b/.github/labels.conf index cd4c22b..4c25eae 100644 --- a/.github/labels.conf +++ b/.github/labels.conf @@ -1,7 +1,7 @@ panel=codex-bot-andresmgsl glm-bot-andresmgsl claude-bot-andresmgsl kimi-bot-andresmgsl triage-actors=claude-bot-andresmgsl scope:cli|C5DEF5|src/ — the command surface (cli.js, api.js, config.js) -scope:packaging|C5DEF5|scripts/ and the release workflow — deb build, registry publish, apt install path +scope:packaging|C5DEF5|scripts/, release workflow, package manifests, changelog, and fragments — release packaging and version surfaces scope:manifests|C5DEF5|manifests/ — the fleet repo registry data -scope:ci|C5DEF5|.forgejo/workflows/ — the test and label gates -scope:docs|C5DEF5|README and docs/ — the prose contract +scope:ci|C5DEF5|.forgejo/workflows/, .github/, and .ceremony/ — the test, label, and governance gates +scope:docs|C5DEF5|root Markdown and docs/ — the prose contract diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fa39ee7..bc3541a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,10 +20,10 @@ authoritative; this table is its human-readable restatement. | Scope | Covers | | --- | --- | | `scope:cli` | `src/` — the command surface (`cli.js`, `api.js`, and `config.js`) | -| `scope:packaging` | `scripts/` and the release workflow — Debian package builds, registry publishing, and the APT install path | +| `scope:packaging` | `scripts/`, release workflow, package manifests, changelog, and fragments — release packaging and version surfaces | | `scope:manifests` | `manifests/` — fleet repository registry data | -| `scope:ci` | `.forgejo/workflows/` — test and label gates | -| `scope:docs` | `README.md` and `docs/` — the prose contract | +| `scope:ci` | `.forgejo/workflows/`, `.github/`, and `.ceremony/` — the test, label, and governance gates | +| `scope:docs` | root Markdown and `docs/` — the prose contract | These names and path descriptions restate the scope rows in [`.github/labels.conf`](.github/labels.conf). diff --git a/test/governance.test.js b/test/governance.test.js index 1d64884..346b4e2 100644 --- a/test/governance.test.js +++ b/test/governance.test.js @@ -1,6 +1,7 @@ const { test } = require('node:test'); const assert = require('node:assert/strict'); const { spawn } = require('node:child_process'); +const { execFileSync } = require('node:child_process'); const fs = require('node:fs'); const http = require('node:http'); const os = require('node:os'); @@ -120,20 +121,24 @@ test('repository governance config resolves the current four-member panel and fi }); }); -test('repository scope mapping covers every configured scope with the ruled paths', () => { +test('repository scope mapping covers every tracked path except the ruled residue', () => { const labeler = fs.readFileSync(REPOSITORY_LABELER, 'utf8'); - const expected = { - 'scope:cli': ['src/**'], - 'scope:packaging': ['scripts/**', '.forgejo/workflows/release.yml'], - 'scope:manifests': ['manifests/**'], - 'scope:ci': ['.forgejo/workflows/**'], - 'scope:docs': ['README.md', 'docs/**'], - }; + const globs = [...labeler.matchAll(/any-glob-to-any-file:\s*(\[[^\n]+\])/g)] + .flatMap((match) => JSON.parse(match[1])); + const tracked = execFileSync('git', ['ls-files'], { + cwd: path.join(__dirname, '..'), + encoding: 'utf8', + }).trim().split('\n'); + const allowed = new Set([ + '.gitignore', + 'assets/logo-mark.svg', + ...tracked.filter((filename) => /^test\/[^/]+\.test\.js$/.test(filename)), + ]); + const uncovered = tracked.filter((filename) => ( + !allowed.has(filename) && !globs.some((glob) => path.matchesGlob(filename, glob)) + )); - for (const [label, globs] of Object.entries(expected)) { - assert.match(labeler, new RegExp(`^"${label}":`, 'm'), `${label} has no mapping`); - for (const glob of globs) assert.ok(labeler.includes(JSON.stringify(glob)), `${label} does not map ${glob}`); - } + assert.deepEqual(uncovered, []); }); test('package lock versions match the package manifest', () => {