@event4u/agent-config 14.13.0 → 14.14.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 (101) 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 +116 -387
  5. package/dist/agent-src/commands/analyze/inbox.md +36 -0
  6. package/dist/agent-src/rules/code-comment-discipline.md +6 -0
  7. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +6 -6
  8. package/dist/agent-src/scripts/guarded_baseline.ts +22 -7
  9. package/dist/agent-src/scripts/stubs_due.ts +118 -2
  10. package/dist/agent-src/scripts/update_roadmap_progress.ts +8 -8
  11. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  12. package/dist/cli-delegate/cmd_conformance.js +38 -1
  13. package/dist/discovery/deprecation-report.md +1 -1
  14. package/dist/discovery/discovery-manifest.json +4 -4
  15. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  16. package/dist/discovery/discovery-manifest.summary.md +1 -1
  17. package/dist/discovery/dormancy-report.md +1 -1
  18. package/dist/discovery/orphan-report.md +1 -1
  19. package/dist/discovery/packs.json +2 -2
  20. package/dist/discovery/trust-report.md +1 -1
  21. package/dist/discovery/workspaces.json +2 -2
  22. package/dist/hooks/dispatch.js +1438 -711
  23. package/dist/mcp/registry-manifest.json +2 -2
  24. package/dist/mcp/server.json +2 -2
  25. package/dist/mcp/server.mjs +112 -17
  26. package/docs/guidelines/agent-infra/guarded-baseline.md +1 -1
  27. package/docs/guidelines/code-clarity.md +18 -0
  28. package/package.json +1 -1
  29. package/src/agent-src/scripts/archive_completed_roadmaps.ts +6 -6
  30. package/src/agent-src/scripts/guarded_baseline.ts +22 -7
  31. package/src/agent-src/scripts/stubs_due.ts +118 -2
  32. package/src/agent-src/scripts/update_roadmap_progress.ts +8 -8
  33. package/src/config/estate-count-budget.json +20 -1
  34. package/src/config/gate-coverage.yml +150 -0
  35. package/src/config/gate-violation-baselines.json +8 -3
  36. package/src/config/preamble-payload-budget.json +19 -11
  37. package/src/config/rule-stub-ceilings.json +14 -7
  38. package/src/scripts/_dispatch.bash +0 -4
  39. package/src/scripts/_lib/activation_ladder.ts +47 -3
  40. package/src/scripts/_lib/activation_receipt_producer.ts +341 -0
  41. package/src/scripts/_lib/adr_frontmatter.ts +26 -0
  42. package/src/scripts/_lib/candidate_pair_delta.ts +349 -0
  43. package/src/scripts/_lib/carrier_status.ts +118 -0
  44. package/src/scripts/_lib/cascade_stage_enumeration.ts +92 -0
  45. package/src/scripts/_lib/corpus_manifest.ts +726 -0
  46. package/src/scripts/_lib/delivery_arm_experiment.ts +18 -2
  47. package/src/scripts/_lib/estate_carrier_cases.ts +72 -0
  48. package/src/scripts/_lib/evaluation_cascade.ts +452 -0
  49. package/src/scripts/_lib/evolution_roi.ts +562 -0
  50. package/src/scripts/_lib/harness_evolution_guards.ts +10 -0
  51. package/src/scripts/_lib/lexical_shortlist.ts +215 -0
  52. package/src/scripts/_lib/llm_candidate_proposer.ts +461 -0
  53. package/src/scripts/_lib/llm_proposer_transport.ts +153 -0
  54. package/src/scripts/_lib/pathology_archive.ts +393 -0
  55. package/src/scripts/_lib/promotion_capability.ts +315 -0
  56. package/src/scripts/_lib/promotion_evidence.ts +414 -0
  57. package/src/scripts/_lib/promotion_review.ts +294 -0
  58. package/src/scripts/_lib/prompt_shape.ts +92 -0
  59. package/src/scripts/_lib/release_highlights.ts +70 -1
  60. package/src/scripts/_lib/release_material.ts +125 -2
  61. package/src/scripts/_lib/routing_corpus.ts +307 -0
  62. package/src/scripts/_lib/routing_index_input.ts +104 -0
  63. package/src/scripts/_lib/rule_injection.ts +22 -1
  64. package/src/scripts/_lib/semantic_noop.ts +167 -0
  65. package/src/scripts/activation_receipt.ts +258 -0
  66. package/src/scripts/ai_council/cross_exam.ts +128 -0
  67. package/src/scripts/ai_council/explain_route.ts +157 -0
  68. package/src/scripts/ai_council/information_gain.ts +293 -0
  69. package/src/scripts/ai_council/leakage_corpus.ts +385 -0
  70. package/src/scripts/ai_council/leakage_patterns.ts +453 -0
  71. package/src/scripts/ai_council/minority_retention.ts +171 -0
  72. package/src/scripts/ai_council/recouncil_savings.ts +299 -0
  73. package/src/scripts/ai_council/replay_route.ts +223 -0
  74. package/src/scripts/ai_council/reviewer_assignment.ts +279 -0
  75. package/src/scripts/ai_council/routing_training_row.ts +197 -0
  76. package/src/scripts/ai_council/synthesis_strategy.ts +159 -0
  77. package/src/scripts/check_adr_frontmatter.ts +4 -9
  78. package/src/scripts/check_estate_count.ts +205 -24
  79. package/src/scripts/check_release_highlights.ts +96 -13
  80. package/src/scripts/check_roadmap_trackable.ts +17 -2
  81. package/src/scripts/corpus_manifest.ts +235 -0
  82. package/src/scripts/evolution_lab.ts +181 -8
  83. package/src/scripts/hook_manifest.json +1 -1
  84. package/src/scripts/hook_manifest.yaml +28 -6
  85. package/src/scripts/hooks/comment_discipline_hook.ts +146 -0
  86. package/src/scripts/hooks/concern_registry.ts +2 -0
  87. package/src/scripts/hooks/turn_end_gate_hook.ts +9 -1
  88. package/src/scripts/language_mirror_hook.ts +14 -2
  89. package/src/scripts/lint_carrier_integrity.ts +820 -0
  90. package/src/scripts/lint_code_comments.ts +643 -0
  91. package/src/scripts/lint_plan_risk_register.ts +39 -19
  92. package/src/scripts/lint_promotion_paths.ts +779 -0
  93. package/src/scripts/llm_propose.ts +256 -0
  94. package/src/scripts/measure_delivery_sets.ts +361 -0
  95. package/src/scripts/measure_routing_signal.ts +307 -0
  96. package/src/scripts/model_rule_injection.ts +19 -3
  97. package/src/scripts/probe_council_retention.ts +495 -0
  98. package/src/scripts/release.ts +21 -37
  99. package/src/scripts/release_drill.ts +143 -0
  100. package/src/scripts/release_env.ts +35 -0
  101. package/src/scripts/release_publication.ts +84 -0
@@ -31,11 +31,13 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
31
31
 
32
32
  import {
33
33
  _MERGE_UPDATE_ROUNDS,
34
+ _set_changelog_reader,
34
35
  _set_exec_override,
35
36
  execute,
36
37
  Plan,
37
38
  SystemExitError,
38
39
  } from './release.js';
40
+ import { DERIVED_MARKER } from './_lib/release_highlights.js';
39
41
 
40
42
  const _HERE = fileURLToPath(import.meta.url);
41
43
  const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
@@ -92,6 +94,34 @@ interface WorldConfig {
92
94
  * removes.
93
95
  */
94
96
  changelog?: string;
97
+ /**
98
+ * Content the WORKING-TREE read returns — `read_changelog_text()`, used by
99
+ * step 5 (PR body) and step 8 (annotated-tag message).
100
+ *
101
+ * Added 2026-09-01 with the publication guard (roadmap § 3.1). `changelog`
102
+ * above only covers `git show <tag>:CHANGELOG.md`; the working tree was
103
+ * still the REAL file, whose `14.13.0` section carries four
104
+ * `_auto-derived, rewrite before merge:_` lines. A guard at the tag call
105
+ * site therefore refused every scenario for a reason unrelated to what the
106
+ * scenario tested — the same coupling `changelog` was introduced to
107
+ * remove, on the other read.
108
+ *
109
+ * Defaults to `defaultChangelogFixture(target)`, which is policy-valid by
110
+ * construction.
111
+ */
112
+ changelog_file?: string;
113
+ /**
114
+ * The tag already exists locally and was never pushed — the § 3.2 resume
115
+ * state, which reached `_push_tag` having read no changelog at all.
116
+ */
117
+ tag_created_unpushed?: boolean;
118
+ /**
119
+ * The tag is already on the remote, so step 8 skips entirely and step 9 —
120
+ * the GitHub Release body — is the FIRST irreversible transition the run
121
+ * reaches. The only state in which the Release guard can be exercised on
122
+ * its own, because a marker otherwise stops the run at step 8.
123
+ */
124
+ tag_already_remote?: boolean;
95
125
  }
96
126
 
97
127
  /**
@@ -122,6 +152,21 @@ export function defaultChangelogFixture(target: string): string {
122
152
  ].join('\n');
123
153
  }
124
154
 
155
+ /**
156
+ * `defaultChangelogFixture` with one head line still carrying the generator's
157
+ * draft marker — the exact state five released sections shipped in.
158
+ *
159
+ * Built from `DERIVED_MARKER` rather than from a literal string: a fixture
160
+ * that hardcodes the marker text keeps passing after the marker is renamed,
161
+ * which is a guard policing a population of zero.
162
+ */
163
+ export function markedChangelogFixture(target: string): string {
164
+ return defaultChangelogFixture(target).replace(
165
+ '- fixture entry for the release drill',
166
+ `### Release highlights\n\n- **Behaviour changes:** ${DERIVED_MARKER} rule/schema diffs in abc1234.\n\n- fixture entry for the release drill`,
167
+ );
168
+ }
169
+
125
170
  /**
126
171
  * A scriptable stand-in for every git/gh/task command execute() issues.
127
172
  * Stateful where the real world is: checkouts move HEAD, `git tag` makes the
@@ -144,6 +189,8 @@ class FakeWorld {
144
189
  private merge_fails_hard: boolean;
145
190
  private checks_fail: boolean;
146
191
  private readonly changelog: string;
192
+ /** Working-tree content; see `WorldConfig.changelog_file`. */
193
+ readonly changelog_file: string;
147
194
 
148
195
  constructor(
149
196
  readonly branch: string,
@@ -152,6 +199,9 @@ class FakeWorld {
152
199
  ) {
153
200
  this.push_rejections = cfg.push_rejections ?? 0;
154
201
  this.changelog = cfg.changelog ?? defaultChangelogFixture(target);
202
+ this.changelog_file = cfg.changelog_file ?? defaultChangelogFixture(target);
203
+ this.tag_local = (cfg.tag_created_unpushed ?? false) || (cfg.tag_already_remote ?? false);
204
+ this.tag_remote = cfg.tag_already_remote ?? false;
155
205
  this.behind_probes = cfg.behind_probes ?? 0;
156
206
  this.merge_races_once = cfg.merge_races_once ?? false;
157
207
  this.merge_fails_hard = cfg.merge_fails_hard ?? false;
@@ -325,6 +375,95 @@ function _count(world: FakeWorld, needle: string): number {
325
375
  }
326
376
 
327
377
  const SCENARIOS: Record<string, Scenario> = {
378
+ 'marker-refuses-before-tag': {
379
+ summary:
380
+ 'step 8: a draft marker in the merged section refuses BEFORE the annotated tag is created',
381
+ config: { changelog_file: markedChangelogFixture(current_version()) },
382
+ expect_success: false,
383
+ verify: (w, err) => {
384
+ const f: string[] = [];
385
+ _expect(
386
+ (err ?? '').includes('refusing to publish the annotated tag'),
387
+ `died for the wrong reason: ${err ?? '(no error)'}`,
388
+ f,
389
+ );
390
+ // The load-bearing assertion, and it is about ORDER, not about the
391
+ // message: nothing irreversible may run after the refusal.
392
+ _expect(
393
+ !w.calls.some((c) => c.startsWith('git tag -a ')),
394
+ 'an annotated tag was created after the refusal',
395
+ f,
396
+ );
397
+ _expect(!w.tag_local && !w.tag_remote, 'a tag exists after the refusal', f);
398
+ _expect(!w.release_created, 'a GitHub Release was created after the refusal', f);
399
+ return f;
400
+ },
401
+ },
402
+ 'marker-refuses-resumed-tag-push': {
403
+ summary:
404
+ 'step 8 (§ 3.2): a tag created but never pushed is NOT pushed when its section carries the marker',
405
+ config: {
406
+ tag_created_unpushed: true,
407
+ changelog_file: markedChangelogFixture(current_version()),
408
+ },
409
+ expect_success: false,
410
+ verify: (w, err) => {
411
+ const f: string[] = [];
412
+ _expect(
413
+ (err ?? '').includes('refusing to publish the tag push (resumed)'),
414
+ `died for the wrong reason: ${err ?? '(no error)'}`,
415
+ f,
416
+ );
417
+ // The recorded bypass: this path read no changelog at all, so the
418
+ // push was the FIRST thing that happened. It must now be the thing
419
+ // that does not happen.
420
+ _expect(!w.tag_remote, 'the tag was pushed after the refusal', f);
421
+ _expect(
422
+ !w.calls.includes(`git push origin ${w.target}`),
423
+ 'a tag push ran after the refusal',
424
+ f,
425
+ );
426
+ _expect(!w.release_created, 'a GitHub Release was created after the refusal', f);
427
+ return f;
428
+ },
429
+ },
430
+ 'marker-refuses-github-release': {
431
+ summary:
432
+ 'step 9: with the tag already pushed, a marker in the TAGGED section refuses before the Release is created',
433
+ config: {
434
+ tag_already_remote: true,
435
+ changelog: markedChangelogFixture(current_version()),
436
+ changelog_file: markedChangelogFixture(current_version()),
437
+ },
438
+ expect_success: false,
439
+ verify: (w, err) => {
440
+ const f: string[] = [];
441
+ _expect(
442
+ (err ?? '').includes('refusing to publish the GitHub Release notes'),
443
+ `died for the wrong reason: ${err ?? '(no error)'}`,
444
+ f,
445
+ );
446
+ _expect(!w.release_created, 'the GitHub Release was created after the refusal', f);
447
+ _expect(
448
+ !w.calls.some((c) => c.startsWith(`gh release create ${w.target}`)),
449
+ 'gh release create ran after the refusal',
450
+ f,
451
+ );
452
+ return f;
453
+ },
454
+ },
455
+ 'clean-section-still-publishes': {
456
+ summary:
457
+ 'the guard is scoped to a real defect: a policy-valid section tags and releases as before',
458
+ config: {},
459
+ expect_success: true,
460
+ verify: (w, _err) => {
461
+ const f: string[] = [];
462
+ _expect(w.tag_remote, 'tag never pushed', f);
463
+ _expect(w.release_created, 'GitHub Release never created', f);
464
+ return f;
465
+ },
466
+ },
328
467
  'happy-resume': {
329
468
  summary: 'resume with everything green runs checkout → checks → merge → tag → release',
330
469
  config: {},
@@ -472,6 +611,9 @@ function run_scenario(name: string): ScenarioOutcome {
472
611
  return true;
473
612
  };
474
613
  _set_exec_override((args) => world.exec(args));
614
+ // The working-tree read, faked for the same reason the command layer is.
615
+ // Without this the drill's step 8 reads the repository's real CHANGELOG.md.
616
+ _set_changelog_reader(() => world.changelog_file);
475
617
  try {
476
618
  execute(plan, { wait_for_checks: true, dry_run: false, resume: true });
477
619
  } catch (err) {
@@ -482,6 +624,7 @@ function run_scenario(name: string): ScenarioOutcome {
482
624
  }
483
625
  } finally {
484
626
  _set_exec_override(null);
627
+ _set_changelog_reader(null);
485
628
  process.stdout.write = orig_out;
486
629
  process.stderr.write = orig_err;
487
630
  }
@@ -91,6 +91,39 @@ const PROJECT_TEMPLATE = path.join(
91
91
  'agent-project-settings.example.yml',
92
92
  );
93
93
  const CHANGELOG = path.join(REPO_ROOT, 'CHANGELOG.md');
94
+
95
+ /**
96
+ * Overridable reader for the WORKING-TREE changelog.
97
+ *
98
+ * Mirrors `_set_exec_override` in `release_publication.ts`, and exists for the
99
+ * same reason: `execute()` reads `CHANGELOG.md` off disk at two points (the PR
100
+ * body and the annotated-tag message), and the release drill fakes commands
101
+ * but not the filesystem. Before this seam the drill's step 8 read the
102
+ * repository's REAL changelog — whose current section carries four
103
+ * `_auto-derived, rewrite before merge:_` lines at `14.13.0` — so a
104
+ * publication guard added at that call site refused every drill scenario for a
105
+ * reason that had nothing to do with what the scenario was testing.
106
+ *
107
+ * That is the failure mode the `WorldConfig.changelog` fixture already removed
108
+ * for `git show`, and the AI council of 2026-08-23 (2/2 convergent) chose a
109
+ * controlled fixture over a policy exemption when it removed it. This is the
110
+ * same choice applied to the other read: the drill supplies policy-valid
111
+ * content and exercises the real parsing and guarding path, rather than being
112
+ * exempted from it.
113
+ *
114
+ * `null` restores the real filesystem read. Test-only; nothing in production
115
+ * sets it.
116
+ */
117
+ let _changelog_reader: ((file: string) => string) | null = null;
118
+
119
+ function _set_changelog_reader(fn: ((file: string) => string) | null): void {
120
+ _changelog_reader = fn;
121
+ }
122
+
123
+ /** The working-tree changelog text, through the seam above. */
124
+ function read_changelog_text(): string {
125
+ return _changelog_reader ? _changelog_reader(CHANGELOG) : fs.readFileSync(CHANGELOG, 'utf-8');
126
+ }
94
127
  const MAIN_BRANCH = 'main';
95
128
  const REMOTE = 'origin';
96
129
  const REPO_SLUG = 'event4u-app/agent-config';
@@ -282,6 +315,8 @@ export {
282
315
  _cap_body,
283
316
  CHANGELOG,
284
317
  CalledProcessError,
318
+ _set_changelog_reader,
319
+ read_changelog_text,
285
320
  GH_PR_BODY_LIMIT,
286
321
  GH_RELEASE_NOTES_LIMIT,
287
322
  MAIN_BRANCH,
@@ -16,12 +16,14 @@
16
16
  * import path changes.
17
17
  */
18
18
  import { spawnSync } from 'node:child_process';
19
+ import { publication_blockers } from './_lib/release_highlights.js';
19
20
  import * as fs from 'node:fs';
20
21
  import process from 'node:process';
21
22
 
22
23
  import { gh_argv_label, gh_retry } from './_lib/gh_transient.js';
23
24
  import {
24
25
  extract_changelog_section,
26
+ tag_message_from_section,
25
27
  pr_body_from_section,
26
28
  } from './_lib/release_material.js';
27
29
 
@@ -34,6 +36,7 @@ import {
34
36
  REPO_ROOT,
35
37
  SystemExitError,
36
38
  _cap_body,
39
+ read_changelog_text,
37
40
  } from './release_env.js';
38
41
 
39
42
  function die(msg: string, code = 2): never {
@@ -718,3 +721,84 @@ export {
718
721
  run,
719
722
  watch_pr_checks,
720
723
  };
724
+
725
+ /**
726
+ * Refuse an irreversible publication whose section is not publishable.
727
+ *
728
+ * Called at the THREE call sites that publish, never inside a formatter:
729
+ * annotated-tag creation, the resumed push of a tag created but never pushed,
730
+ * and the GitHub Release body. A pure formatter has no notion of whether it is
731
+ * actually publishing, which is why an earlier attempt at that placement was
732
+ * refused; the call site does, and it is the one that can still stop.
733
+ *
734
+ * **The resumed push is the bypass worth naming.** The changelog is read only
735
+ * in the tag-CREATION branch, so a resume over a tag that was created but never
736
+ * pushed reached `_push_tag` having read nothing. A guard covering creation
737
+ * alone misses exactly that path, and pushing a tag is as irreversible as
738
+ * creating it.
739
+ *
740
+ * `die` throws before the command runs, so nothing irreversible fires after a
741
+ * refusal — an ordering asserted against the drill's recorded command list
742
+ * rather than by reading this comment.
743
+ *
744
+ * Lives here rather than in `release.ts` because that file is 2000+ lines and
745
+ * every line above 1500 is charged by the source-size growth ratchet, while
746
+ * this module is far below it and already owns `die`. Behaviour unchanged.
747
+ */
748
+ export function _refuse_unpublishable(sectionBody: string, version: string, surface: string): void {
749
+ const blockers = publication_blockers(sectionBody, version);
750
+ if (blockers.length === 0) {
751
+ return;
752
+ }
753
+ die(
754
+ `refusing to publish the ${surface} for ${version} — ` +
755
+ `${String(blockers.length)} publication blocker(s):\n` +
756
+ blockers.map((b) => ` - ${b}`).join('\n'),
757
+ );
758
+ }
759
+
760
+ /**
761
+ * Read the section under release and refuse if it is not publishable.
762
+ *
763
+ * One helper for all three publish sites so the read, the missing-section
764
+ * refusal and the blocker refusal cannot drift apart between them — the
765
+ * resumed-push path drifted exactly that way before this existed, reaching
766
+ * `_push_tag` having read nothing.
767
+ */
768
+ export function guard_publication(version: string, surface: string): void {
769
+ const section = extract_changelog_section(read_changelog_text(), version);
770
+ if (!section) {
771
+ die(
772
+ `CHANGELOG.md on ${MAIN_BRANCH} carries no section for ${version} — ` +
773
+ `refusing to publish the ${surface}`,
774
+ );
775
+ return;
776
+ }
777
+ _refuse_unpublishable(section.body, version, surface);
778
+ }
779
+
780
+ /**
781
+ * Derive the tag message from the MERGED changelog, refuse if unpublishable,
782
+ * create the annotated tag and push it.
783
+ *
784
+ * The three steps are one function because they are one irreversible act with
785
+ * one precondition, and splitting them across call sites is what let the
786
+ * resumed-push path reach `_push_tag` having read nothing. Sequencing is
787
+ * load-bearing (release-truth Phase 1, council 2026-08-03): merge FIRST, pull
788
+ * main, THEN derive the message — tagging before the merge reads a section that
789
+ * does not exist yet. The annotated tag replaces the previous lightweight one so
790
+ * tag metadata is a fourth surface carrying the same single-source content.
791
+ */
792
+ export function create_and_push_annotated_tag(version: string): void {
793
+ const merged = extract_changelog_section(read_changelog_text(), version);
794
+ if (!merged) {
795
+ die(
796
+ `CHANGELOG.md on ${MAIN_BRANCH} carries no section for ${version} — ` +
797
+ 'refusing to tag a release whose changelog entry is missing',
798
+ );
799
+ return;
800
+ }
801
+ _refuse_unpublishable(merged.body, version, 'annotated tag');
802
+ run(['git', 'tag', '-a', version, '-m', tag_message_from_section(merged.body, version)]);
803
+ _push_tag(version);
804
+ }