@event4u/agent-config 16.0.0 → 16.1.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.
Files changed (246) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +297 -137
  5. package/CONTRIBUTING.md +9 -0
  6. package/README.md +2 -2
  7. package/dist/agent-src/commands/analyze/inbox.md +26 -0
  8. package/dist/agent-src/commands/analyze/roadmap-repos.md +27 -0
  9. package/dist/agent-src/commands/challenge-me/closure.md +178 -0
  10. package/dist/agent-src/commands/challenge-me.md +9 -2
  11. package/dist/agent-src/commands/council/default.md +25 -5
  12. package/dist/agent-src/commands/feature/plan.md +28 -1
  13. package/dist/agent-src/commands/feature/roadmap.md +28 -1
  14. package/dist/agent-src/commands/implement-ticket.md +27 -0
  15. package/dist/agent-src/commands/jira-ticket.md +27 -0
  16. package/dist/agent-src/commands/pr/create.md +29 -0
  17. package/dist/agent-src/commands/pr/merge.md +60 -9
  18. package/dist/agent-src/commands/roadmap/ai-council.md +7 -1
  19. package/dist/agent-src/commands/roadmap/create.md +74 -27
  20. package/dist/agent-src/commands/roadmap/materialize.md +27 -0
  21. package/dist/agent-src/commands/roadmap/process-full.md +123 -18
  22. package/dist/agent-src/commands/team.md +22 -0
  23. package/dist/agent-src/contexts/authority/scope-growth-ownership.md +80 -0
  24. package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +47 -0
  25. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +44 -3
  26. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +15 -15
  27. package/dist/agent-src/contexts/execution/decision-closure-execution.md +93 -0
  28. package/dist/agent-src/contexts/execution/fix-loop-ladder.md +108 -0
  29. package/dist/agent-src/contexts/execution/plan-confidence-gate.md +4 -2
  30. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +75 -6
  31. package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
  32. package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
  33. package/dist/agent-src/guidelines/design-fidelity-routing.md +29 -0
  34. package/dist/agent-src/rules/autonomous-execution.md +15 -8
  35. package/dist/agent-src/rules/delegation-policy.md +23 -45
  36. package/dist/agent-src/rules/design-fidelity.md +32 -37
  37. package/dist/agent-src/rules/evaluator-independence.md +43 -49
  38. package/dist/agent-src/rules/test-first.md +41 -0
  39. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
  40. package/dist/agent-src/skills/ai-council/SKILL.md +11 -3
  41. package/dist/agent-src/skills/api-design/data/manifest.json +3 -3
  42. package/dist/agent-src/skills/database/data/manifest.json +4 -4
  43. package/dist/agent-src/skills/database/data/query-tuning.csv +3 -3
  44. package/dist/agent-src/skills/design-review/SKILL.md +50 -0
  45. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +14 -4
  46. package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
  47. package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
  48. package/dist/agent-src/skills/storybook-workshop/SKILL.md +42 -3
  49. package/dist/agent-src/skills/threat-modeling/SKILL.md +9 -4
  50. package/dist/agent-src/skills/threat-modeling/data/manifest.json +4 -4
  51. package/dist/agent-src/skills/verify-repair-loop/SKILL.md +37 -3
  52. package/dist/agent-src/templates/agent-settings.md +1 -1
  53. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  54. package/dist/agent-src/templates/roadmaps.md +188 -0
  55. package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  56. package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  57. package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  58. package/dist/cli-delegate/check_update_banner.js +40 -2
  59. package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
  60. package/dist/cli-delegate/chunk-5R7HOLAF.js +99 -0
  61. package/dist/cli-delegate/{chunk-IVBPRCQO.js → chunk-6KFEC3G7.js} +1 -1
  62. package/dist/cli-delegate/{chunk-2TAQ2K4V.js → chunk-6KM6ORRY.js} +55 -386
  63. package/dist/cli-delegate/{chunk-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
  64. package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-FXYUYU4N.js} +57 -13
  65. package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-GJCBB2UG.js} +168 -10
  66. package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-HDVVGPQW.js} +873 -539
  67. package/dist/cli-delegate/{chunk-H32DGUJH.js → chunk-P6ZHP5W2.js} +1 -1
  68. package/dist/cli-delegate/{chunk-57PN4GYJ.js → chunk-RGNIWTEA.js} +1 -1
  69. package/dist/cli-delegate/chunk-ROYYTEZU.js +39 -0
  70. package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-UNCRJBT3.js} +31 -7
  71. package/dist/cli-delegate/{chunk-2DK2VJWG.js → chunk-WGXTVKCQ.js} +20 -6
  72. package/dist/cli-delegate/chunk-YDPILCOL.js +152 -0
  73. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  74. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  75. package/dist/cli-delegate/cmd_conformance.js +90 -56
  76. package/dist/cli-delegate/cmd_doctor.js +7 -5
  77. package/dist/cli-delegate/cmd_explain.js +2 -2
  78. package/dist/cli-delegate/cmd_export.js +9 -7
  79. package/dist/cli-delegate/cmd_fleet.js +4 -2
  80. package/dist/cli-delegate/cmd_handoff.js +4 -4
  81. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  82. package/dist/cli-delegate/cmd_migrate.js +2 -2
  83. package/dist/cli-delegate/cmd_packs_active.js +2 -2
  84. package/dist/cli-delegate/cmd_preflight.js +4 -2
  85. package/dist/cli-delegate/cmd_prune.js +10 -8
  86. package/dist/cli-delegate/cmd_route_audit.js +2 -2
  87. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  88. package/dist/cli-delegate/cmd_session_recycle.js +2 -2
  89. package/dist/cli-delegate/cmd_settings_get.js +28 -2
  90. package/dist/cli-delegate/cmd_settings_migrate.js +9 -7
  91. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  92. package/dist/cli-delegate/cmd_sync.js +10 -8
  93. package/dist/cli-delegate/cmd_uninstall.js +16 -13
  94. package/dist/cli-delegate/cmd_update.js +1 -1
  95. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  96. package/dist/cli-delegate/cmd_validate.js +12 -10
  97. package/dist/cli-delegate/cmd_versions.js +1 -1
  98. package/dist/discovery/deprecation-report.md +1 -1
  99. package/dist/discovery/discovery-manifest.json +85 -38
  100. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  101. package/dist/discovery/discovery-manifest.summary.md +4 -4
  102. package/dist/discovery/dormancy-report.md +1 -1
  103. package/dist/discovery/orphan-report.md +1 -1
  104. package/dist/discovery/packs.json +10 -8
  105. package/dist/discovery/trust-report.md +3 -3
  106. package/dist/discovery/workspaces.json +8 -6
  107. package/dist/hooks/dispatch.js +1832 -1007
  108. package/dist/install/claudeRuleRewrite.js +14 -3
  109. package/dist/install/conflict.js +60 -10
  110. package/dist/install/conflictTracker.js +218 -0
  111. package/dist/install/fsPrimitives.js +54 -0
  112. package/dist/install/install.mjs +2007 -1481
  113. package/dist/install/manifestFiles.js +259 -0
  114. package/dist/install/preflight.js +14 -2
  115. package/dist/install/preserve.js +157 -0
  116. package/dist/install/recordedOwnership.js +132 -0
  117. package/dist/install/txlog.js +11 -2
  118. package/dist/mcp/registry-manifest.json +3 -3
  119. package/dist/mcp/server.json +2 -2
  120. package/dist/mcp/server.mjs +398 -39
  121. package/dist/router.json +1 -1
  122. package/dist/scripts/_lib/agent_settings.js +3 -2
  123. package/dist/scripts/_lib/fs_atomic.js +167 -0
  124. package/dist/scripts/_lib/installed_tools.js +460 -0
  125. package/dist/scripts/_lib/lean_projection_mode.js +3 -2
  126. package/dist/scripts/_lib/settings_renamed_keys.js +78 -0
  127. package/dist/server/routes/install.js +8 -0
  128. package/dist/server/routes/wizard.js +9 -3
  129. package/dist/server/schemas/settings.js +17 -5
  130. package/dist/shared/missionExecution.js +97 -0
  131. package/dist/ui/assets/{index-D0jaCRPM.js → index-DH1xc62v.js} +17 -17
  132. package/dist/ui/index.html +1 -1
  133. package/docs/MIGRATION.md +103 -0
  134. package/docs/contracts/settings-classes.md +26 -7
  135. package/docs/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
  136. package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
  137. package/docs/guidelines/design-fidelity-routing.md +29 -0
  138. package/package.json +1 -1
  139. package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  140. package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  141. package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  142. package/src/config/agent-settings.template.yml +139 -27
  143. package/src/config/auto-rule-budget.json +16 -0
  144. package/src/config/ci-local-parity.yml +18 -5
  145. package/src/config/continuity-surface.json +21 -0
  146. package/src/config/gate-coverage.yml +146 -13
  147. package/src/config/gate-violation-baselines.json +6 -6
  148. package/src/config/hook-token-budget.json +2 -0
  149. package/src/config/loop-surfaces.yaml +215 -0
  150. package/src/config/platform-anchor.json +3 -2
  151. package/src/config/release-gate-locality.yml +11 -0
  152. package/src/config/routing-coverage-seed.json +18 -4
  153. package/src/config/rule-enforcement-baseline.json +0 -1
  154. package/src/install/claudeRuleRewrite.ts +16 -2
  155. package/src/install/conflict.ts +65 -9
  156. package/src/install/conflictTracker.ts +299 -0
  157. package/src/install/fsPrimitives.ts +54 -0
  158. package/src/install/manifestFiles.ts +312 -0
  159. package/src/install/preflight.ts +16 -2
  160. package/src/install/preserve.ts +205 -0
  161. package/src/install/recordedOwnership.ts +149 -0
  162. package/src/install/txlog.ts +11 -2
  163. package/src/install/types.ts +19 -4
  164. package/src/scripts/_cli/cmd_conformance.ts +88 -15
  165. package/src/scripts/_cli/cmd_doctor.ts +4 -3
  166. package/src/scripts/_cli/cmd_route_explain.ts +9 -2
  167. package/src/scripts/_cli/cmd_settings_get.ts +44 -0
  168. package/src/scripts/_cli/doctor_execution.ts +90 -0
  169. package/src/scripts/_lib/activation_payload.ts +247 -0
  170. package/src/scripts/_lib/agent_settings.ts +4 -2
  171. package/src/scripts/_lib/authority_path.ts +102 -0
  172. package/src/scripts/_lib/cascade_base.ts +94 -0
  173. package/src/scripts/_lib/changelog_eras.ts +7 -2
  174. package/src/scripts/_lib/continuation_ladder.ts +149 -2
  175. package/src/scripts/_lib/council_transport.ts +129 -0
  176. package/src/scripts/_lib/delivery_ready.ts +112 -0
  177. package/src/scripts/_lib/forge_protection.ts +267 -0
  178. package/src/scripts/_lib/hook_platform_keys.ts +27 -0
  179. package/src/scripts/_lib/json_python_parity.ts +127 -0
  180. package/src/scripts/_lib/lean_projection_mode.ts +14 -3
  181. package/src/scripts/_lib/loop_surfaces.ts +382 -0
  182. package/src/scripts/_lib/mission_record.ts +190 -0
  183. package/src/scripts/_lib/obligation_frequency.ts +217 -0
  184. package/src/scripts/_lib/obligations.ts +402 -0
  185. package/src/scripts/_lib/orchestration_record.ts +11 -20
  186. package/src/scripts/_lib/release_highlights.ts +199 -10
  187. package/src/scripts/_lib/release_holds.ts +493 -0
  188. package/src/scripts/_lib/release_migration_gate.ts +184 -0
  189. package/src/scripts/_lib/rides_along.ts +102 -0
  190. package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
  191. package/src/scripts/_lib/test_delta.ts +166 -0
  192. package/src/scripts/_lib/test_provenance.ts +171 -0
  193. package/src/scripts/_lib/typed_op_grant.ts +246 -0
  194. package/src/scripts/_lib/typed_op_watch.ts +185 -0
  195. package/src/scripts/ai_council/config.ts +8 -11
  196. package/src/scripts/ai_council/fallback_config.ts +5 -3
  197. package/src/scripts/ai_council/ownership_classes.ts +118 -0
  198. package/src/scripts/ask_block_census.ts +285 -15
  199. package/src/scripts/check_enforcement_matrix.ts +624 -0
  200. package/src/scripts/check_gate_reachability.ts +173 -10
  201. package/src/scripts/check_generator_sync.ts +954 -0
  202. package/src/scripts/check_preamble_payload_budget.ts +75 -2
  203. package/src/scripts/check_release_highlights.ts +33 -1
  204. package/src/scripts/check_release_holds.ts +567 -0
  205. package/src/scripts/check_rule_projection_integrity.ts +22 -12
  206. package/src/scripts/check_test_delta.ts +294 -0
  207. package/src/scripts/check_test_weakening.ts +299 -0
  208. package/src/scripts/closure_scan.ts +333 -0
  209. package/src/scripts/compile_hook_manifest.ts +43 -3
  210. package/src/scripts/council_record_shape.ts +209 -0
  211. package/src/scripts/hook_manifest.json +1 -1
  212. package/src/scripts/hook_manifest.yaml +88 -9
  213. package/src/scripts/hooks/concern_registry.ts +4 -0
  214. package/src/scripts/hooks/design_pass_hook.ts +128 -3
  215. package/src/scripts/hooks/evidence_independence.ts +96 -0
  216. package/src/scripts/hooks/memory_recall_hook.ts +540 -0
  217. package/src/scripts/hooks/obligation_settle_hook.ts +182 -0
  218. package/src/scripts/hooks/rule_inject_hook.ts +49 -1
  219. package/src/scripts/hooks/run_continuation_hook.ts +17 -20
  220. package/src/scripts/hooks_doctor.ts +85 -0
  221. package/src/scripts/hooks_status.ts +19 -0
  222. package/src/scripts/install.ts +106 -206
  223. package/src/scripts/janitor.ts +7 -0
  224. package/src/scripts/lint_decision_classes.ts +464 -0
  225. package/src/scripts/lint_hook_manifest.ts +11 -0
  226. package/src/scripts/lint_major_migration_sections.ts +450 -0
  227. package/src/scripts/lint_plan_risk_register.ts +47 -3
  228. package/src/scripts/lint_roadmap_blockers.ts +61 -0
  229. package/src/scripts/lint_roadmap_producers.ts +202 -0
  230. package/src/scripts/measure_rule_budget.ts +101 -5
  231. package/src/scripts/new_roadmap.ts +37 -0
  232. package/src/scripts/orchestration_record.ts +0 -5
  233. package/src/scripts/release.ts +14 -98
  234. package/src/scripts/release_drill.ts +267 -1
  235. package/src/scripts/release_publication.ts +51 -0
  236. package/src/scripts/report_enforcement_drift.ts +566 -0
  237. package/src/scripts/report_host_injection_effect.ts +3 -11
  238. package/src/scripts/report_standing_payload_by_host.ts +26 -152
  239. package/src/scripts/schemas/agent-settings.schema.json +23 -0
  240. package/src/scripts/schemas/command.schema.json +4 -0
  241. package/src/scripts/scope_growth.ts +219 -0
  242. package/src/scripts/trigger_eval_grandfather.json +0 -1
  243. package/src/scripts/ui_conformance_probe.ts +622 -0
  244. package/src/server/schemas/settings.ts +31 -7
  245. package/src/shared/missionExecution.ts +126 -0
  246. package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Every roadmap producer ends in closure — mechanically, not by convention.
4
+ *
5
+ * A command that produces a plan and hands it back with open decisions in it
6
+ * has moved those decisions into execution, where meeting one costs a run.
7
+ * The entrance list used to be hard-coded in three command bodies, so the
8
+ * other six producers were simply not in it and nothing said so.
9
+ *
10
+ * The declaration is now the command's own: `produces_roadmap: true` in the
11
+ * frontmatter. This gate reds a command that declares it and does not end in
12
+ * the closure pass, which is the half a hard-coded list could never check.
13
+ *
14
+ * What counts as ending in closure: the body references the closure pass by
15
+ * one of its two names — the `/challenge-me closure` sub-command, or the
16
+ * `closure_scan` detector it runs. Deliberately a reference check rather than
17
+ * a prose check: prose drifts, and a gate that guesses at intent from wording
18
+ * is the detector that gets weakened until it finds nothing.
19
+ *
20
+ * Exit codes: 0 clean · 1 violations · 2 internal error / bad argv.
21
+ */
22
+
23
+ import * as fs from 'node:fs';
24
+ import * as path from 'node:path';
25
+ import process from 'node:process';
26
+ import { fileURLToPath } from 'node:url';
27
+
28
+ import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
29
+
30
+ const _HERE = fileURLToPath(import.meta.url);
31
+ const _PROG = 'lint_roadmap_producers';
32
+
33
+ /** The frontmatter declaration a producer carries. */
34
+ export const DECLARATION_RE = /^produces_roadmap:\s*true\s*$/m;
35
+
36
+ /** Either name of the closure pass discharges the obligation. */
37
+ export const CLOSURE_REFS: readonly RegExp[] = [
38
+ /\/challenge-me\s+closure/,
39
+ /challenge-me\/closure/,
40
+ /\bclosure_scan\b/,
41
+ ];
42
+
43
+ export interface Violation {
44
+ readonly file: string;
45
+ readonly reason: string;
46
+ }
47
+
48
+ function _frontmatter(text: string): string {
49
+ if (!text.startsWith('---')) return '';
50
+ const end = text.indexOf('\n---', 3);
51
+ return end < 0 ? '' : text.slice(0, end);
52
+ }
53
+
54
+ export function declaresProducer(text: string): boolean {
55
+ return DECLARATION_RE.test(_frontmatter(text));
56
+ }
57
+
58
+ export function endsInClosure(text: string): boolean {
59
+ return CLOSURE_REFS.some((re) => re.test(text));
60
+ }
61
+
62
+ export function checkFile(rel: string, text: string): Violation[] {
63
+ if (!declaresProducer(text)) return [];
64
+ if (endsInClosure(text)) return [];
65
+ return [
66
+ {
67
+ file: rel,
68
+ reason:
69
+ 'declares `produces_roadmap: true` and does not end in the closure pass — ' +
70
+ 'a producer hands back an execution contract, not a plan with open decisions ' +
71
+ 'in it. Reference `/challenge-me closure` (or the `closure_scan` detector) ' +
72
+ 'as the command’s last step.',
73
+ },
74
+ ];
75
+ }
76
+
77
+ function _walk(dir: string, out: string[]): void {
78
+ let entries: fs.Dirent[];
79
+ try {
80
+ entries = fs.readdirSync(dir, { withFileTypes: true });
81
+ } catch {
82
+ return;
83
+ }
84
+ for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
85
+ const full = path.join(dir, e.name);
86
+ if (e.isDirectory()) _walk(full, out);
87
+ else if (e.isFile() && e.name === 'command.md') out.push(full);
88
+ }
89
+ }
90
+
91
+ export function listCommands(root: string): string[] {
92
+ const out: string[] = [];
93
+ _walk(root, out);
94
+ return out;
95
+ }
96
+
97
+ function _repoRoot(): string {
98
+ return path.resolve(path.dirname(_HERE), '..', '..');
99
+ }
100
+
101
+ function _usage(): string {
102
+ return (
103
+ `usage: ${_PROG} [-h] [--quiet] [--json] [--self-test]\n\n` +
104
+ `Every command declaring \`produces_roadmap: true\` ends in the closure pass.\n`
105
+ );
106
+ }
107
+
108
+ export function main(argv?: readonly string[]): number {
109
+ const args = argv ?? process.argv.slice(2);
110
+ let quiet = false;
111
+ let json = false;
112
+ let selfTest = false;
113
+ const unknown: string[] = [];
114
+ for (const a of args) {
115
+ if (a === '-h' || a === '--help') {
116
+ process.stdout.write(_usage());
117
+ return 0;
118
+ } else if (a === '--quiet') quiet = true;
119
+ else if (a === '--json') json = true;
120
+ else if (a === '--self-test') selfTest = true;
121
+ else unknown.push(a);
122
+ }
123
+ if (unknown.length) {
124
+ process.stderr.write(`${_usage()}${_PROG}: error: unrecognized arguments: ${unknown.join(' ')}\n`);
125
+ return 2;
126
+ }
127
+ if (selfTest) return _selfTest();
128
+
129
+ const repo = _repoRoot();
130
+ const root = path.join(repo, 'src', 'domains');
131
+ const files = listCommands(root);
132
+ const producers = files.filter((f) => declaresProducer(fs.readFileSync(f, 'utf-8')));
133
+
134
+ try {
135
+ // The corpus is the DECLARED producers, not every command: a tree where
136
+ // nobody declares the key is a tree where the declaration was lost, and
137
+ // that is exactly the silent-disarm this assertion exists to catch.
138
+ assertScanned({
139
+ gate: _PROG,
140
+ scanned: producers.length,
141
+ units: 'roadmap producer(s)',
142
+ roots: ['src/domains'],
143
+ });
144
+ } catch (e) {
145
+ if (e instanceof DeadScopeError) {
146
+ process.stderr.write(`❌ ${e.message}\n`);
147
+ return 2;
148
+ }
149
+ throw e;
150
+ }
151
+
152
+ const violations: Violation[] = [];
153
+ for (const f of producers) {
154
+ violations.push(...checkFile(path.relative(repo, f), fs.readFileSync(f, 'utf-8')));
155
+ }
156
+
157
+ if (json) {
158
+ process.stdout.write(JSON.stringify(violations, null, 2) + '\n');
159
+ return violations.length ? 1 : 0;
160
+ }
161
+ if (violations.length) {
162
+ process.stderr.write(`❌ ${_PROG}: ${violations.length} producer(s) do not end in closure\n`);
163
+ for (const v of violations) process.stderr.write(` ${v.file} — ${v.reason}\n`);
164
+ return 1;
165
+ }
166
+ if (!quiet) {
167
+ process.stdout.write(
168
+ `✅ ${_PROG}: ${producers.length} roadmap producer(s), all ending in closure\n`,
169
+ );
170
+ }
171
+ return 0;
172
+ }
173
+
174
+ /** Both directions in-process: a producer without closure reds, one with it does not. */
175
+ function _selfTest(): number {
176
+ const withClosure = '---\nname: x\nproduces_roadmap: true\n---\n\nRun `/challenge-me closure`.\n';
177
+ const without = '---\nname: x\nproduces_roadmap: true\n---\n\nHand the plan back.\n';
178
+ const notAProducer = '---\nname: x\n---\n\nHand the plan back.\n';
179
+ const failures: string[] = [];
180
+ if (checkFile('a.md', without).length === 0) failures.push('a producer without closure did not red');
181
+ if (checkFile('b.md', withClosure).length !== 0) failures.push('a producer with closure red');
182
+ if (checkFile('c.md', notAProducer).length !== 0) failures.push('a non-producer was judged');
183
+ if (failures.length) {
184
+ for (const f of failures) process.stderr.write(`❌ ${_PROG} --self-test: ${f}\n`);
185
+ return 1;
186
+ }
187
+ process.stdout.write(`✅ ${_PROG} --self-test: both directions hold\n`);
188
+ return 0;
189
+ }
190
+
191
+ function _isCliEntry(): boolean {
192
+ if (!process.argv[1]) return false;
193
+ try {
194
+ return fs.realpathSync(_HERE) === fs.realpathSync(path.resolve(process.argv[1]));
195
+ } catch {
196
+ return false;
197
+ }
198
+ }
199
+
200
+ if (_isCliEntry() || process.argv[1] === _HERE) {
201
+ process.exitCode = main();
202
+ }
@@ -38,6 +38,7 @@ const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
38
38
 
39
39
  const OVERRIDES_FILE = path.join(REPO_ROOT, 'docs', 'contracts', 'iron-law-overrides.txt');
40
40
  const TREND_FILE = path.join(REPO_ROOT, 'agents', 'runtime', '.rule-budget-history.jsonl');
41
+ const AUTO_BUDGET_FILE = path.join(REPO_ROOT, 'src', 'config', 'auto-rule-budget.json');
41
42
 
42
43
  // Council R2 amendments (2026-05-06) — see docs/contracts/kernel-membership.md § 5.1.
43
44
  // Per-rule cap raised 1.5k → 2.5k; warning band raised 1.2k → 2.0k.
@@ -59,6 +60,8 @@ export interface RuleMeasure {
59
60
  lines: number;
60
61
  tokens_gpt: number;
61
62
  tokens_claude: number;
63
+ /** Iron-Law headings in the body — the obligation count a char count hides. */
64
+ iron_laws: number;
62
65
  }
63
66
 
64
67
  export interface Aggregate {
@@ -80,6 +83,7 @@ export interface Aggregate {
80
83
  per_rule_hard: number;
81
84
  per_rule_target: number;
82
85
  per_rule_override_ceiling: number;
86
+ iron_law_total: number;
83
87
  oversize_rules: RuleMeasure[];
84
88
  top5_largest: RuleMeasure[];
85
89
  }
@@ -130,9 +134,32 @@ export function measure_rule(p: string): RuleMeasure {
130
134
  lines: _count(body, '\n'),
131
135
  tokens_gpt: token_count.gpt_tokens(body).tokens,
132
136
  tokens_claude: token_count.claude_tokens(body).tokens,
137
+ iron_laws: count_iron_laws(body),
133
138
  };
134
139
  }
135
140
 
141
+ /**
142
+ * Count Iron-Law HEADINGS in a rule body.
143
+ *
144
+ * road-to-design-fidelity-proof Phase 3.2. A rule is not an instruction:
145
+ * `token-efficiency` carries five Iron Laws in one file, so a file count and a
146
+ * char count both understate what a model is asked to hold at once. The
147
+ * instruction-following literature measures against the number of SIMULTANEOUS
148
+ * constraints, and an Iron-Law heading is the closest proxy this tree can count
149
+ * deterministically.
150
+ *
151
+ * Heading forms accepted, matching `preservation-guard`'s own definition: any
152
+ * level, optional `The`, singular or plural, optional trailing number or
153
+ * qualifier (`## Iron Law 2`, `## Iron Law — Gate`). A prose mention is NOT a
154
+ * heading, and a heading that merely contains the words later (`## Why the Iron
155
+ * Law exists`) is not one either — the phrase must open the heading text, or
156
+ * every retrospective paragraph would inflate the count.
157
+ */
158
+ export function count_iron_laws(body: string): number {
159
+ const re = /^#{1,6}[ \t]+(?:The[ \t]+)?Iron[ \t]+Laws?\b/gim;
160
+ return (body.match(re) ?? []).length;
161
+ }
162
+
136
163
  /**
137
164
  * Collect rule measurements from every source root (multi-root aware).
138
165
  * Deduplicates on logical rule id (stem) — first root wins. Mirrors
@@ -198,6 +225,7 @@ export function aggregate(rules: RuleMeasure[]): Aggregate {
198
225
  per_rule_hard: PER_RULE_HARD,
199
226
  per_rule_target: PER_RULE_TARGET,
200
227
  per_rule_override_ceiling: PER_RULE_OVERRIDE_CEILING,
228
+ iron_law_total: _sum(rules.map((r) => r.iron_laws)),
201
229
  oversize_rules: rules
202
230
  .filter((r) => r.chars > PER_RULE_HARD)
203
231
  .sort((a, b) => _cmp([-a.chars, a.id], [-b.chars, b.id])),
@@ -324,6 +352,59 @@ export function kernel_budget_check(
324
352
  return [0, out];
325
353
  }
326
354
 
355
+ export interface AutoBudgetConfig {
356
+ baseline_chars: number;
357
+ }
358
+
359
+ /** Read the auto-bucket baseline. Throws rather than defaulting: a ratchet that
360
+ * silently falls back to "no bound" is a permanently green gate. */
361
+ export function load_auto_budget(file: string = AUTO_BUDGET_FILE): AutoBudgetConfig {
362
+ const raw = JSON.parse(fs.readFileSync(file, 'utf-8')) as { baseline_chars?: unknown };
363
+ if (typeof raw.baseline_chars !== 'number') {
364
+ throw new Error(`${file}: baseline_chars must be a number`);
365
+ }
366
+ return { baseline_chars: raw.baseline_chars };
367
+ }
368
+
369
+ /**
370
+ * Enforce the `auto`-bucket growth ratchet. Returns `[exit_code, report_lines]`.
371
+ *
372
+ * road-to-design-fidelity-proof Phase 3.1. `kernel_budget_check` above bounds
373
+ * nine rules; this bounds the other 107, which carry ~93 % of the estate's rule
374
+ * prose and grew against nothing. Shrink-only: growth fails, a shrink passes and
375
+ * says how much slack it created, so the number walks down rather than becoming
376
+ * unused headroom.
377
+ *
378
+ * The Iron-Law total is REPORTED, never gated. No measurement in this tree says
379
+ * how many simultaneous obligations this layer can carry, and a bound nobody
380
+ * measured would publish a guess as a finding.
381
+ */
382
+ export function auto_budget_check(agg: Aggregate, cfg: AutoBudgetConfig): [number, string[]] {
383
+ const out: string[] = [];
384
+ const delta = agg.auto_chars - cfg.baseline_chars;
385
+ const signed = delta >= 0 ? `+${delta}` : String(delta);
386
+ out.push(
387
+ `auto-bucket: ${agg.auto_chars} / ${cfg.baseline_chars} chars ` +
388
+ `(${agg.auto_count} rules, ${signed} vs baseline)`,
389
+ );
390
+ out.push(`iron-law total: ${agg.iron_law_total} heading(s) — reported, not gated`);
391
+ out.push('');
392
+ if (delta > 0) {
393
+ out.push(`\u274c auto budget check: the auto bucket grew ${signed} chars`);
394
+ out.push(
395
+ ' The baseline may only move DOWN. Walk the bucket back, or state the ' +
396
+ 'compensating reduction — raising it to clear this is config weakening.',
397
+ );
398
+ return [1, out];
399
+ }
400
+ out.push(
401
+ delta === 0
402
+ ? '\u2705 auto budget check: pass (at the baseline)'
403
+ : `\u2705 auto budget check: pass (${signed} chars of slack — ratchet it down)`,
404
+ );
405
+ return [0, out];
406
+ }
407
+
327
408
  /**
328
409
  * Append a daily snapshot to agents/runtime/.rule-budget-history.jsonl.
329
410
  * Idempotent per UTC day. Mirrors `trend_append`. Returns `[exit, msg]`.
@@ -363,19 +444,22 @@ export function trend_append(agg: Aggregate): [number, string] {
363
444
  interface Args {
364
445
  json: boolean;
365
446
  kernelBudgetCheck: boolean;
447
+ autoBudgetCheck: boolean;
366
448
  trendAppend: boolean;
367
449
  }
368
450
 
369
451
  function parse_args(argv: string[]): Args {
370
452
  let json = false;
371
453
  let kernelBudgetCheck = false;
454
+ let autoBudgetCheck = false;
372
455
  let trendAppend = false;
373
456
  for (const a of argv) {
374
457
  if (a === '--json') json = true;
375
458
  else if (a === '--kernel-budget-check') kernelBudgetCheck = true;
459
+ else if (a === '--auto-budget-check') autoBudgetCheck = true;
376
460
  else if (a === '--trend-append') trendAppend = true;
377
461
  }
378
- return { json, kernelBudgetCheck, trendAppend };
462
+ return { json, kernelBudgetCheck, autoBudgetCheck, trendAppend };
379
463
  }
380
464
 
381
465
  export function main(argv: string[] = process.argv.slice(2)): number {
@@ -384,10 +468,22 @@ export function main(argv: string[] = process.argv.slice(2)): number {
384
468
  const rules = collect();
385
469
  const agg = aggregate(rules);
386
470
 
387
- if (args.kernelBudgetCheck) {
388
- const overrides = load_overrides();
389
- const [code, report] = kernel_budget_check(rules, agg, overrides);
390
- process.stdout.write(report.join('\n') + '\n');
471
+ // Both budget flags may be passed in one invocation, and the CI step does
472
+ // exactly that — a second task step would be a new gate `check_ci_local_parity`
473
+ // has to be told about, for two checks over one measurement.
474
+ if (args.kernelBudgetCheck || args.autoBudgetCheck) {
475
+ let code = 0;
476
+ if (args.kernelBudgetCheck) {
477
+ const overrides = load_overrides();
478
+ const [kCode, report] = kernel_budget_check(rules, agg, overrides);
479
+ process.stdout.write(report.join('\n') + '\n');
480
+ code = Math.max(code, kCode);
481
+ }
482
+ if (args.autoBudgetCheck) {
483
+ const [aCode, report] = auto_budget_check(agg, load_auto_budget());
484
+ process.stdout.write(report.join('\n') + '\n');
485
+ code = Math.max(code, aCode);
486
+ }
391
487
  return code;
392
488
  }
393
489
 
@@ -48,6 +48,13 @@ function titleOf(slug: string): string {
48
48
  return slug.replace(/-/g, ' ');
49
49
  }
50
50
 
51
+ // The two heading markers the release-holds block emits, interpolated rather
52
+ // than written literally: `lint_code_comments` strips indentation before
53
+ // matching, so a template-literal line beginning `## ` reads to it as a
54
+ // markdown heading left in source. The emitted text is unchanged.
55
+ const H2 = '#'.repeat(2) + ' ';
56
+ const H3 = '#'.repeat(3) + ' ';
57
+
51
58
  export function skeleton(slug: string, complexity: 'lightweight' | 'structural', date: string): string {
52
59
  return `---
53
60
  complexity: ${complexity}
@@ -73,6 +80,36 @@ could tell whether it happened.
73
80
  verify: the check that proves it — a command, a test, an observable
74
81
  state. Never "looks right".
75
82
 
83
+ <!-- Release holds — emitted commented out, because the default is that there is
84
+ not one. Uncomment ONLY if an intermediate tree state of this roadmap must
85
+ not be published. Template rule 28 is the contract; the authoring order is
86
+ re-sequence -> guard -> hold, and a hold is the last resort, never the
87
+ first tool.
88
+
89
+ Record the outcome either way. A roadmap that considered a hold and reached
90
+ rung 1 or 2 instead writes the one-line \`resequenced:\` / \`guarded:\` note
91
+ below and deletes the entry; that note is a counted outcome, not a comment.
92
+
93
+ resequenced: <one line — the broken intermediate state, and the phase cut
94
+ that removed it, so no window was ever needed.>
95
+
96
+ ${H2}Release holds
97
+
98
+ ${H3}hold: <kebab-id>
99
+ - **Channel:** all (all | latest; omitted parses to all)
100
+ - **Opened by:** <phase.step> (the checkbox whose [x] opens the window)
101
+ - **Cleared by:** <phase.step> (the checkbox whose [x] closes it)
102
+ - **State:** <one sentence naming what is broken in the tree while open.>
103
+ - **Why not a guard:** <why the guard rung failed, concretely. Mandatory —
104
+ an entry without it is malformed and reddens CI.>
105
+
106
+ Both named steps carry an inline HTML-comment marker on the checkbox line
107
+ itself — \`opens-hold: <kebab-id>\` on the opener, \`clears-hold: <kebab-id>\`
108
+ on the clearer — so the binding is readable from the checkbox and not only
109
+ from this section. The clearing step MUST carry a \`verify:\` field: a hold
110
+ cleared by an unverified flip is a hold cleared by assertion.
111
+ -->
112
+
76
113
  ## Risk Register
77
114
  <!-- risk-review: v1 | reviewed: ${date} | reviewer: claude/host -->
78
115
 
@@ -18,7 +18,6 @@
18
18
  * [--rules-carried 32] [--rules-used 5] \
19
19
  * [--work-tokens 41000] [--floor-provenance measured|estimated] \
20
20
  * [--capsule-emitted true|false] [--capsule-entries 9] \
21
- * [--watermark-step 8] [--saturation-step 4] [--trigger-arm-earlier watermark|saturation|tie] \
22
21
  * [--dir <audit-dir>] [--dry-run]
23
22
  *
24
23
  * Read by `src/scripts/orchestration_savings_report.ts`.
@@ -42,7 +41,6 @@ import {
42
41
  type LineOutcome,
43
42
  type RecordInput,
44
43
  type RouteTaken,
45
- type TriggerArm,
46
44
  } from './_lib/orchestration_record.js';
47
45
 
48
46
  // Exported so other writers of the same audit-log-v1 orchestration line
@@ -174,9 +172,6 @@ export function main(argv: string[] = process.argv.slice(2)): number {
174
172
  floor_provenance: str(flags, 'floor-provenance') as Provenance | undefined,
175
173
  capsule_emitted: bool(flags, 'capsule-emitted'),
176
174
  capsule_entries: int(flags, 'capsule-entries'),
177
- watermark_step: int(flags, 'watermark-step'),
178
- saturation_step: int(flags, 'saturation-step'),
179
- trigger_arm_earlier: str(flags, 'trigger-arm-earlier') as TriggerArm | undefined,
180
175
  phase: str(flags, 'phase') as LinePhase | undefined,
181
176
  outcome: str(flags, 'outcome') as LineOutcome | undefined,
182
177
  confidence_band: str(flags, 'confidence-band') as Band | undefined,
@@ -122,9 +122,7 @@ import {
122
122
  import {
123
123
  HEAD_LABELS,
124
124
  type MixObligation,
125
- collect_span_commits,
126
- derive_category_hits,
127
- render_derived_head_values,
125
+ derive_head_prefill,
128
126
  } from './_lib/release_highlights.js';
129
127
  import { canPrompt, promptLine } from './_lib/tty_prompt.js';
130
128
  import { preflightPosition } from './_lib/release_position.js';
@@ -137,6 +135,11 @@ import {
137
135
  release_notes_from_section,
138
136
  render_mix_response,
139
137
  } from './_lib/release_material.js';
138
+ import {
139
+ assert_major_migration_section,
140
+ assert_scheduled_deprecations_clear,
141
+ } from './_lib/release_migration_gate.js';
142
+ import { releaseHoldPreflight } from './_lib/release_holds.js';
140
143
 
141
144
  // `__doc__.splitlines()[0]` in `_parse_args` — the argparse description. Kept
142
145
  // as a referenceable constant so the first docstring line is preserved exactly.
@@ -144,7 +147,6 @@ const MODULE_DOC_FIRST_LINE = 'End-to-end release automation for `event4u/agent-
144
147
 
145
148
  const _HERE = fileURLToPath(import.meta.url);
146
149
 
147
-
148
150
  import {
149
151
  ArgparseExit,
150
152
  AUGMENT_MARKETPLACE_JSON,
@@ -169,7 +171,6 @@ import {
169
171
  _rstrip,
170
172
  _splitlines,
171
173
  } from './release_env.js';
172
- import type { RunResult } from './release_publication.js';
173
174
  // Re-export surface for the names tests import from `release.js` and that moved
174
175
  // into the publication unit — eleven since the test-count-trend unit followed. `export ... from` rather than a bare import,
175
176
  // because these are not USED here — an unused import would be dropped and the
@@ -226,7 +227,6 @@ import {
226
227
  // existing block resolve. This is the whole of step 1.1's "re-export shape that
227
228
  // keeps callers unaffected": no caller and no test import path changes.
228
229
 
229
-
230
230
  // ---------------------------------------------------------------------------
231
231
  // Parity helpers — code-point length, comma grouping, regex escape, JSON
232
232
  // ---------------------------------------------------------------------------
@@ -304,8 +304,7 @@ export { RELEASE_HEAD_CAP_LINES } from './_lib/release_material.js';
304
304
  */
305
305
  function _derive_head_prefill(prev: string | null): Record<string, string> {
306
306
  try {
307
- const span = collect_span_commits(prev, 'HEAD', REPO_ROOT);
308
- return render_derived_head_values(derive_category_hits(span));
307
+ return derive_head_prefill(prev, 'HEAD', REPO_ROOT);
309
308
  } catch (err) {
310
309
  process.stderr.write(
311
310
  `warning: could not derive release-head highlights (${(err as Error).message}); ` +
@@ -698,93 +697,6 @@ function set_template_pin(p: string, version: string): void {
698
697
  fs.writeFileSync(p, lines.join('\n'), 'utf-8');
699
698
  }
700
699
 
701
- /**
702
- * Refuse a MAJOR cut that carries a scheduled deprecation due at or before it.
703
- *
704
- * **The trigger is the shape of the TARGET, not a comparison with the current
705
- * version.** A major target is `X.0.0` — and that one predicate covers every
706
- * path to a major: the `--as major` flag, an explicit `--version 13.0.0`,
707
- * auto-detection from a `feat!:` commit, AND `--resume`, where
708
- * `_detect_in_flight_target()` returns the already-bumped `package.json`
709
- * version so `target === current` and any current-vs-target comparison
710
- * silently returns. Resume was the fourth path an earlier version of this
711
- * guard missed while its own comment claimed three paths converged.
712
- *
713
- * Refusing a resumed release can strand a partially-completed one, and that is
714
- * the deliberate trade: a stranded release is recoverable by fixing the table
715
- * and resuming again, whereas a major shipped over a missed commitment is the
716
- * failure this whole surface exists to prevent.
717
- *
718
- * The target version is PASSED to the gate. Without it the gate falls back to
719
- * `package.json`, which at the cut to N still reads N-1 — so a row committed
720
- * to N reads as one major early and passes, and only rows already a major late
721
- * could ever be refused. That is the lateness being prevented, so measuring
722
- * against the shipped version would have made the refusal fire exactly one
723
- * major too late, forever.
724
- *
725
- * Runs under `--dry-run` too, unlike the rest of preflight: this check is one
726
- * subprocess reading two files, so the "keep a preview fast" rationale that
727
- * excludes the ~15s test-trend collection does not apply — and a preview that
728
- * reports green for the single condition that will refuse the real run is the
729
- * case an operator runs a preview to discover. It REPORTS there rather than
730
- * dying: `--dry-run` exiting 0 before `execute()` and before `preflight()` is a
731
- * contract this file's own tests assert, and an earlier revision of this guard
732
- * broke it by sitting above the dry-run branch with no preview mode.
733
- *
734
- * @param runner Seam for the gate invocation. Production passes nothing and
735
- * gets the real `run`; tests inject a stub, because the alternative — reaching
736
- * this branch only by mutating `docs/MIGRATION.md` — would make the refusal
737
- * path testable exclusively through a tracked-file edit.
738
- */
739
- export function assert_scheduled_deprecations_clear(
740
- target: string,
741
- runner: (args: readonly string[]) => RunResult = (args) => run(args, { check: false, capture: true }),
742
- opts: { previewOnly?: boolean } = {},
743
- ): void {
744
- const [, minor, patch] = parse_version(target);
745
- if (minor !== 0 || patch !== 0) {
746
- return;
747
- }
748
- const res = runner([
749
- './scripts-run',
750
- 'src/scripts/lint_scheduled_deprecations',
751
- '--cutting',
752
- target,
753
- ]);
754
- if (res.returncode === 0) {
755
- return;
756
- }
757
- process.stderr.write(res.stdout);
758
- process.stderr.write(res.stderr);
759
- if (res.returncode !== 1) {
760
- // The gate reserves 1 for a finding and 2 for a usage/environment
761
- // failure. Diagnosing the latter as "the table has an overdue row"
762
- // sends the releaser to edit a file that is not the problem.
763
- die(
764
- `refusing the ${target} cut: the scheduled-deprecations check could not run ` +
765
- `(exit ${String(res.returncode)}). That is an environment or usage failure, not a ` +
766
- 'finding in docs/MIGRATION.md — fix the invocation or the checkout, then re-run.',
767
- );
768
- }
769
- if (opts.previewOnly === true) {
770
- // The preview's job is to SHOW what the real run will refuse. Dying
771
- // here would break the `--dry-run exits 0` contract asserted elsewhere
772
- // in this file's tests — trading a documented exit code for a message
773
- // that has already been printed above.
774
- process.stderr.write(
775
- `\n(dry-run) the ${target} cut WOULD BE REFUSED for the reason above. ` +
776
- 'Previewing only; exit code unchanged.\n',
777
- );
778
- return;
779
- }
780
- die(
781
- `refusing the ${target} cut: the scheduled-deprecations table in docs/MIGRATION.md ` +
782
- 'has a row due at or before this major, or one that cannot be resolved. Act on ' +
783
- "it — perform the removal in its own change, or revise the row's commitment and " +
784
- 'record why the surface stays — then re-run.',
785
- );
786
- }
787
-
788
700
  // ─── preflight ────────────────────────────────────────────────────────────────
789
701
 
790
702
  /**
@@ -878,6 +790,7 @@ function preflight(target: string, opts: { resume?: boolean; ci?: boolean } = {}
878
790
  die(`tag '${target}' already exists; nothing to release`);
879
791
  }
880
792
  }
793
+ releaseHoldPreflight(REPO_ROOT, die);
881
794
  }
882
795
 
883
796
  // ─── plan ─────────────────────────────────────────────────────────────────────
@@ -1109,9 +1022,9 @@ function execute(
1109
1022
  if (pr_merged) {
1110
1023
  _step(4, total, 'PR already merged — skip push');
1111
1024
  } else {
1112
- // `git push -u` is naturally idempotent — it prints "Everything
1113
- // up-to-date" when remote already matches. push_release_branch
1114
- // additionally absorbs a remote that moved under us.
1025
+ // push_release_branch reads the remote FIRST — it skips the push when
1026
+ // the remote is already at head (a no-op push still runs the pre-push
1027
+ // hook, which then refuses over pending CI) and merges one that moved.
1115
1028
  // Not a `_step`: a second `[4/11]` makes the cited evidence anchors ambiguous.
1116
1029
  process.stdout.write(' · verifying release gates locally (`task release:verify -- --cheap`)\n');
1117
1030
  run(local_release_gate_argv());
@@ -1673,6 +1586,7 @@ function main(argv: readonly string[] | null = null): number {
1673
1586
  head: _derive_head_prefill(prev),
1674
1587
  mix: measure_mix_obligation(target, prev),
1675
1588
  });
1589
+ assert_major_migration_section(target, full, { previewOnly: args.dry_run });
1676
1590
 
1677
1591
  // Era-split planning — gate on the POST-release view (2026-07-07 fix).
1678
1592
  // The drift test measures the era AFTER this release's section is
@@ -1777,6 +1691,8 @@ export {
1777
1691
  bump_version,
1778
1692
  commits_since,
1779
1693
  infer_bump,
1694
+ assert_major_migration_section,
1695
+ assert_scheduled_deprecations_clear,
1780
1696
  render_changelog_entry,
1781
1697
  _changelog_line,
1782
1698
  _cap_body,