@event4u/agent-config 7.0.0 → 7.0.1

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.
@@ -0,0 +1,126 @@
1
+ # Changelog Archive — pre-7.0.0
2
+
3
+ > Frozen snapshot of `event4u/agent-config` changelog entries
4
+ > released before `7.0.0`, split out of the main
5
+ > [`CHANGELOG.md`](../../CHANGELOG.md) by `scripts/release.py`
6
+ > once the active era's body crossed the drift cap enforced by
7
+ > `tests/test_changelog_eras.py`.
8
+ >
9
+ > **Read-only.** New entries land in `CHANGELOG.md`. Entries
10
+ > here are not amended — git tags remain the canonical source
11
+ > for what shipped.
12
+ >
13
+ > Entry shape follows
14
+ > [`../contracts/CHANGELOG-conventions.md`](../contracts/CHANGELOG-conventions.md).
15
+
16
+ ## [6.1.0](https://github.com/event4u-app/agent-config/compare/6.0.0...6.1.0) (2026-06-14)
17
+
18
+ ### Features
19
+
20
+ * **rules:** Iron Law — never drop inherited/shared-branch commits without asking ([e03dc18](https://github.com/event4u-app/agent-config/commit/e03dc18300a2ef032d508062be032267a1f1f844))
21
+ * **workspace:** plain-language host-decision explainability + employee-mode gate (6.0.0 P5) ([f485476](https://github.com/event4u-app/agent-config/commit/f485476050d3d7660aa3aaeacc54438e5eee44a9))
22
+ * **governance:** workspace boundary contract + import-edge drift linter (6.0.0 P4) ([943eb29](https://github.com/event4u-app/agent-config/commit/943eb29747eb3dca0988317e2a526cd38d12f06c))
23
+ * **install:** preview-only dry-run for the upgrade-cleanup reaper (6.0.0 P2) ([4caa802](https://github.com/event4u-app/agent-config/commit/4caa802b1fd1bbf1beda71e5ad553000e7b7f2bf))
24
+ * **create-pr:** run the PR-gate archival sweep before opening the PR ([bdd645b](https://github.com/event4u-app/agent-config/commit/bdd645b3bcc7443c00df653d8f53982417d0ca3e))
25
+ * **roadmaps:** deterministic PR-gate archival sweep ([10643ca](https://github.com/event4u-app/agent-config/commit/10643ca9f5ff56ff02f376531e5c8c07998aa08d))
26
+ * **memory:** remove agent-memory Layer 2; file-first memory only ([c8903c6](https://github.com/event4u-app/agent-config/commit/c8903c6f3a5502434b29c1b55535a9ebc3c55ac4))
27
+ * **bench:** v2 pilot run + honest-null report; spawn stratified follow-up (Phase 4-6) ([5b7f7bd](https://github.com/event4u-app/agent-config/commit/5b7f7bd1112462f83ca42ef6509f4871cdba7ebe))
28
+ * **bench:** v2 dual-axis scorer + runner + paired stats (Phase 2-3) ([ae25ee2](https://github.com/event4u-app/agent-config/commit/ae25ee2c7a8b0fe6325a0c886911a8c48c7546d5))
29
+ * **bench:** v2 discipline-axis corpus + fixtures (Phase 0-1) ([1371dd0](https://github.com/event4u-app/agent-config/commit/1371dd05096c38c7f28f0c4a55b1daf2bb230500))
30
+ * **bench:** wrapper-lift A/B benchmark — model-pin, budget-cap, error-aware ([a09238a](https://github.com/event4u-app/agent-config/commit/a09238ad0455baaf6f68016a195ad54725acd3a3))
31
+ * **reasoning:** add model-agnostic Reasoning Discipline Protocol (RDP) ([b4b5bec](https://github.com/event4u-app/agent-config/commit/b4b5bec18f58630add4e7802c9e32b46b88f3084))
32
+ * **security:** Phase 3 consumer audit + runtime injection hook + review skill ([23efe50](https://github.com/event4u-app/agent-config/commit/23efe5001104c1fd7056f7b2216fc4583f7f29f3))
33
+ * **security:** Phase 2 injection-aware authoring rules ([eb41da9](https://github.com/event4u-app/agent-config/commit/eb41da9c7a1e3140d1efad760bd06e0bd53cb513))
34
+ * **security:** Phase 1 self-audit corpus linters + containment convention ([f678aba](https://github.com/event4u-app/agent-config/commit/f678abab7b521f389e16abb837594b3d23a38d5f))
35
+ * **ai-council:** resolve config user-global-first ([9177b52](https://github.com/event4u-app/agent-config/commit/9177b52a99842b3adffc21b325b39610ef520c6d))
36
+ * **ci:** guard against re-introducing external-source references ([88d35d9](https://github.com/event4u-app/agent-config/commit/88d35d913f5afec7db33f5817229b366cac7256f))
37
+ * **doctor:** add read-only stale-orphans health check ([48e6118](https://github.com/event4u-app/agent-config/commit/48e6118eab2e39fbfa1884a9f6defb1c8719a2ba))
38
+ * **packs:** split meta into capability-scoped packs ([b363839](https://github.com/event4u-app/agent-config/commit/b3638393a6c67ecaac41ae19df89b2ed0b03154b))
39
+ * **commands:** add first-class visibility frontmatter field ([db4d88d](https://github.com/event4u-app/agent-config/commit/db4d88dcf41629f27973624d6476f935be32e2c9))
40
+
41
+ ### Bug Fixes
42
+
43
+ * **refs:** mark the RDP-contract acceptance criterion as a forward-ref ([c7cee96](https://github.com/event4u-app/agent-config/commit/c7cee966495a5c96932c9f2b71ab72aae517623f))
44
+ * **memory:** mine-session description ≤200 chars + command-count floor ([f368e06](https://github.com/event4u-app/agent-config/commit/f368e0667980e3f3a03233e8152fd23daaa8bc5d))
45
+ * **bench:** lint-bench-ab validates the v2 docs/benchmark.md sections ([41d552b](https://github.com/event4u-app/agent-config/commit/41d552bd3a162577be3a898fca9123cce87b38b9))
46
+ * **server:** mirror reasoning.* in settings schema (template parity + diff) ([5f77256](https://github.com/event4u-app/agent-config/commit/5f77256da1ecbfa03f3aa1261f23ec940991c2bc))
47
+ * **reasoning:** skill-lint senior sections + valid rule tier (2b) ([7f3e278](https://github.com/event4u-app/agent-config/commit/7f3e2789ab483b6938a39aac303f1fbe2f35792e))
48
+ * **security:** refresh threat-model command effective-hash ([5505b57](https://github.com/event4u-app/agent-config/commit/5505b57fa6e4e4bf02e2891f49a30e6dc9128da1))
49
+ * **security:** tighten security-sensitive-stop — clear long_rule lint regression ([d528126](https://github.com/event4u-app/agent-config/commit/d528126120bf51335267bdde66793343ec376c87))
50
+ * **security:** list agent-security-review + security-audit-config in marketplace.json ([f432a62](https://github.com/event4u-app/agent-config/commit/f432a62d42fab5fc6707b166e869ca8cc2d5d866))
51
+ * **security:** CI — portability reword + settings-schema parity for injection_scan ([fa78a58](https://github.com/event4u-app/agent-config/commit/fa78a589101fd41984d85496666f2fd618e49a1f))
52
+ * **cli:** preflight dependency guard with an actionable message ([ce58c76](https://github.com/event4u-app/agent-config/commit/ce58c7683541a3bc22852186a9764be127f87e4b))
53
+ * **repo:** untrack stray node_modules symlink, ignore symlink form ([67cc9e6](https://github.com/event4u-app/agent-config/commit/67cc9e61deb241aa23e9affaa5adc1fd92d99f14))
54
+ * **refs:** exempt gitignored agents/.harvest-local in check_references ([0d0b170](https://github.com/event4u-app/agent-config/commit/0d0b1702c27c06e96d486211f639148f7ed0628e))
55
+ * **refs:** mark gitignored harvest-local ref in security-pillar roadmap ([3abf215](https://github.com/event4u-app/agent-config/commit/3abf215692cf49ac190248c0007668b5a7b1e1f0))
56
+ * **ci:** guard against empty (0-byte) roadmap files ([5115865](https://github.com/event4u-app/agent-config/commit/511586591171735437bba8cdb106094628a3d611))
57
+ * **roadmaps:** restore competitive-borrow v6 work, drop empty reaping stub ([9fceeb7](https://github.com/event4u-app/agent-config/commit/9fceeb7fc9585965f44e25543a9699d0ff8ee519))
58
+ * **ai-council:** align user-global config path with the setup wizard ([54de5a8](https://github.com/event4u-app/agent-config/commit/54de5a853c271311d79b2b436dd3fa58fa25eb69))
59
+ * **rule:** drop non-portable task-invocations from source-confidentiality backstop ([57e6f7f](https://github.com/event4u-app/agent-config/commit/57e6f7fe5199cc5192ead2a1468ad7d04dd5d5bc))
60
+ * **install:** always-run tag sweep so reaping catches pre-inventory orphans ([91e5418](https://github.com/event4u-app/agent-config/commit/91e541852c30899cb84dd2070f705dfe7947d350))
61
+ * **discovery:** register visibility + new capability packs in schema/vocab ([1fcc72b](https://github.com/event4u-app/agent-config/commit/1fcc72b08fbd13f85c01d3df7fbc98790395ac91))
62
+
63
+ ### Documentation
64
+
65
+ * **domain-watch:** capture deferred heavyweight tracks in one watch-note (6.0.0 P6) ([e212e1f](https://github.com/event4u-app/agent-config/commit/e212e1fcebed5d61c63d0719deb485787aca6fde))
66
+ * **changelog:** add 6.0.0 release overview + breaking-changes section (6.0.0 P3) ([fdb4b63](https://github.com/event4u-app/agent-config/commit/fdb4b63cd495489341215430ae12a9866cb8503b))
67
+ * fix stale source-of-truth paths and drop above-fold jargon (6.0.0 P1) ([9ee9870](https://github.com/event4u-app/agent-config/commit/9ee98708e1fdcf612848b393aace48b33a548e99))
68
+ * **roadmaps:** add capability-discoverability follow-up from fable-feedback-3 ([a658c2f](https://github.com/event4u-app/agent-config/commit/a658c2f9c338182d1db2db503ab8ce6aa9c1f42f))
69
+ * **model-recommendation:** add orchestrator→subagent model-routing guidance ([669f83d](https://github.com/event4u-app/agent-config/commit/669f83de5b2e6de590d87f7a093eb098bcf8a086))
70
+ * **roadmaps:** add RDP discoverability roadmap ([c65e1b2](https://github.com/event4u-app/agent-config/commit/c65e1b2648af8bfd2a39a6c6dc74124cfc67101b))
71
+ * **roadmaps:** revise discipline-axis pilot for weak-host-first methodology ([9ccb816](https://github.com/event4u-app/agent-config/commit/9ccb816b1fe449cb537567696487c09a4ea2ee8e))
72
+ * **rules:** replace Merge-gated with PR-gate in roadmap-progress-sync ([c140687](https://github.com/event4u-app/agent-config/commit/c1406876e880babd70a178bb958af89f31c49eec))
73
+ * **roadmaps:** memory-layer-cleanup removal + consolidation plans ([254758f](https://github.com/event4u-app/agent-config/commit/254758f1a24d7898b5828a3396d20b7498370d08))
74
+ * **roadmap:** check-refs:skip discipline-axis bench (external repo citations) ([13c5fdb](https://github.com/event4u-app/agent-config/commit/13c5fdb04016c40488140f59688e9db88eee0d9d))
75
+ * **roadmaps:** agent-memory removal + memory-pipeline consolidation plans ([6fbf6a6](https://github.com/event4u-app/agent-config/commit/6fbf6a6d33571839f8c85b276a8728ff62c606c7))
76
+ * **roadmaps:** 3-condition value bench (v1) + discipline-axis bench (v2) ([f5a89c6](https://github.com/event4u-app/agent-config/commit/f5a89c69e60c345dbb655f3b0dab294d2b56669a))
77
+ * **roadmaps:** RDP eval+promotion follow-up; archive frontier-reasoning parent ([417223b](https://github.com/event4u-app/agent-config/commit/417223bce4a4d04404512cfa804ad872dd11324c))
78
+ * **roadmap:** security pillar — supply-chain integrity + injection-aware authoring ([c15ef6a](https://github.com/event4u-app/agent-config/commit/c15ef6adc17226513660b324b7960dc80125bbf3))
79
+ * **roadmap:** fill in road-to-6.0.0-final-readiness ([46ba5a0](https://github.com/event4u-app/agent-config/commit/46ba5a002fb300afbce43e56b92a945bd0957dec))
80
+ * **roadmap:** add image/brand/typography + greenfield-scaffold roadmaps ([f615452](https://github.com/event4u-app/agent-config/commit/f6154528a7b1ba34fb5543c91ac2c905999d2e8c))
81
+ * **ai-council:** repoint config references to the user-global path ([1307f80](https://github.com/event4u-app/agent-config/commit/1307f804b2c1cc4a2b9a3d2ad1b829f2a7108619))
82
+ * **ai-council:** document user-global config location (ADR-093) ([c501e55](https://github.com/event4u-app/agent-config/commit/c501e557fdb94d7214555a3b53042a275b2dc923))
83
+ * **roadmap:** competitive-borrow plan (source-anonymous) ([9e3c692](https://github.com/event4u-app/agent-config/commit/9e3c692745170ad62b0f67d40afa8aad751e34dd))
84
+ * **roadmap:** close metadata-leanness, spawn tier-removal follow-up ([93d5361](https://github.com/event4u-app/agent-config/commit/93d5361d05d5ddef944d4931c6260e69464d8270))
85
+ * **adr:** ADR-092 defer command tier-alias removal (forcing-function) ([71d2630](https://github.com/event4u-app/agent-config/commit/71d26300d14c9b894dfef19ed6a6b3c996f5895f))
86
+ * **roadmap:** metadata & command-surface leanness plan + evidence ([b31d106](https://github.com/event4u-app/agent-config/commit/b31d1061f5816eb3a8aadd4bd154e76ee00d089a))
87
+ * **roadmap:** archive completed reaping-orphans fix plan ([c1ed447](https://github.com/event4u-app/agent-config/commit/c1ed4478f14c634c5fde5dbed224f72ae2c6eedb))
88
+ * record evidence-gated decisions, archive residuals roadmap ([3657e12](https://github.com/event4u-app/agent-config/commit/3657e12fbcacb4740b4d1d84309d7dea28cd3c58))
89
+
90
+ ### Refactoring
91
+
92
+ * **ai-council:** relocate config out of the repo to user-global ([17fb14a](https://github.com/event4u-app/agent-config/commit/17fb14a03736cb14f2f56861f194e5bfc00e80b9))
93
+ * **commands:** fold fix:pr-bot/developer-comments into fix:pr-comments ([4c1c1c8](https://github.com/event4u-app/agent-config/commit/4c1c1c8795d422e1ba8d5ae77409f43f3b8fb2e5))
94
+
95
+ ### Tests
96
+
97
+ * lower command-surface floor to 148 after fix:pr-* fold ([2b5e9ef](https://github.com/event4u-app/agent-config/commit/2b5e9ef7f6a8160aac6d2d26bb2876d7d8ed1427))
98
+ * **install:** lock the reaping-gap class against regression ([d7ca6d0](https://github.com/event4u-app/agent-config/commit/d7ca6d0f967672d02d0b367fcfa408f6d675a220))
99
+
100
+ ### CI
101
+
102
+ * **skill-lint:** also enforce rule tiers + guard the CI wiring ([6cde280](https://github.com/event4u-app/agent-config/commit/6cde2809c5ace248807236dee9634a590955795d))
103
+ * **skill-lint:** run lint-command-tiers on every PR ([2e123c6](https://github.com/event4u-app/agent-config/commit/2e123c696a6bf2047cbc4473a9555c51b8f33da8))
104
+
105
+ ### Chores
106
+
107
+ * **roadmaps:** complete + archive road-to-6.0.0-final-readiness (6.0.0) ([652294a](https://github.com/event4u-app/agent-config/commit/652294ad01ad48a2875ebd75953b82d06c350e56))
108
+ * **roadmaps:** archive completed memory-layer-cleanup roadmaps ([9380482](https://github.com/event4u-app/agent-config/commit/9380482cf512c73f0783ff7a2270793d5dca8b95))
109
+ * **memory:** regenerate pack manifest for shortened mine-session description ([14cec0e](https://github.com/event4u-app/agent-config/commit/14cec0ed0c6de254639e3727d7ad1f3a3147902e))
110
+ * **memory:** regenerate projections, catalogs, and dashboard ([78cc1a1](https://github.com/event4u-app/agent-config/commit/78cc1a1180f6eced4c60e0c408da1103d0621ab0))
111
+ * **roadmaps:** regenerate progress dashboard ([d0d2757](https://github.com/event4u-app/agent-config/commit/d0d2757e93026fab6f995732a130f126371e7978))
112
+ * **security:** archive road-to-security-pillar (Phases 1-3 shipped) ([ad5f552](https://github.com/event4u-app/agent-config/commit/ad5f55204bae429b960db40976c3dc40ea759f97))
113
+ * **security:** bump command-count messaging 148→149 (new /security-audit-config) ([5f5b79a](https://github.com/event4u-app/agent-config/commit/5f5b79a1d741b1565cfbc0aaf8c74aa43165fd0b))
114
+ * **security:** regenerate dist, router, counts, pack manifests ([84ff0fc](https://github.com/event4u-app/agent-config/commit/84ff0fcd1216fbbd05a3a340148f681b95c2d4a8))
115
+ * add uncomitted roadmaps ([699c2c5](https://github.com/event4u-app/agent-config/commit/699c2c5e8a70453a46b9c70e68ba621f96a5f64c))
116
+ * **condense:** reconcile stale condensation hashes ([d9ac27e](https://github.com/event4u-app/agent-config/commit/d9ac27e232cc60dba941f75632dde9a5bbd2c4db))
117
+ * add uncomitted roadmaps ([28ff06c](https://github.com/event4u-app/agent-config/commit/28ff06ce3741db46a5d39e1bc0a2a698d3a94c1f))
118
+ * **rules:** track anonymized harvest roadmaps; harden source-confidentiality ([bc77fb9](https://github.com/event4u-app/agent-config/commit/bc77fb93476c8045cd227f94ad09b0f1f3cebbdd))
119
+ * sync stale condensation for work / agents-init / agents-optimize ([0f816ca](https://github.com/event4u-app/agent-config/commit/0f816cae017824af718cd750457bfc1476252927))
120
+ * sync command-count messaging to 148 after fix:pr-* removal ([94097ce](https://github.com/event4u-app/agent-config/commit/94097ce0bee1739d2f54688705b1aaf78a73cee4))
121
+ * sync stale condensation hashes for unchanged command files ([a32da9a](https://github.com/event4u-app/agent-config/commit/a32da9a5c4199d1148549f63bf7c0012496cd2b6))
122
+ * remove external-source references from the tracked tree ([3248611](https://github.com/event4u-app/agent-config/commit/3248611a8ae2e7d0574ba8e121f85077faae9f69))
123
+ * **commands:** backfill visibility + re-tag pack across command sources ([d08f5b6](https://github.com/event4u-app/agent-config/commit/d08f5b6550735db22f4f855f155d2fd91a8dc047))
124
+ * **changelog:** split era 5.9.x → pre-6.0.0 ([c442750](https://github.com/event4u-app/agent-config/commit/c44275053c07352402e162d6accbe67f6d380010))
125
+
126
+ Tests: 6068
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@event4u/agent-config",
3
- "version": "7.0.0",
3
+ "version": "7.0.1",
4
4
  "description": "Universal AI Agent OS — audited skills, governance rules, commands, and templates for AI coding tools (Claude Code, Cursor, Windsurf, Copilot).",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -126,6 +126,64 @@ export function current_era_body_size(lines: readonly string[] | null = null): n
126
126
  return next_era_line - current_idx - 1;
127
127
  }
128
128
 
129
+ /**
130
+ * Lines in the current era body EXCLUDING the newest (topmost) released
131
+ * version section.
132
+ *
133
+ * The drift cap bounds *accumulated prior* releases in the current era — the
134
+ * mechanism by which "growth forces an era split" (see
135
+ * `docs/contracts/CHANGELOG-conventions.md § Era splits`). The newest released
136
+ * section is exempt: a single release is allowed to be arbitrarily large and
137
+ * instead forces the split on the **next** bump, once it has become a prior
138
+ * entry. Without this exemption a single large catch-up release — e.g. a
139
+ * 376-commit bump whose `## [x.y.z]` section alone exceeds the cap — trips the
140
+ * gate with no era split that could ever bring it under, which is the exact
141
+ * failure this function fixes.
142
+ *
143
+ * Returns the same count as {@link current_era_body_size} when the era holds no
144
+ * released section yet (nothing to exempt).
145
+ */
146
+ export function current_era_accumulated_body_size(lines: readonly string[] | null = null): number {
147
+ const all = lines ?? read_changelog_lines();
148
+ const spans = era_spans(all);
149
+ const current_idx = current_era_index(spans);
150
+ if (current_idx === null) {
151
+ return 0;
152
+ }
153
+ let next_era_line = all.length;
154
+ for (const span of spans) {
155
+ if (span.line_index > current_idx) {
156
+ next_era_line = span.line_index;
157
+ break;
158
+ }
159
+ }
160
+ const total = next_era_line - current_idx - 1;
161
+ if (total <= 0) {
162
+ return total;
163
+ }
164
+ // Topmost (newest) version heading in the era body.
165
+ let first = -1;
166
+ for (let i = current_idx + 1; i < next_era_line; i++) {
167
+ if (VERSION_HEADING_RE.test(all[i]!)) {
168
+ first = i;
169
+ break;
170
+ }
171
+ }
172
+ if (first === -1) {
173
+ return total; // no released section yet → nothing to exempt
174
+ }
175
+ // The next version heading marks where accumulated prior releases begin.
176
+ let next_ver = next_era_line;
177
+ for (let i = first + 1; i < next_era_line; i++) {
178
+ if (VERSION_HEADING_RE.test(all[i]!)) {
179
+ next_ver = i;
180
+ break;
181
+ }
182
+ }
183
+ const newest_section = next_ver - first;
184
+ return Math.max(0, total - newest_section);
185
+ }
186
+
129
187
  /** Parse `M.N.x` into `[M, N]`; return null for archived labels. */
130
188
  export function parse_era_label(label: string): [number, number] | null {
131
189
  const m = ERA_LABEL_RE.exec(label.trim());
@@ -429,23 +429,58 @@ function tuple_lt(a: [number, number, number], b: [number, number, number]): boo
429
429
  return false;
430
430
  }
431
431
 
432
- /** Read `version` from the package's own `package.json`. */
433
- export function current_package_version(repo_root?: string | null): string {
434
- let root = repo_root ?? null;
435
- if (root === null) {
436
- // __file__ = src/scripts/_lib/installed_lock.ts → repo root is three
437
- // levels up (_lib → scripts → src → root), i.e. parents[3] in Python.
438
- const here = path.dirname(fileURLToPath(import.meta.url));
439
- root = path.resolve(here, "..", "..", "..");
440
- }
432
+ /** Read a string `version` from `<dir>/package.json`, or null if absent/malformed. */
433
+ function _read_package_version(dir: string): string | null {
441
434
  try {
442
- const data = JSON.parse(fs.readFileSync(path.join(root, "package.json"), { encoding: "utf-8" }));
435
+ const data = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), { encoding: "utf-8" }));
443
436
  const version = (data as { version?: unknown }).version;
444
437
  if (typeof version === "string" && version.trim()) {
445
438
  return version.trim();
446
439
  }
447
440
  } catch {
448
- // Missing / unreadable / malformed package.json → fall through.
441
+ // Missing / unreadable / malformed package.json → null.
442
+ }
443
+ return null;
444
+ }
445
+
446
+ /**
447
+ * Walk upward from `start_dir` to the nearest `package.json` carrying a string
448
+ * `version`. Layout-independent: works in the source tree
449
+ * (`src/scripts/_lib/`), a per-file `dist/`, AND the esbuild single-file bundle
450
+ * (`dist/cli/agent-config.js`) where this module's runtime location differs
451
+ * from its source path. Returns null at the filesystem root.
452
+ */
453
+ export function _find_package_version_upward(start_dir: string): string | null {
454
+ let dir = start_dir;
455
+ for (;;) {
456
+ const found = _read_package_version(dir);
457
+ if (found !== null) {
458
+ return found;
459
+ }
460
+ const parent = path.dirname(dir);
461
+ if (parent === dir) {
462
+ return null; // reached the filesystem root
463
+ }
464
+ dir = parent;
465
+ }
466
+ }
467
+
468
+ /**
469
+ * Read `version` from the package's own `package.json`.
470
+ *
471
+ * With no `repo_root`, the version is resolved by walking **upward** from this
472
+ * module's runtime directory to the nearest `package.json`. A previous fixed
473
+ * `parents[3]` hop assumed the source layout (`src/scripts/_lib/`); in the
474
+ * shipped esbuild bundle the module runs from `dist/cli/`, so three hops
475
+ * overshot the package root → `package.json` not found → `"0.0.0"` → a
476
+ * spurious "downgrade" refusal on `agent-config global` (lockfile recorded a
477
+ * real version, current package read as `0.0.0`). The upward walk is correct
478
+ * in both layouts.
479
+ */
480
+ export function current_package_version(repo_root?: string | null): string {
481
+ if (repo_root != null) {
482
+ return _read_package_version(repo_root) ?? "0.0.0";
449
483
  }
450
- return "0.0.0";
484
+ const here = path.dirname(fileURLToPath(import.meta.url));
485
+ return _find_package_version_upward(here) ?? "0.0.0";
451
486
  }
@@ -94,7 +94,7 @@ import type {
94
94
  SplitPlan} from './_lib/changelog_eras.js';
95
95
  import {
96
96
  CURRENT_ERA_BODY_CAP,
97
- current_era_body_size,
97
+ current_era_accumulated_body_size,
98
98
  current_era_insertion_point,
99
99
  perform_split,
100
100
  plan_split,
@@ -1014,9 +1014,11 @@ function print_preview(plan: Plan): void {
1014
1014
  process.stdout.write('\n');
1015
1015
  }
1016
1016
 
1017
- function confirm(prompt: string): boolean {
1018
- const ans = _input(`${prompt} [y/N] `).trim().toLowerCase();
1019
- return ans === 'y' || ans === 'yes';
1017
+ function confirm(prompt: string): boolean | null {
1018
+ const ans = _input(`${prompt} [y/N] `);
1019
+ if (ans === null) return null; // no usable controlling terminal
1020
+ const norm = ans.trim().toLowerCase();
1021
+ return norm === 'y' || norm === 'yes';
1020
1022
  }
1021
1023
 
1022
1024
  /** Can we prompt at all — is fd 0 a TTY, or is a controlling terminal openable? */
@@ -1033,35 +1035,42 @@ function _canPrompt(): boolean {
1033
1035
  }
1034
1036
 
1035
1037
  /**
1036
- * Mirror of Python `input(prompt)` — write prompt, read one line.
1038
+ * Mirror of Python `input(prompt)` — write the prompt, read one line from the
1039
+ * controlling terminal. Returns `null` when no terminal is reachable.
1037
1040
  *
1038
- * Reads from the controlling terminal (`/dev/tty`) when fd 0 is not itself a
1039
- * TTY. `task release` / `./scripts-run` spawn this script with stdin detached
1040
- * from the terminal, so a naive `readSync(0)` hits EOF immediately and the
1041
- * `[y/N]` prompt "auto-aborts" without ever waiting. Falling back to `/dev/tty`
1042
- * keeps the prompt interactive regardless of how the script is invoked.
1041
+ * The answer is ALWAYS read from a freshly opened, blocking `/dev/tty`
1042
+ * (`openSync(..., 'rs')`), NEVER from fd 0. `task release` / `./scripts-run`
1043
+ * run this under go-task's `interactive: true`, which leaves fd 0 a TTY
1044
+ * (`process.stdin.isTTY === true`) but one Node treats as NON-blocking — so a
1045
+ * bare `readSync(0)` throws `EAGAIN`, the catch swallows it, and the `[y/N]`
1046
+ * prompt "auto-aborts" without ever waiting (the exact failure this fixes;
1047
+ * reproduced with `isTTY === true`). A fresh blocking `/dev/tty` descriptor
1048
+ * blocks for real input regardless of how the script was invoked — the proven
1049
+ * synchronous-prompt pattern.
1043
1050
  */
1044
- function _input(prompt: string): string {
1051
+ function _input(prompt: string): string | null {
1045
1052
  process.stdout.write(prompt);
1046
- let fd = 0;
1047
- let openedTty = false;
1048
- if (!process.stdin.isTTY) {
1049
- try {
1050
- fd = fs.openSync('/dev/tty', 'r');
1051
- openedTty = true;
1052
- } catch {
1053
- return ''; // no controlling terminal (true non-interactive); guarded by _canPrompt upstream.
1054
- }
1053
+ let fd: number;
1054
+ try {
1055
+ // 'rs' → O_RDONLY | O_SYNC: a fresh, blocking descriptor on the
1056
+ // controlling terminal, unaffected by Node's non-blocking fd 0.
1057
+ fd = fs.openSync('/dev/tty', 'rs');
1058
+ } catch {
1059
+ return null; // no controlling terminal (true non-interactive)
1055
1060
  }
1056
1061
  try {
1057
1062
  const buf = Buffer.alloc(1);
1058
1063
  const chars: number[] = [];
1059
- while (true) {
1064
+ for (;;) {
1060
1065
  let bytesRead: number;
1061
1066
  try {
1062
1067
  bytesRead = fs.readSync(fd, buf, 0, 1, null);
1063
- } catch {
1064
- break; // EOF / error → EOFError analogue; return what we have.
1068
+ } catch (e) {
1069
+ const code = (e as NodeJS.ErrnoException).code;
1070
+ // A blocking /dev/tty should not yield these, but inherited
1071
+ // descriptor flags can — retry rather than abort the prompt.
1072
+ if (code === 'EAGAIN' || code === 'EINTR') continue;
1073
+ break; // EOF / EIO → EOFError analogue; return what we have.
1065
1074
  }
1066
1075
  if (bytesRead === 0) break;
1067
1076
  const b = buf[0] as number;
@@ -1070,7 +1079,7 @@ function _input(prompt: string): string {
1070
1079
  }
1071
1080
  return Buffer.from(chars).toString('utf-8');
1072
1081
  } finally {
1073
- if (openedTty) fs.closeSync(fd);
1082
+ fs.closeSync(fd);
1074
1083
  }
1075
1084
  }
1076
1085
 
@@ -1090,18 +1099,19 @@ export interface ConfirmVerdict {
1090
1099
  */
1091
1100
  export function confirmGate(target: string, yes: boolean): ConfirmVerdict {
1092
1101
  if (yes) return { proceed: true };
1093
- if (!_canPrompt()) {
1094
- return {
1095
- proceed: false,
1096
- stream: 'stderr',
1097
- message:
1098
- 'No terminal available for the [y/N] confirmation (non-interactive shell). ' +
1099
- 'Re-run with --yes to confirm, e.g. `task release -- --yes`.',
1100
- };
1101
- }
1102
- if (!confirm(`Proceed with release ${target}?`)) {
1103
- return { proceed: false, stream: 'stdout', message: 'aborted.' };
1104
- }
1102
+ const noTerminal: ConfirmVerdict = {
1103
+ proceed: false,
1104
+ stream: 'stderr',
1105
+ message:
1106
+ 'No terminal available for the [y/N] confirmation (non-interactive shell). ' +
1107
+ 'Re-run with --yes to confirm, e.g. `task release -- --yes`.',
1108
+ };
1109
+ if (!_canPrompt()) return noTerminal;
1110
+ const answer = confirm(`Proceed with release ${target}?`);
1111
+ // null = the controlling terminal vanished between the probe and the read;
1112
+ // surface the actionable --yes guidance, never a bare silent "aborted.".
1113
+ if (answer === null) return noTerminal;
1114
+ if (!answer) return { proceed: false, stream: 'stdout', message: 'aborted.' };
1105
1115
  return { proceed: true };
1106
1116
  }
1107
1117
 
@@ -1382,6 +1392,7 @@ interface Args {
1382
1392
  dry_run: boolean;
1383
1393
  no_wait: boolean;
1384
1394
  resume: boolean;
1395
+ check_confirm: boolean;
1385
1396
  }
1386
1397
 
1387
1398
  const PROG = 'release.py';
@@ -1391,7 +1402,7 @@ const PROG = 'release.py';
1391
1402
  // not the body prose.
1392
1403
  const USAGE =
1393
1404
  `usage: ${PROG} [-h] [--as {major,minor,patch}] [--version EXPLICIT] [--yes]\n` +
1394
- ' [--dry-run] [--no-wait] [--resume]\n';
1405
+ ' [--dry-run] [--no-wait] [--resume] [--check-confirm]\n';
1395
1406
 
1396
1407
  function _argError(msg: string): never {
1397
1408
  process.stderr.write(USAGE);
@@ -1407,6 +1418,7 @@ function _parse_args(argv: readonly string[]): Args {
1407
1418
  dry_run: false,
1408
1419
  no_wait: false,
1409
1420
  resume: false,
1421
+ check_confirm: false,
1410
1422
  };
1411
1423
 
1412
1424
  const positionals: string[] = [];
@@ -1481,6 +1493,13 @@ function _parse_args(argv: readonly string[]): Args {
1481
1493
  i += 1;
1482
1494
  continue;
1483
1495
  }
1496
+ if (flag === '--check-confirm') {
1497
+ if (inlineVal !== null)
1498
+ _argError(`argument --check-confirm: ignored explicit argument '${inlineVal}'`);
1499
+ args.check_confirm = true;
1500
+ i += 1;
1501
+ continue;
1502
+ }
1484
1503
  if (a.startsWith('-') && a !== '-') {
1485
1504
  _argError(`unrecognized arguments: ${a}`);
1486
1505
  }
@@ -1560,6 +1579,32 @@ function _detect_in_flight_target(): string | null {
1560
1579
  function main(argv: readonly string[] | null = null): number {
1561
1580
  const args = _parse_args(argv === null ? process.argv.slice(2) : argv);
1562
1581
 
1582
+ // Self-test the confirmation prompt without touching git/gh/npm. Run it the
1583
+ // same way a real release is invoked — `task release -- --check-confirm` —
1584
+ // to verify the `[y/N]` prompt can actually read your answer through the
1585
+ // go-task / scripts-run wrapper chain. Nothing is released either way.
1586
+ if (args.check_confirm) {
1587
+ process.stdout.write(
1588
+ 'Confirm-gate self-test — answer the [y/N] prompt below.\n' +
1589
+ 'Nothing is released; this only checks that the prompt can read your answer.\n',
1590
+ );
1591
+ const verdict = confirmGate('SELF-TEST (no release)', args.yes);
1592
+ if (verdict.proceed) {
1593
+ process.stdout.write(
1594
+ 'result: OK — the prompt read your confirmation. ' +
1595
+ '`task release` will work interactively.\n',
1596
+ );
1597
+ return 0;
1598
+ }
1599
+ const sink = verdict.stream === 'stderr' ? process.stderr : process.stdout;
1600
+ if (verdict.message) sink.write(`${verdict.message}\n`);
1601
+ process.stdout.write(
1602
+ `result: the prompt did not read a "y" (stdin.isTTY=${String(process.stdin.isTTY)}). ` +
1603
+ 'If you typed y and still see this, paste this whole block back.\n',
1604
+ );
1605
+ return 1;
1606
+ }
1607
+
1563
1608
  const current = (
1564
1609
  JSON.parse(fs.readFileSync(PACKAGE_JSON, 'utf-8')) as Record<string, unknown>
1565
1610
  )['version'] as string;
@@ -1596,12 +1641,18 @@ function main(argv: readonly string[] | null = null): number {
1596
1641
  test_trend_line,
1597
1642
  });
1598
1643
 
1599
- // Era-split planning: only crosses the gate when the current era body
1600
- // has grown past the drift cap AND the release crosses a minor/major
1601
- // boundary. Patch overflow is caught by the drift test (red CI), not
1602
- // by an auto-split into a nonsensical "pre-X.Y.Z" archive.
1644
+ // Era-split planning: only crosses the gate when the current era's
1645
+ // *accumulated* body (prior releases, excluding the newest section) has
1646
+ // grown past the drift cap AND the release crosses a minor/major boundary.
1647
+ // The newest release is exempt — a single large catch-up release forces
1648
+ // the split on the *next* minor/major once it has become a prior entry,
1649
+ // not on the patch that immediately follows it. This is the same quantity
1650
+ // the drift test enforces (current_era_accumulated_body_size); measuring
1651
+ // the raw total here would re-fire the gate the exemption exists to clear.
1652
+ // Patch overflow that genuinely accumulates is caught by the drift test
1653
+ // (red CI), not by an auto-split into a nonsensical "pre-X.Y.Z" archive.
1603
1654
  let split: SplitPlan | null = null;
1604
- const body_size = current_era_body_size();
1655
+ const body_size = current_era_accumulated_body_size();
1605
1656
  if (body_size > CURRENT_ERA_BODY_CAP) {
1606
1657
  const candidate = plan_split(target);
1607
1658
  if (candidate === null) {