@event4u/agent-config 5.1.0 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Shared agent configuration \u2014 skills for AI coding tools (Claude Code, Augment, Cursor, Cline, Windsurf, Gemini CLI).",
9
- "version": "5.1.0",
9
+ "version": "5.2.0",
10
10
  "keywords": [
11
11
  "agent-config",
12
12
  "skills",
package/CHANGELOG.md CHANGED
@@ -802,6 +802,22 @@ our recommendation order, not its support status.
802
802
  > that forces a new era split (`# Era: 4.6.x`, etc.) — see
803
803
  > [`docs/contracts/CHANGELOG-conventions.md § Era splits`](docs/contracts/CHANGELOG-conventions.md).
804
804
 
805
+ ## [5.2.0](https://github.com/event4u-app/agent-config/compare/5.1.0...5.2.0) (2026-05-29)
806
+
807
+ ### Features
808
+
809
+ * **ci:** reject sloppy commit subjects before they leak into the changelog ([26a94e9](https://github.com/event4u-app/agent-config/commit/26a94e9e0f07e862a135cfc96b742a999e7131ec))
810
+
811
+ ### Documentation
812
+
813
+ * **adr:** land ADR-033 distribution-identity npm-primary ([63d38cf](https://github.com/event4u-app/agent-config/commit/63d38cf75df6506218cfc6bc17f0b5ba0c90fe9d))
814
+
815
+ ### Chores
816
+
817
+ * **roadmaps:** flip distribution-identity Phase 1-3 + regen dashboard ([0885b9d](https://github.com/event4u-app/agent-config/commit/0885b9d4dda67a2439fc5292367249fa99766a02))
818
+
819
+ Tests: 5186 (+26 since 5.1.0)
820
+
805
821
  ## [5.1.0](https://github.com/event4u-app/agent-config/compare/5.0.0...5.1.0) (2026-05-29)
806
822
 
807
823
  ### Features
package/README.md CHANGED
@@ -37,10 +37,15 @@ Beyond software: [`user-types/`](packages/core/.agent-src.uncondensed/user-types
37
37
 
38
38
  <p align="center">
39
39
  <a href="CHANGELOG.md">CHANGELOG</a> ·
40
+ <a href="CHANGELOG.md#breaking--v400-unified-setup-road-to-unified-setup">Breaking changes</a> ·
40
41
  <a href="https://github.com/event4u-app/agent-config/releases/latest">Latest release</a> ·
41
42
  <a href="https://github.com/event4u-app/agent-config/discussions">Discussions</a>
42
43
  </p>
43
44
 
45
+ <p align="center">
46
+ <sub>Distribution: <code>npm install @event4u/agent-config</code> · npm-primary per <a href="docs/decisions/ADR-033-distribution-identity-npm-primary.md">ADR-033</a>. Major bumps follow <a href="CONTRIBUTING.md#versioning-policy">semver</a>; each ships a <a href="CHANGELOG.md#breaking--v400-unified-setup-road-to-unified-setup"><code>### Breaking</code></a> entry.</sub>
47
+ </p>
48
+
44
49
  ---
45
50
 
46
51
  ## Use it in your project
@@ -1,6 +1,6 @@
1
1
  # Discovery — Deprecation Report
2
2
 
3
- - Generated: `2026-05-29T12:00:12Z`
3
+ - Generated: `2026-05-29T14:14:52Z`
4
4
  - Deprecated artefacts: **0**
5
5
 
6
6
  _None. Tree is clean._
@@ -9565,7 +9565,7 @@
9565
9565
  "reason": "scaffold for new SKILL.md authoring"
9566
9566
  }
9567
9567
  ],
9568
- "generated_at": "2026-05-29T12:00:12Z",
9568
+ "generated_at": "2026-05-29T14:14:52Z",
9569
9569
  "packs": [
9570
9570
  {
9571
9571
  "artefact_count": 85,
@@ -1 +1 @@
1
- 6e36d1bdab54a687328a853b8cbeee77060ad359c006180c0109daa959eba603 discovery-manifest.json
1
+ c0a417183acd5273d0cd21648816539b30da57dd1840f7e967f67ef90a1cffe5 discovery-manifest.json
@@ -1,6 +1,6 @@
1
1
  # Discovery Manifest — Summary
2
2
 
3
- - Generated: `2026-05-29T12:00:12Z`
3
+ - Generated: `2026-05-29T14:14:52Z`
4
4
  - Scanner: `d75eba636abb`
5
5
  - Artefacts: **433**
6
6
  - Unassigned: **0**
@@ -1,6 +1,6 @@
1
1
  # Discovery — Orphan Report
2
2
 
3
- - Generated: `2026-05-29T12:00:12Z`
3
+ - Generated: `2026-05-29T14:14:52Z`
4
4
  - Orphan artefacts: **0**
5
5
 
6
6
  > An orphan is an artefact whose declared pack has no other members.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "checksum": "sha256:653636e679543062954910f794e458eb8424d7f2618b889a29969d21c74fcf0e",
3
- "generated_at": "2026-05-29T12:00:12Z",
3
+ "generated_at": "2026-05-29T14:14:52Z",
4
4
  "packs": [
5
5
  {
6
6
  "artefact_count": 85,
@@ -1,6 +1,6 @@
1
1
  # Discovery — Trust Report
2
2
 
3
- - Generated: `2026-05-29T12:00:12Z`
3
+ - Generated: `2026-05-29T14:14:52Z`
4
4
  - Workspaces tracked: **8**
5
5
  - Human-review-required artefacts: **2**
6
6
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "checksum": "sha256:653636e679543062954910f794e458eb8424d7f2618b889a29969d21c74fcf0e",
3
- "generated_at": "2026-05-29T12:00:12Z",
3
+ "generated_at": "2026-05-29T14:14:52Z",
4
4
  "scanner_version": "d75eba636abb",
5
5
  "workspaces": [
6
6
  {
@@ -9,7 +9,7 @@
9
9
  "homepage": "https://github.com/event4u-app/agent-config#readme",
10
10
  "name": "@event4u/agent-config",
11
11
  "repository": "https://github.com/event4u-app/agent-config",
12
- "version": "5.1.0"
12
+ "version": "5.2.0"
13
13
  },
14
14
  "registries": [
15
15
  {
@@ -0,0 +1,81 @@
1
+ ---
2
+ adr: 033
3
+ status: accepted
4
+ date: 2026-05-29
5
+ decision: distribution-identity-npm-primary
6
+ supersedes: —
7
+ superseded_by: —
8
+ phase: distribution-identity
9
+ type: structural
10
+ review_date: 2026-08-29
11
+ ---
12
+
13
+ # ADR-033 — Distribution identity: npm-primary, Packagist deprecated-in-place
14
+
15
+ ## Status
16
+
17
+ **Accepted** · 2026-05-29. AI Council (claude-sonnet-4-5 + gpt-4o, analysis lens, 2026-05-29) converged on **npm-primary with Packagist deprecated-in-place**. No maintainer veto surfaced; the de-facto evidence and the operational-honesty argument both point to a single answer. Review date 2026-08-29.
18
+
19
+ ## Context
20
+
21
+ External feedback rounds 10 / 12 / 13 returned to two recurring symptoms — *"Packagist still shows 1.0.4"* and *"two major bumps in six days with no breaking-change signal"*. A council deliberation (lens: analysis) re-framed both as **one distribution-identity question** the repo has answered in practice but never recorded:
22
+
23
+ - **`package.json` at `5.1.0`** — the package is past the unified-setup 4.0.0 major and a subsequent 5.0.0 line; semver discipline (per `CONTRIBUTING.md § Versioning policy`) treats installer-layout changes as major, so the cadence is policy-correct.
24
+ - **`scripts/release.py` invokes `npm publish` exclusively** — the only publish surface that ships from a release run.
25
+ - **No `composer.json` exists in this repo** — the file was removed during the npm pivot (pre-3.x era). The Packagist `event4u/agent-config` 1.0.4 listing is a zombie pointing at a repository state that no longer exists.
26
+ - **No automation syncs the Composer surface** — none has existed since the pivot. Pretending the channel is supported is a maintenance lie.
27
+ - **ADR-027** already locks the changelog-machine path (`scripts/release.py` derives `CHANGELOG.md § Breaking` from Conventional Commits). The "no breaking-change signal" symptom is stale against that surface — what is missing is a one-click pointer for consumers.
28
+
29
+ Council framing — two perspectives surfaced:
30
+
31
+ **Lens A — Strategic / consumer-positioning.** A consumer who lands on Packagist sees 1.0.4 and installs an artefact the repo cannot support. The honest signal is deprecation; dual-track without resources is worse than a single declared channel.
32
+
33
+ **Lens B — Technical / operational-honesty.** A channel is "supported" only if a release pipeline publishes to it. Composer/Packagist has had no such pipeline since the npm pivot. The simpler invariant — *one channel, one truth* — wins on every measurable axis: maintenance load, consumer trust, audit clarity.
34
+
35
+ Both lenses converged.
36
+
37
+ ## Decision
38
+
39
+ 1. **The package is npm-primary.** The canonical install path is `npm install @event4u/agent-config` (and the consumer-facing `npx @event4u/agent-config install` wizard). All release tooling (`scripts/release.py`, `package.json`, `CONTRIBUTING.md § Versioning policy`) already aligns with this surface — this ADR records the policy, no code changes follow from the declaration itself.
40
+
41
+ 2. **Composer / Packagist is deprecated-in-place.** No `composer.json` ships from this repo; no PHP autoload surface is supported; the Packagist `event4u/agent-config` 1.0.4 listing is treated as legacy and gets a deprecation pointer through the only mechanism available to a deleted-composer-json repo: a registry-side claim/archive action by the maintainer. The corresponding human-owner item is surfaced in `docs/distribution/registries.md` (see Phase 2 of the [`road-to-distribution-identity.md`](../../agents/roadmaps/road-to-distribution-identity.md) roadmap).
42
+
43
+ 3. **Breaking-change communication uses `CHANGELOG.md § Breaking`.** ADR-027 locked the auto-generated changelog from Conventional Commits. This ADR adds one consumer-facing affordance — a README / distribution-doc pointer linking that section — so a consumer who sees a major-version bump has a one-click path to *what broke, what to do*. No new `BREAKING_CHANGES.md` file unless the maintainer prefers one.
44
+
45
+ 4. **Commit-subject hygiene is enforced in CI.** Because the changelog generator reads commit subjects verbatim, sloppy subjects (`leftover`, `wip`, `temp`, `fixup`, or sub-10-character one-word entries) leak directly into the public changelog. A CI lint (`task lint-commit-subjects` or sibling) rejects those subjects on PR. This is wired in Phase 3 of the same roadmap and is the one hygiene item that ties directly to distribution identity.
46
+
47
+ ## Consequences
48
+
49
+ **Positive:**
50
+
51
+ - The distribution story is **single-channel, single-truth**. Anyone scanning the package — consumer, contributor, auditor, package-registry crawler — gets one consistent answer.
52
+ - The zombie Packagist listing no longer misdirects PHP-shop consumers (once the maintainer files the registry-side archive action).
53
+ - Breaking-change discoverability is two clicks from the README: "release notes" → `CHANGELOG.md § Breaking`. No bespoke `BREAKING_CHANGES.md` to maintain.
54
+ - CI catches sloppy commit subjects before they become public changelog lines — the changelog is as clean as the gate that feeds it.
55
+
56
+ **Negative / accepted costs:**
57
+
58
+ - A PHP-shop consumer who depends on the 1.0.4 Packagist release is left behind. Mitigation: clear deprecation notice + npm install pointer in the registry-side archive.
59
+ - The maintainer must perform a one-time login at `packagist.org/packages/event4u/agent-config` to claim or archive the listing — autonomous tooling cannot do that.
60
+ - Future re-entry into the Composer ecosystem would require a new ADR superseding this one. That cost is acceptable: a re-entry would be a strategic redirect, not a quiet re-add.
61
+
62
+ **Operationally neutral:**
63
+
64
+ - `scripts/release.py` already does the right thing; no script change follows from this ADR.
65
+ - `CONTRIBUTING.md § Versioning policy` already does the right thing; no policy change follows from this ADR. The major bumps that prompted the feedback (4.0.0 unified-setup, 5.0.0 follow-up) were policy-correct under the existing rule.
66
+
67
+ ## Alternatives considered
68
+
69
+ - **Dual-track with auto-sync.** Rejected. The repo carries no Composer surface to sync; restoring one would mean re-introducing a `composer.json` + a sync pipeline + a PHP-side autoload story, all to support a consumer base nobody has named. The carrying cost outweighs the demand signal.
70
+ - **Dual-track without auto-sync (status quo).** Rejected. This is the failure mode that produced the external feedback in the first place — a stale 1.0.4 listing pretends a channel is supported when it is not. Operational-honesty argument carries.
71
+ - **Re-introduce composer.json as a stub linking to npm.** Rejected. A stub on Packagist is still a Packagist artifact; consumers may still try to `composer require` it and hit a non-functional package. Registry-side archive is the cleaner signal.
72
+ - **A bespoke `BREAKING_CHANGES.md` file.** Rejected by default; ADR-027 already locks the machine-generated changelog as the breaking-change surface. Maintainer may revisit if the changelog format proves insufficient for end-of-life or migration-guide-shape communication.
73
+
74
+ ## References
75
+
76
+ - [`agents/roadmaps/road-to-distribution-identity.md`](../../agents/roadmaps/road-to-distribution-identity.md) — the work-item plan this ADR underwrites.
77
+ - [`ADR-027-changelog-machine-vs-manual.md`](ADR-027-changelog-machine-vs-manual.md) — the prior decision locking the auto-generated changelog.
78
+ - [`CONTRIBUTING.md § Versioning policy`](../../CONTRIBUTING.md) — the semver discipline this ADR confirms.
79
+ - [`docs/distribution/registries.md`](../distribution/registries.md) — external-registry submission posture; this ADR adds a distribution-channel-identity section to that file.
80
+ - `scripts/release.py` — the `npm publish` release pipeline.
81
+ - External feedback rounds 10 / 12 / 13 (private session transcripts; convergence summary inlined above to keep this ADR self-contained).
@@ -36,6 +36,7 @@ _Auto-generated by `scripts/adr/regenerate_index.py`. Do not edit._
36
36
  | [ADR-030](ADR-030-claude-code-command-projection.md) | Claude Code Command Projection | accepted | 2026-05-28 | — |
37
37
  | [ADR-031](ADR-031-validation-severity-tiers-and-projection-roundtrip.md) | Validation Severity Tiers And Projection Roundtrip | accepted | 2026-05-29 | — |
38
38
  | [ADR-032](ADR-032-linked-projects-scope.md) | Linked Projects Scope Go Option A | accepted | 2026-05-29 | — |
39
+ | [ADR-033](ADR-033-distribution-identity-npm-primary.md) | Distribution Identity Npm Primary | accepted | 2026-05-29 | — |
39
40
 
40
41
  ## Unnumbered (legacy)
41
42
 
@@ -4,6 +4,35 @@ Track third-party registries / directories we want this package to surface in. S
4
4
 
5
5
  > **Authority** — Phase 2 of [`road-to-product-adoption.md`](../../agents/roadmaps/road-to-product-adoption.md). The autonomous roadmap pass cannot open PRs in third-party repos; this file is the handoff.
6
6
 
7
+ ## Distribution channels — npm-primary
8
+
9
+ Per [`ADR-033`](../decisions/ADR-033-distribution-identity-npm-primary.md), the package is **npm-primary, Packagist deprecated-in-place**. Both lenses of the council deliberation (strategic / operational) converged on a single-channel posture: this is the canonical record of which registries we publish to vs. which we treat as legacy.
10
+
11
+ | Channel | Status | Canonical install | Notes |
12
+ |---|---|---|---|
13
+ | npm — `@event4u/agent-config` | **Primary** | `npm install @event4u/agent-config` or `npx @event4u/agent-config install` | The release pipeline (`scripts/release.py`) runs `npm publish` exclusively; `package.json` is the source of truth for the published version. |
14
+ | Packagist — `event4u/agent-config` | **Deprecated-in-place** | (do not install — see ADR-033) | The 1.0.4 listing is a legacy artefact from the pre-3.x repo namespace. No `composer.json` ships from this repo. Maintainer-side claim/archive action required (see below). |
15
+
16
+ ### Packagist deprecation — human-owner item
17
+
18
+ The Packagist `event4u/agent-config` listing pins at 1.0.4 from a repository state that no longer exists. The autonomous pipeline cannot retire that listing — it requires a maintainer login at `packagist.org/packages/event4u/agent-config`. Two paths exist; either is acceptable per ADR-033.
19
+
20
+ - [ ] **Claim + abandoned-flag the package.** Log in at packagist.org, claim the `event4u/agent-config` namespace, set the package to `abandoned` with the replacement pointer `event4u/agent-config` on npm (or the npm package URL as a body note where Packagist's abandoned-replacement field expects a Composer-namespace value, fall back to a `description` field redirect).
21
+ - [ ] **OR: leave the listing as legacy + add a description-field redirect.** If claim is blocked or out of scope, edit the listing description to add a one-line `> Deprecated — install via npm: \`@event4u/agent-config\`` so any consumer who lands there sees the correct path.
22
+
23
+ This item is **owner-owned**, not autonomous; the roadmap explicitly captures it as such (`road-to-distribution-identity.md` Phase 2 Step 1). Mark the chosen path with `[x]` once executed.
24
+
25
+ ### Breaking-change communication
26
+
27
+ Major-version bumps are policy-correct per [`CONTRIBUTING.md § Versioning policy`](../../CONTRIBUTING.md) (semver — installer-layout changes are major). The auto-generated `CHANGELOG.md § Breaking` section is the **single source of truth** for breaking changes; [`ADR-027`](../decisions/ADR-027-changelog-machine-vs-manual.md) locks the machine-generated path.
28
+
29
+ Consumers who see a major-version bump should follow:
30
+
31
+ 1. [`CHANGELOG.md § Breaking`](../../CHANGELOG.md#breaking) — the diff between the prior and the new major. Every breaking change carries a Conventional-Commits subject prefixed `feat!:` or with a `BREAKING CHANGE:` footer.
32
+ 2. The release-line link in the GitHub release entry for the new version (links the auto-generated changelog section).
33
+
34
+ No bespoke `BREAKING_CHANGES.md` is maintained — the changelog is the authoritative surface.
35
+
7
36
  ## Submission status
8
37
 
9
38
  | # | Registry | URL | Submission shape | Status | PR link |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@event4u/agent-config",
3
- "version": "5.1.0",
3
+ "version": "5.2.0",
4
4
  "description": "Universal AI Agent OS \u2014 audited skills, governance rules, commands, and templates for AI coding tools (Claude Code, Cursor, Windsurf, Copilot).",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env python3
2
+ """Reject commit subjects that would leak into the auto-generated changelog.
3
+
4
+ `scripts/release.py` reads commit subjects verbatim from
5
+ `<prev-tag>..HEAD` and writes them into `CHANGELOG.md` sections (notably
6
+ `### Breaking`). A sloppy subject — `wip`, `commit leftovers`, `fixup`,
7
+ short typos — becomes a sloppy public changelog line. Per
8
+ [ADR-033](../docs/decisions/ADR-033-distribution-identity-npm-primary.md)
9
+ and Phase 3 of `road-to-distribution-identity.md` this lint is the
10
+ CI-enforced gate that closes that surface.
11
+
12
+ Rules (PRO commit, range `<base>..<head>`):
13
+
14
+ - Subject length **after** stripping the Conventional-Commits type-prefix
15
+ (`feat:` / `fix(scope):` / `chore!:` / …) must be ≥ 10 characters.
16
+ - Subject must not contain blocklist words as whole tokens:
17
+ `leftover` / `leftovers` / `wip` / `temp` / `tmp` / `fixup`.
18
+ Case-insensitive; matched on word boundaries so legitimate uses like
19
+ `template` or `tempor­ary` (rare) survive — the linter targets the
20
+ short hand-wave forms reviewers see in feedback rounds.
21
+
22
+ Carve-outs:
23
+
24
+ - Merge commits (`Merge pull request …`, `Merge branch …`) are skipped —
25
+ they are GitHub-generated and not consumer-visible in the changelog.
26
+ - Revert commits (`Revert "…"`) are skipped — they ride the original
27
+ subject's discipline.
28
+
29
+ Local invocation defaults to `origin/main..HEAD` (the "what I am about
30
+ to push" range). CI sets `--base $GITHUB_BASE_REF --head $GITHUB_SHA`.
31
+
32
+ Cap: ≤ 150 LOC, stdlib only. Hooked into `task ci` via
33
+ `task lint-commit-subjects`.
34
+ """
35
+ from __future__ import annotations
36
+
37
+ import argparse
38
+ import re
39
+ import subprocess
40
+ import sys
41
+
42
+ BLOCKLIST = frozenset({"leftover", "leftovers", "wip", "temp", "tmp", "fixup"})
43
+ MIN_SUBJECT_LEN = 10
44
+
45
+ # Conventional Commits prefix — `type(scope)!?: message`. Matches the
46
+ # heads our `scripts/release.py` and CHANGELOG section logic respect.
47
+ CONVENTIONAL_PREFIX = re.compile(
48
+ r"^(feat|fix|chore|docs|refactor|test|perf|style|build|ci|revert)"
49
+ r"(\([^)]+\))?!?:\s+",
50
+ re.IGNORECASE,
51
+ )
52
+
53
+ # Skip lines — GitHub-generated merge subjects and revert commits.
54
+ SKIP_PREFIXES = ("Merge pull request", "Merge branch", "Merge remote-tracking",
55
+ 'Revert "')
56
+
57
+
58
+ def fetch_subjects(base: str, head: str) -> list[str]:
59
+ """Return one commit subject per element. Empty on no-range / no-commits."""
60
+ try:
61
+ result = subprocess.run(
62
+ ["git", "log", f"{base}..{head}", "--format=%s", "--no-merges"],
63
+ capture_output=True, text=True, check=True,
64
+ )
65
+ except subprocess.CalledProcessError as exc:
66
+ # CI without a proper base ref (force-push, first commit, weird state).
67
+ # Lint is advisory in that case — never block on git plumbing failures.
68
+ print(f"⚠️ git log {base}..{head} failed: {exc.stderr.strip()}",
69
+ file=sys.stderr)
70
+ return []
71
+ return [line.strip() for line in result.stdout.splitlines() if line.strip()]
72
+
73
+
74
+ def check_subject(subject: str) -> list[str]:
75
+ """Return list of issue strings for the subject; empty = clean."""
76
+ if any(subject.startswith(p) for p in SKIP_PREFIXES):
77
+ return []
78
+ issues: list[str] = []
79
+ body = CONVENTIONAL_PREFIX.sub("", subject, count=1)
80
+ if len(body) < MIN_SUBJECT_LEN:
81
+ issues.append(
82
+ f"subject body < {MIN_SUBJECT_LEN} chars after Conventional-Commits "
83
+ f"prefix: {subject!r}"
84
+ )
85
+ tokens = {t.lower() for t in re.findall(r"[A-Za-z]+", body)}
86
+ hits = sorted(tokens & BLOCKLIST)
87
+ if hits:
88
+ issues.append(
89
+ f"blocklist token(s) {hits} in subject: {subject!r}"
90
+ )
91
+ return issues
92
+
93
+
94
+ def main() -> int:
95
+ parser = argparse.ArgumentParser(description=__doc__)
96
+ parser.add_argument("--base", default="origin/main",
97
+ help="base ref (default: origin/main)")
98
+ parser.add_argument("--head", default="HEAD",
99
+ help="head ref (default: HEAD)")
100
+ parser.add_argument("--quiet", action="store_true",
101
+ help="suppress the clean-pass success line")
102
+ args = parser.parse_args()
103
+
104
+ subjects = fetch_subjects(args.base, args.head)
105
+ if not subjects:
106
+ if not args.quiet:
107
+ print(f"✅ No commit subjects to check in "
108
+ f"{args.base}..{args.head}.")
109
+ return 0
110
+
111
+ failures: list[tuple[str, str]] = []
112
+ for subj in subjects:
113
+ for issue in check_subject(subj):
114
+ failures.append((subj, issue))
115
+
116
+ if failures:
117
+ print(f"❌ {len(failures)} commit-subject issue(s) in "
118
+ f"{args.base}..{args.head}:", file=sys.stderr)
119
+ for _, issue in failures:
120
+ print(f" - {issue}", file=sys.stderr)
121
+ print(
122
+ "\nThese subjects feed the auto-generated CHANGELOG.md via "
123
+ "scripts/release.py — sloppy subjects become sloppy public "
124
+ "changelog lines. Per ADR-033 + "
125
+ "agents/roadmaps/road-to-distribution-identity.md § Phase 3.\n"
126
+ "Fix: rewrite the offending commits (e.g. `git rebase -i "
127
+ f"{args.base}` and `r`eword) with descriptive subjects, "
128
+ "then re-push.",
129
+ file=sys.stderr,
130
+ )
131
+ return 1
132
+
133
+ if not args.quiet:
134
+ print(f"✅ {len(subjects)} commit subject(s) clean.")
135
+ return 0
136
+
137
+
138
+ if __name__ == "__main__":
139
+ raise SystemExit(main())