@event4u/agent-config 9.0.0 → 9.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +22 -204
  3. package/MIGRATION.md +14 -0
  4. package/README.md +2 -2
  5. package/dist/agent-src/commands/explain-run.md +88 -0
  6. package/dist/agent-src/commands/implement-ticket.md +2 -0
  7. package/dist/agent-src/commands/pr/create/description-only.md +138 -8
  8. package/dist/agent-src/commands/pr/create.md +16 -5
  9. package/dist/agent-src/commands/review/changes.md +22 -0
  10. package/dist/agent-src/contexts/execution/subagent-modes-detail.md +41 -6
  11. package/dist/agent-src/contexts/execution/verify-budget.md +15 -0
  12. package/dist/agent-src/rules/cross-source-consistency.md +92 -0
  13. package/dist/agent-src/rules/delegation-policy.md +1 -1
  14. package/dist/agent-src/skills/estimate-ticket/SKILL.md +2 -0
  15. package/dist/agent-src/skills/feature-planning/SKILL.md +1 -1
  16. package/dist/agent-src/skills/llm-provider-knowledge/SKILL.md +146 -0
  17. package/dist/agent-src/skills/llm-provider-knowledge/evals/triggers.json +20 -0
  18. package/dist/agent-src/skills/po-discovery/SKILL.md +2 -0
  19. package/dist/agent-src/skills/refine-ticket/SKILL.md +7 -0
  20. package/dist/agent-src/skills/roadmap-writing/SKILL.md +14 -0
  21. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +26 -5
  22. package/dist/agent-src/skills/subagent-orchestration/evals/evals.json +18 -0
  23. package/dist/agent-src/skills/subagent-orchestration/prompts/README.md +14 -2
  24. package/dist/agent-src/skills/subagent-orchestration/prompts/adversarial-verification-council.md +79 -0
  25. package/dist/agent-src/skills/subagent-orchestration/schemas/adversarial-findings.json +56 -0
  26. package/dist/agent-src/skills/token-optimizer/SKILL.md +10 -4
  27. package/dist/agent-src/templates/agent-settings.md +28 -0
  28. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  29. package/dist/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +4 -1
  30. package/dist/discovery/deprecation-report.md +1 -1
  31. package/dist/discovery/discovery-manifest.json +105 -28
  32. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  33. package/dist/discovery/discovery-manifest.summary.md +5 -5
  34. package/dist/discovery/orphan-report.md +1 -1
  35. package/dist/discovery/packs.json +16 -12
  36. package/dist/discovery/trust-report.md +8 -8
  37. package/dist/discovery/workspaces.json +12 -8
  38. package/dist/install/install.mjs +47 -31
  39. package/dist/mcp/registry-manifest.json +3 -3
  40. package/dist/router.json +1 -1
  41. package/dist/scripts/_lib/agent_settings.js +4 -1
  42. package/dist/scripts/_lib/agent_settings.js.map +1 -1
  43. package/dist/server/schemas/settings.js +10 -0
  44. package/dist/server/schemas/settings.js.map +1 -1
  45. package/docs/CLAIMS.md +10 -3
  46. package/docs/MIGRATION.md +20 -0
  47. package/docs/architecture.md +4 -4
  48. package/docs/archive/CHANGELOG-pre-9.2.0.md +276 -0
  49. package/docs/benchmark.md +24 -0
  50. package/docs/command-flows.md +1 -1
  51. package/docs/contracts/agents-layout.md +1 -1
  52. package/docs/contracts/consumer-bridge.md +55 -63
  53. package/docs/contracts/install-layout.md +1 -1
  54. package/docs/contracts/migrate-command.md +1 -1
  55. package/docs/customization.md +22 -1
  56. package/docs/decisions/ADR-020-global-only-consumer-scope.md +34 -0
  57. package/docs/decisions/ADR-122-adversarial-verification-council.md +136 -0
  58. package/docs/decisions/INDEX.md +1 -0
  59. package/docs/design/adversarial-council-eval.md +106 -0
  60. package/docs/featured-skills.md +2 -2
  61. package/docs/getting-started-by-role.md +1 -1
  62. package/docs/getting-started.md +2 -2
  63. package/docs/governance-advantage.md +1 -1
  64. package/docs/guidelines/agent-infra/api-cost-levers.md +126 -0
  65. package/docs/guidelines/agent-infra/cross-source-consistency-mechanics.md +109 -0
  66. package/docs/installation.md +2 -1
  67. package/docs/maintainers/dev-mode.md +3 -2
  68. package/docs/proof.md +7 -7
  69. package/package.json +1 -1
  70. package/src/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +4 -1
  71. package/src/config/agent-settings.template.yml +81 -0
  72. package/src/config/gitignore-block.txt +16 -6
  73. package/src/scripts/_cli/cmd_conformance.ts +2 -2
  74. package/src/scripts/_cli/cmd_doctor.ts +162 -13
  75. package/src/scripts/_cli/cmd_refresh.ts +35 -118
  76. package/src/scripts/_lib/adversarial_council_gate.test.ts +112 -0
  77. package/src/scripts/_lib/adversarial_council_gate.ts +98 -0
  78. package/src/scripts/_lib/adversarial_reconcile.test.ts +197 -0
  79. package/src/scripts/_lib/adversarial_reconcile.ts +187 -0
  80. package/src/scripts/_lib/agent_settings.ts +4 -1
  81. package/src/scripts/_lib/verify_budget.ts +4 -1
  82. package/src/scripts/ai_council/clients.ts +68 -6
  83. package/src/scripts/ai_council/pricing.ts +62 -0
  84. package/src/scripts/council_cli.ts +19 -3
  85. package/src/scripts/install.ts +44 -45
  86. package/src/scripts/lint_agents_layout.ts +10 -4
  87. package/src/scripts/lint_breaking_changes_index.ts +303 -0
  88. package/src/scripts/mcp_server/tools.ts +1 -3
  89. package/src/scripts/schemas/agent-settings.schema.json +28 -0
  90. package/src/scripts/self_review_gate.test.ts +11 -0
  91. package/src/scripts/self_review_gate.ts +7 -1
  92. package/src/scripts/skill_eval_coverage.ts +4 -1
  93. package/src/server/schemas/settings.ts +24 -0
@@ -138,6 +138,12 @@ export class CouncilResponse {
138
138
  text: string;
139
139
  input_tokens: number;
140
140
  output_tokens: number;
141
+ // Prompt-cache accounting (Anthropic). `cache_read_input_tokens` bill at
142
+ // ~0.1× input; `cache_creation_input_tokens` at 1.25×/2×. Both default 0
143
+ // (no cache, or a provider that does not report them) so existing callers
144
+ // and the persisted response JSON stay backward-compatible.
145
+ cache_creation_input_tokens: number;
146
+ cache_read_input_tokens: number;
141
147
  latency_ms: number;
142
148
  error: string | null;
143
149
  metadata: Record<string, unknown>;
@@ -148,6 +154,8 @@ export class CouncilResponse {
148
154
  text: string;
149
155
  input_tokens?: number;
150
156
  output_tokens?: number;
157
+ cache_creation_input_tokens?: number;
158
+ cache_read_input_tokens?: number;
151
159
  latency_ms?: number;
152
160
  error?: string | null;
153
161
  metadata?: Record<string, unknown>;
@@ -157,6 +165,8 @@ export class CouncilResponse {
157
165
  this.text = opts.text;
158
166
  this.input_tokens = opts.input_tokens ?? 0;
159
167
  this.output_tokens = opts.output_tokens ?? 0;
168
+ this.cache_creation_input_tokens = opts.cache_creation_input_tokens ?? 0;
169
+ this.cache_read_input_tokens = opts.cache_read_input_tokens ?? 0;
160
170
  this.latency_ms = opts.latency_ms ?? 0;
161
171
  this.error = opts.error ?? null;
162
172
  // Python dataclass uses `field(default_factory=dict)` — each instance
@@ -278,6 +288,15 @@ interface ApiClientOptions {
278
288
  model?: string | undefined;
279
289
  client?: unknown;
280
290
  api_key?: string | null | undefined;
291
+ // Anthropic prompt caching (GA — no beta header). **Explicit opt-in,
292
+ // default OFF** — a caller must pass `true` to send the stable `system` +
293
+ // artefact prefix as cache-controlled blocks (member 2..N in a round, and
294
+ // round-2+ within the 5-min TTL, then read it at ~0.1× input). The council
295
+ // builder opts in via `_construct_api_member`; any other `AnthropicClient`
296
+ // caller (e.g. an API-mode team, a single-shot feature) gets NO caching
297
+ // unless it asks — so a single call never pays the ~1.25× write premium by
298
+ // surprise. Only AnthropicClient consumes this; other providers ignore it.
299
+ enable_prompt_cache?: boolean | undefined;
281
300
  }
282
301
 
283
302
  /** Shared ctor-options shape for the CLI clients. */
@@ -335,10 +354,15 @@ export class AnthropicClient extends ExternalAIClient {
335
354
  override name = 'anthropic';
336
355
  override billable = true;
337
356
  private _client: unknown;
357
+ // Prompt caching is explicit opt-in (default OFF) — see
358
+ // ApiClientOptions.enable_prompt_cache. Prevents silent activation (and the
359
+ // write premium) for callers that don't benefit from a shared-prefix cache.
360
+ private _enablePromptCache: boolean;
338
361
 
339
362
  constructor(opts: ApiClientOptions = {}) {
340
363
  super();
341
364
  this.model = opts.model ?? DEFAULT_ANTHROPIC_MODEL;
365
+ this._enablePromptCache = opts.enable_prompt_cache ?? false;
342
366
  if (opts.client !== undefined && opts.client !== null) {
343
367
  this._client = opts.client;
344
368
  return;
@@ -381,12 +405,44 @@ export class AnthropicClient extends ExternalAIClient {
381
405
  if (typeof create !== 'function') {
382
406
  throw new TypeError("'messages.create' is not callable");
383
407
  }
384
- response = create.call(messages, {
385
- model: this.model,
386
- max_tokens,
387
- system: system_prompt,
388
- messages: [{ role: 'user', content: user_prompt }],
389
- });
408
+ // Prompt caching (GA — no beta header). Breakpoints on BOTH the
409
+ // system block (stable across rounds) and the user artefact block
410
+ // (the large system+artefact prefix is byte-identical across
411
+ // members 2..N within a round, so they read it at ~0.1× input).
412
+ // base_system_prompt alone is often below the model's min cacheable
413
+ // prefix and would never cache solo — the artefact block is where
414
+ // the real saving lands. Two breakpoints (≤ the 4 allowed).
415
+ const kwargs: Record<string, unknown> = this._enablePromptCache
416
+ ? {
417
+ model: this.model,
418
+ max_tokens,
419
+ system: [
420
+ {
421
+ type: 'text',
422
+ text: system_prompt,
423
+ cache_control: { type: 'ephemeral' },
424
+ },
425
+ ],
426
+ messages: [
427
+ {
428
+ role: 'user',
429
+ content: [
430
+ {
431
+ type: 'text',
432
+ text: user_prompt,
433
+ cache_control: { type: 'ephemeral' },
434
+ },
435
+ ],
436
+ },
437
+ ],
438
+ }
439
+ : {
440
+ model: this.model,
441
+ max_tokens,
442
+ system: system_prompt,
443
+ messages: [{ role: 'user', content: user_prompt }],
444
+ };
445
+ response = create.call(messages, kwargs);
390
446
  } catch (exc) {
391
447
  return new CouncilResponse({
392
448
  provider: this.name,
@@ -426,6 +482,12 @@ export class AnthropicClient extends ExternalAIClient {
426
482
  text,
427
483
  input_tokens: usage ? (_getattr(usage, 'input_tokens', 0) as number) : 0,
428
484
  output_tokens: usage ? (_getattr(usage, 'output_tokens', 0) as number) : 0,
485
+ cache_creation_input_tokens: usage
486
+ ? (_getattr(usage, 'cache_creation_input_tokens', 0) as number)
487
+ : 0,
488
+ cache_read_input_tokens: usage
489
+ ? (_getattr(usage, 'cache_read_input_tokens', 0) as number)
490
+ : 0,
429
491
  latency_ms,
430
492
  });
431
493
  }
@@ -101,6 +101,68 @@ export function estimate_cost(
101
101
  };
102
102
  }
103
103
 
104
+ // ── prompt-cache repricing ─────────────────────────────────────────
105
+ //
106
+ // Prompt-cache multipliers derived from the base input rate (Anthropic, GA —
107
+ // no beta header): cache reads bill at ~0.1× input; cache writes at 1.25×
108
+ // (5-min TTL) / 2× (1-h TTL). Kept as CONSTANTS, not columns in
109
+ // `.agent-prices.md`, because that table's row format is byte-frozen (see the
110
+ // file header) and downstream tests pin it.
111
+ export const CACHE_READ_MULTIPLIER = 0.1;
112
+ export const CACHE_WRITE_MULTIPLIER_5M = 1.25;
113
+ export const CACHE_WRITE_MULTIPLIER_1H = 2.0;
114
+
115
+ export interface CacheTokenBreakdown {
116
+ input_tokens: number; // uncached input, billed at the full rate
117
+ cache_read_input_tokens?: number; // billed at CACHE_READ_MULTIPLIER × input
118
+ cache_creation_input_tokens?: number; // billed at the write multiplier
119
+ output_tokens: number;
120
+ }
121
+
122
+ /**
123
+ * Realized cost of a call given OBSERVED cache-token counts (from
124
+ * `CouncilResponse.usage`). Deliberately separate from `estimate_cost`, which
125
+ * stays cache-agnostic (a conservative 0%-cache-hit pre-flight) so the budget
126
+ * gate never under-predicts. This function is the post-hoc actual that surfaces
127
+ * the saving. `ttl` selects the write multiplier (default 5-min).
128
+ */
129
+ export function reprice_with_cache(
130
+ provider: string,
131
+ model: string,
132
+ tokens: CacheTokenBreakdown,
133
+ table: PriceTable,
134
+ ttl: '5m' | '1h' = '5m',
135
+ ): CostEstimate {
136
+ const price = lookup(table, provider, model);
137
+ const read = tokens.cache_read_input_tokens ?? 0;
138
+ const write = tokens.cache_creation_input_tokens ?? 0;
139
+ const totalInput = tokens.input_tokens + read + write;
140
+ if (price === null) {
141
+ return {
142
+ provider,
143
+ model,
144
+ input_tokens: totalInput,
145
+ output_tokens: tokens.output_tokens,
146
+ input_usd: 0.0,
147
+ output_usd: 0.0,
148
+ };
149
+ }
150
+ const writeMult = ttl === '1h' ? CACHE_WRITE_MULTIPLIER_1H : CACHE_WRITE_MULTIPLIER_5M;
151
+ const inputUsd =
152
+ (tokens.input_tokens / 1_000_000) * price.input_per_1m_usd +
153
+ (read / 1_000_000) * price.input_per_1m_usd * CACHE_READ_MULTIPLIER +
154
+ (write / 1_000_000) * price.input_per_1m_usd * writeMult;
155
+ const outputUsd = (tokens.output_tokens / 1_000_000) * price.output_per_1m_usd;
156
+ return {
157
+ provider,
158
+ model,
159
+ input_tokens: totalInput,
160
+ output_tokens: tokens.output_tokens,
161
+ input_usd: inputUsd,
162
+ output_usd: outputUsd,
163
+ };
164
+ }
165
+
104
166
  // ── staleness ──────────────────────────────────────────────────────
105
167
 
106
168
  /** Return the most recent Monday 00:00 UTC as a date (YYYY-MM-DD). */
@@ -603,8 +603,13 @@ function build_members(settings: Dict, opts: BuildMembersOptions = {}): External
603
603
  );
604
604
  }
605
605
  const api_key_ref = (cfg['api_key_ref'] as string | null) ?? null;
606
+ // Council opts in explicitly (caching is client-default OFF): on
607
+ // unless the operator sets `prompt_cache: false` on the member.
608
+ const enable_prompt_cache = cfg['prompt_cache'] !== false;
606
609
  for (const sib_model of siblings[name] as string[]) {
607
- members.push(_construct_api_member(name, sib_model, { api_key_ref }));
610
+ members.push(
611
+ _construct_api_member(name, sib_model, { api_key_ref, enable_prompt_cache }),
612
+ );
608
613
  }
609
614
  continue;
610
615
  }
@@ -613,6 +618,9 @@ function build_members(settings: Dict, opts: BuildMembersOptions = {}): External
613
618
  members.push(
614
619
  _construct_api_member(name, model, {
615
620
  api_key_ref: (cfg['api_key_ref'] as string | null) ?? null,
621
+ // Council opts in explicitly (client-default OFF); on unless
622
+ // the operator sets `prompt_cache: false`.
623
+ enable_prompt_cache: cfg['prompt_cache'] !== false,
616
624
  }),
617
625
  );
618
626
  } else if (mode === 'cli' && _CLI_PROVIDERS.has(name)) {
@@ -747,14 +755,18 @@ function _format_advisor_summary(
747
755
  function _construct_api_member(
748
756
  name: string,
749
757
  model: string | null,
750
- opts: { api_key_ref?: string | null } = {},
758
+ opts: { api_key_ref?: string | null; enable_prompt_cache?: boolean | undefined } = {},
751
759
  ): ExternalAIClient {
752
760
  const api_key_ref = opts.api_key_ref ?? null;
753
761
  if (name === 'anthropic') {
754
762
  const api_key = api_key_ref
755
763
  ? resolve_api_key(api_key_ref, 'ai_council.members.anthropic')
756
764
  : load_anthropic_key();
757
- return new AnthropicClient({ model: model || 'claude-sonnet-4-5', api_key });
765
+ return new AnthropicClient({
766
+ model: model || 'claude-sonnet-4-5',
767
+ api_key,
768
+ enable_prompt_cache: opts.enable_prompt_cache,
769
+ });
758
770
  }
759
771
  if (name === 'openai') {
760
772
  const api_key = api_key_ref
@@ -1409,6 +1421,8 @@ function _serialise_responses(responses: CouncilResponse[]): Dict[] {
1409
1421
  text: r.text,
1410
1422
  input_tokens: r.input_tokens,
1411
1423
  output_tokens: r.output_tokens,
1424
+ cache_creation_input_tokens: r.cache_creation_input_tokens,
1425
+ cache_read_input_tokens: r.cache_read_input_tokens,
1412
1426
  latency_ms: r.latency_ms,
1413
1427
  error: r.error,
1414
1428
  metadata,
@@ -1427,6 +1441,8 @@ function _deserialise_responses(items: Dict[]): CouncilResponse[] {
1427
1441
  text: (d['text'] as string) ?? '',
1428
1442
  input_tokens: _pyInt(d['input_tokens'] ?? 0, 0),
1429
1443
  output_tokens: _pyInt(d['output_tokens'] ?? 0, 0),
1444
+ cache_creation_input_tokens: _pyInt(d['cache_creation_input_tokens'] ?? 0, 0),
1445
+ cache_read_input_tokens: _pyInt(d['cache_read_input_tokens'] ?? 0, 0),
1430
1446
  latency_ms: _pyInt(d['latency_ms'] ?? 0, 0),
1431
1447
  error: (d['error'] as string | null) ?? null,
1432
1448
  metadata: (d['metadata'] as Dict) || {},
@@ -2494,7 +2494,11 @@ function _detect_legacy_for_migration(project_root: string): string[] {
2494
2494
  return [];
2495
2495
  }
2496
2496
 
2497
- if (isFile(path.join(project_root, CONSUMER_BRIDGE_MARKER_RELPATH))) return [];
2497
+ // Migration-idempotency sentinel. Previously the consumer bridge marker;
2498
+ // that file was retired (ADR-020 amendment 2026-07-13), so the durable
2499
+ // "this project has already been installed / migrated" signal is now the
2500
+ // install-mode marker, written on every full or minimal install.
2501
+ if (isFile(path.join(project_root, INSTALL_MODE_MARKER_REL))) return [];
2498
2502
 
2499
2503
  const found: string[] = [];
2500
2504
  for (const name of MIGRATE_LEGACY_YAML_FILES) {
@@ -2573,38 +2577,34 @@ function _format_global_root_for_marker(global_root: string): string {
2573
2577
  }
2574
2578
 
2575
2579
  /**
2576
- * Write `agents/.event4u-bridge.yml`. Skipped under `AGENT_CONFIG_DEV_MODE=1`
2577
- * and when the project root is the agent-config source repo
2578
- * (`.agent-src.uncondensed/` present) — same rationale. Atomic write.
2580
+ * Remove a legacy `agents/.event4u-bridge.yml` if a previous install wrote
2581
+ * one. The bridge marker was retired (ADR-020 dated amendment 2026-07-13):
2582
+ * it carried no project-specific data — `global_root` is derived directly
2583
+ * from the well-known `~/.event4u/agent-config` path — yet was committed to
2584
+ * the shared project tree, so every developer's install rewrote its volatile
2585
+ * `installed_at` / `installer_version` fields and overwrote each other's.
2586
+ * The global root is now resolved from the well-known path; the migration
2587
+ * idempotency sentinel moved to `agents/.agent-state/install-mode.txt`.
2588
+ *
2589
+ * Skipped under `AGENT_CONFIG_DEV_MODE=1` and inside the agent-config source
2590
+ * repo (detected by the uncondensed-source marker dir) — the same gate the
2591
+ * writer used. Returns the removed path (so the caller can report it) or null.
2579
2592
  */
2580
- function _write_consumer_bridge_marker(
2593
+ function _remove_legacy_consumer_bridge_marker(
2581
2594
  project_root: string,
2582
- installer_version: string,
2583
2595
  env: NodeJS.ProcessEnv | null = null,
2584
- now: Date | null = null,
2585
2596
  ): string | null {
2586
2597
  const env_map = env ?? process.env;
2587
2598
  if (env_map['AGENT_CONFIG_DEV_MODE'] === '1') return null;
2588
2599
  if (isDir(path.join(project_root, '.agent-src.uncondensed'))) return null;
2589
2600
 
2590
- const global_root_str = _format_global_root_for_marker(
2591
- user_global_paths.event4u_root(env_map),
2592
- );
2593
- const stamp = utcStamp(now ?? undefined);
2594
-
2595
- const body =
2596
- '# event4u/agent-config — consumer bridge marker (auto-written).\n' +
2597
- '# Spec: docs/contracts/consumer-bridge.md (event4u-bridge/v1).\n' +
2598
- '# Reader contract: expand ~ against the current $HOME; fail closed\n' +
2599
- '# when global_root is missing on disk; never write back through it.\n' +
2600
- 'schema: event4u-bridge/v1\n' +
2601
- `global_root: ${global_root_str}\n` +
2602
- `installed_at: ${stamp}\n` +
2603
- `installer_version: ${installer_version}\n`;
2604
-
2605
2601
  const target = path.join(project_root, CONSUMER_BRIDGE_MARKER_RELPATH);
2606
- mkdirp(path.dirname(target));
2607
- atomicWrite0644(target, body, '.event4u-bridge.');
2602
+ if (!isFile(target)) return null;
2603
+ try {
2604
+ fs.rmSync(target);
2605
+ } catch {
2606
+ return null;
2607
+ }
2608
2608
  return target;
2609
2609
  }
2610
2610
 
@@ -2641,16 +2641,14 @@ function _write_per_tool_project_anchors(
2641
2641
  const target = path.join(project_root, rel_path);
2642
2642
  mkdirp(path.dirname(target));
2643
2643
 
2644
- const bridge_abs = path.join(project_root, CONSUMER_BRIDGE_MARKER_RELPATH);
2645
- const bridge_rel = path.relative(path.dirname(target), bridge_abs);
2646
-
2647
2644
  const body =
2648
2645
  '# event4u/agent-config — per-tool project anchor (auto-written).\n' +
2649
2646
  '# Spec: docs/contracts/consumer-bridge.md § Per-tool anchor strategy.\n' +
2650
- `# Tool: ${tool_id}. Bridge marker: agents/.event4u-bridge.yml.\n` +
2647
+ `# Tool: ${tool_id}. Resolves the global install directly — no\n` +
2648
+ '# project-local bridge marker (retired, ADR-020 amendment 2026-07-13).\n' +
2649
+ '# This anchor is gitignored: each developer regenerates it on install.\n' +
2651
2650
  'schema: event4u-bridge/v1\n' +
2652
2651
  `tool: ${tool_id}\n` +
2653
- `bridge: ${bridge_rel}\n` +
2654
2652
  `global_root: ${global_root_str}\n` +
2655
2653
  `installed_at: ${stamp}\n`;
2656
2654
 
@@ -3377,15 +3375,15 @@ function install_global(
3377
3375
  const rc = _update_installed_tools_manifest(project_root, tools, 'global', force, files_by_tool);
3378
3376
  if (rc !== 0) return rc;
3379
3377
 
3380
- // Consumer bridge marker (Phase 4.2). The surrounding
3381
- // `.agent-src.uncondensed` guard already covers the source-repo case;
3382
- // the dev-mode skip is enforced inside the writer.
3383
- const marker_path = _write_consumer_bridge_marker(project_root, installed_version);
3384
- if (marker_path !== null && !state.QUIET) {
3385
- const rel = isRelativeTo(marker_path, project_root)
3386
- ? path.relative(project_root, marker_path)
3387
- : marker_path;
3388
- info(`Bridge marker written: ${rel}`);
3378
+ // Consumer bridge marker retired (ADR-020 amendment 2026-07-13):
3379
+ // clean up any legacy `agents/.event4u-bridge.yml` a prior install
3380
+ // committed. The global root is resolved from the well-known path.
3381
+ const removed_marker = _remove_legacy_consumer_bridge_marker(project_root);
3382
+ if (removed_marker !== null && !state.QUIET) {
3383
+ const rel = isRelativeTo(removed_marker, project_root)
3384
+ ? path.relative(project_root, removed_marker)
3385
+ : removed_marker;
3386
+ info(`Removed legacy bridge marker: ${rel}`);
3389
3387
  }
3390
3388
 
3391
3389
  const anchor_paths = _write_per_tool_project_anchors(project_root, tools);
@@ -3800,13 +3798,14 @@ function install_minimal(target_root_in: string, force: boolean, user_type: stri
3800
3798
  }
3801
3799
  }
3802
3800
 
3803
- const installed_version = installed_lock.current_package_version();
3804
- const marker_path = _write_consumer_bridge_marker(target_root, installed_version);
3805
- if (marker_path !== null) {
3806
- const rel = isRelativeTo(marker_path, target_root)
3807
- ? path.relative(target_root, marker_path)
3808
- : marker_path;
3809
- success(`Wrote ${rel}`);
3801
+ // Bridge marker retired (ADR-020 amendment 2026-07-13) — clean up any
3802
+ // legacy `agents/.event4u-bridge.yml` a prior install left behind.
3803
+ const removed_marker = _remove_legacy_consumer_bridge_marker(target_root);
3804
+ if (removed_marker !== null) {
3805
+ const rel = isRelativeTo(removed_marker, target_root)
3806
+ ? path.relative(target_root, removed_marker)
3807
+ : removed_marker;
3808
+ success(`Removed legacy bridge marker: ${rel}`);
3810
3809
  }
3811
3810
 
3812
3811
  _write_install_mode_marker(target_root, 'minimal');
@@ -42,7 +42,11 @@ const ALLOWED_FLAT_FILES: ReadonlySet<string> = new Set([
42
42
  // separate violation caught by check_tracked_but_ignored.ts.
43
43
  '.ai-video.xml', // operator AI-video config; gitignored per .gitignore § AI Video
44
44
  'installed-tools.lock', // installer per-user inventory; gitignored per ADR-020
45
- '.event4u-bridge.yml', // consumer bridge marker; ADR-020 (should only be in consumers)
45
+ // Retired marker (ADR-020 amendment 2026-07-13). No longer written; the
46
+ // global root resolves from `~/.event4u/agent-config`. Kept tolerated so a
47
+ // legacy leftover in a not-yet-reinstalled consumer is not flagged as a
48
+ // violation — the next install deletes it.
49
+ '.event4u-bridge.yml',
46
50
  ]);
47
51
 
48
52
  // All directories allowed at the agents/ root in the SOURCE REPO.
@@ -75,6 +79,8 @@ const ALLOWED_SOURCE_DIRS: ReadonlySet<string> = new Set([
75
79
  const CONSUMER_EXPECTED_ENTRIES: ReadonlySet<string> = new Set([
76
80
  // Required consumer entries
77
81
  'overrides',
82
+ // Retired marker (ADR-020 amendment 2026-07-13) — no longer written, but
83
+ // kept in the tolerated set so a legacy leftover does not warn.
78
84
  '.event4u-bridge.yml',
79
85
  '.gitkeep',
80
86
  // Optional but legitimate consumer entries (will not trigger warnings)
@@ -93,9 +99,9 @@ const CONSUMER_EXPECTED_ENTRIES: ReadonlySet<string> = new Set([
93
99
  const MIGRATE_HINT =
94
100
  'Run `npx @event4u/agent-config migrate` to sweep legacy project-scope ' +
95
101
  'artefacts in one pass. The unified `migrate` command (see ' +
96
- '`docs/contracts/migrate-command.md`) leaves `agents/overrides/` + ' +
97
- '`agents/.event4u-bridge.yml` as the only consumer-side files; the ' +
98
- 'wizard recreates fresh config on `agent-config setup`.';
102
+ '`docs/contracts/migrate-command.md`) leaves `agents/overrides/` as the ' +
103
+ 'only consumer-side agent surface; the wizard recreates fresh config on ' +
104
+ '`agent-config setup`.';
99
105
 
100
106
  function _isDir(p: string): boolean {
101
107
  try {