@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
@@ -518,6 +518,34 @@ design:
518
518
  # autonomy setting, roadmap, or standing instruction lifts it).
519
519
  fidelity_mode: strict
520
520
 
521
+ # --- Cross-source consistency ---
522
+ #
523
+ # When the agent plans, refines, roadmaps, or implements from multiple
524
+ # sources — ticket text, an attached image / mockup / screenshot, the spec,
525
+ # the codebase — it actively checks them AGAINST each other and STOPS to ask
526
+ # before proceeding on a detected discrepancy, instead of silently guessing
527
+ # (even a correct guess). Consumed by the `cross-source-consistency` rule.
528
+ # Catches: (a) ticket text vs an attached image/mockup, (b) a spec silent on
529
+ # a clearly-needed behavior (weekend/holiday shifting, empty/error states),
530
+ # (c) spec vs the codebase, (d) intra-ticket contradiction (AC vs description).
531
+ #
532
+ # The discipline is deliberately subordinate to `ask-when-uncertain` (it folds
533
+ # a detected discrepancy into the same turn's single question — never a second
534
+ # prompt) and defers to `scope-control` for the permission to expand scope.
535
+ consistency:
536
+ # on | auto | off (default: on)
537
+ #
538
+ # on = default. Detect discrepancies across sources and surface ONE
539
+ # numbered-options question before proceeding. A silent guess on a
540
+ # real contradiction is a rule violation, even when the guess is
541
+ # correct. An inferred-but-unspecified behavior is a scope expansion:
542
+ # ask before implementing it.
543
+ # auto = detect + surface only HIGH-confidence contradictions; low-confidence
544
+ # ones are stated as an assumption and proceed (noise control).
545
+ # off = no cross-source checking (legacy: the agent may proceed on its own
546
+ # reading of whichever source it happened to read).
547
+ cross_source: "on"
548
+
521
549
  code_style:
522
550
  # minimal | full (default: minimal)
523
551
  #
@@ -604,6 +632,20 @@ subagents:
604
632
  # Integer, default 3. Set to 1 to serialize. Hard cap enforced by runtime.
605
633
  max_parallel: 3
606
634
 
635
+ # Opt-in adversarial-verification-council (subagent-orchestration Mode 9,
636
+ # ADR-122): off | ask | on
637
+ #
638
+ # off (default) = never runs. ask = offer it on an explicit high-risk change.
639
+ # on = auto-run on high-risk changes (security, tenant, migration, public API).
640
+ #
641
+ # A panel of distinct-model skeptics red-teams a real, already-verified change
642
+ # for defect FINDING coverage (not a go/no-go decision) and returns an advisory
643
+ # findings-by-severity verdict. ADVISORY ONLY — it NEVER auto-gates the change
644
+ # (Hard Floor). The registered claim + high-risk tier use cross-vendor skeptics.
645
+ # Stays default-off until the adversarial-council-finding-coverage claim is
646
+ # backed by the two-stage residual-detection benchmark (prove-or-drop).
647
+ adversarial_council: off
648
+
607
649
  # --- Git worktrees ---
608
650
  #
609
651
  # Controls how the `using-git-worktrees` skill and `subagent-orchestration`
@@ -761,6 +803,45 @@ commands:
761
803
  create_pr:
762
804
  preview_description: false
763
805
 
806
+ # Verbosity tier for the generated PR description body.
807
+ # min (default) = title + 2-3 sentence what/why/impact + linked ticket.
808
+ # Token-frugal. Critical info (breaking changes,
809
+ # migrations, security, rollback) is ALWAYS included
810
+ # regardless of tier — the tier governs explanatory
811
+ # depth, never whether a critical callout appears.
812
+ # med = min + grouped logical changes + tests note.
813
+ # max = med + how-to-test + edge cases / trade-offs +
814
+ # reviewer guidance (the richest, ~legacy output).
815
+ detail_level: min
816
+
817
+ # JSON request/response examples for API-endpoint changes.
818
+ # true (default) = include a fenced example ONLY when it can be
819
+ # grounded in a real source (response DTO/resource,
820
+ # OpenAPI/schema, test fixture, or an actual probe).
821
+ # No grounded source → a one-line pointer, never an
822
+ # invented example (a hallucinated contract misleads
823
+ # reviewers, so it is worse than none).
824
+ # false = never add API examples.
825
+ api_examples: true
826
+
827
+ # Screenshots for frontend changes. Default false (off). Capability-gated:
828
+ # requires the host to have browser/preview tooling (Playwright / a
829
+ # dev-server URL). When true + a frontend change + no such tooling, the
830
+ # agent emits a one-line note and leaves the template placeholder — it
831
+ # never fails or blocks PR creation, and never claims an embed that did
832
+ # not happen. Before/after + changed-region highlighting is best-effort.
833
+ # false (default) = never attempt screenshots.
834
+ # true = attempt when capable (see command contract).
835
+ screenshots: false
836
+
837
+ # Optional glob lists that make frontend / API-endpoint detection
838
+ # explicit instead of heuristic. Empty (default) = the command uses a
839
+ # light path/extension heuristic and fails open (no false enrichment
840
+ # when the surface is ambiguous). Populate to pin your project's paths,
841
+ # e.g. ui_paths: ["resources/views/**", "src/pages/**"].
842
+ ui_paths: []
843
+ api_paths: []
844
+
764
845
  # --- Memory ---
765
846
  #
766
847
  # Engineering memory consolidation behaviour. Memory is entirely
@@ -1,12 +1,9 @@
1
1
  # Agent config — tool-scope leftovers (ADR-020 § global-only consumer
2
2
  # install). After the Phase-5 migration the entire `.augment/`, `.claude/`,
3
3
  # `.cursor/` trees live under `~/.event4u/agent-config/`; consumers carry
4
- # only `agents/overrides/` + `agents/.event4u-bridge.yml`. The per-tool
5
- # bridge pointer files for Windsurf / Cline / Gemini-CLI live under
6
- # `.windsurf/`, `.clinerules/`, `.gemini/` and ARE committed — those
7
- # directories stay tracked (not listed here). Pre-Phase-5 entries kept
8
- # as a defence-in-depth ignore list for legacy installs that have not
9
- # migrated yet — Phase 5 unstages them on first migrate run.
4
+ # only `agents/overrides/`. Pre-Phase-5 entries kept as a defence-in-depth
5
+ # ignore list for legacy installs that have not migrated yet — Phase 5
6
+ # unstages them on first migrate run.
10
7
  .augment/skills/
11
8
  .augment/commands/
12
9
  .augment/guidelines/
@@ -21,6 +18,19 @@
21
18
  .claude/CLAUDE.md
22
19
  .cursor/rules/
23
20
 
21
+ # Agent config — consumer bridge marker + per-tool project anchors.
22
+ # The bridge marker was retired (ADR-020 amendment 2026-07-13): it carried
23
+ # no project-specific data (global_root resolves from `~/.event4u/agent-config`)
24
+ # yet was committed and rewritten by every developer's install. The global
25
+ # root is now the well-known path. The Windsurf / Cline / Gemini-CLI anchors
26
+ # remain (those tools cannot read user scope) but are gitignored — each
27
+ # developer regenerates them on install; they are never shared. The install
28
+ # deletes any legacy committed marker; this entry keeps a stale one ignored.
29
+ /agents/.event4u-bridge.yml
30
+ .windsurf/agent-config.bridge.yml
31
+ .clinerules/agent-config.bridge.yml
32
+ .gemini/agent-config.bridge.yml
33
+
24
34
  # Agent config — Phase 5 migration snapshot (ADR-020). Written by
25
35
  # `agent-config migrate-to-global` as a copy-then-move safety net so a
26
36
  # migration can be rolled back. Local-only — never commit.
@@ -47,7 +47,7 @@ import {
47
47
  _run_checks_no_manifest,
48
48
  _scan_foreign,
49
49
  ArgparseExit,
50
- BRIDGE_MARKER_RELATIVE,
50
+ _is_global_only_consumer,
51
51
  STATUS_SYMBOLS,
52
52
  } from './cmd_doctor.js';
53
53
 
@@ -630,7 +630,7 @@ function main(argv: string[] | null = null): number {
630
630
  let doctorChecks: Dict[];
631
631
  let drift: Record<string, Dict[]> = { missing: [], modified: [], foreign: [], tag_drift: [] };
632
632
  if (manifest === null) {
633
- const bridge_present = fs.existsSync(path.join(project_root, BRIDGE_MARKER_RELATIVE));
633
+ const bridge_present = _is_global_only_consumer(project_root);
634
634
  doctorChecks = _run_checks_no_manifest(project_root, bridge_present, null);
635
635
  } else {
636
636
  const [records, known] = _collect_manifest_entries(project_root, manifest);
@@ -100,6 +100,12 @@ import {
100
100
  import * as ai_council_clients from '../ai_council/clients.js';
101
101
  import * as ai_council_config from '../ai_council/config.js';
102
102
  import { review_gate_doctor_signal } from '../ai_team/review_gate.js';
103
+ import {
104
+ LEGACY_ALL,
105
+ excludedRuleBasenames,
106
+ ruleScopeFromSettings,
107
+ type RuleScope,
108
+ } from '../../install/rule_scope.js';
103
109
 
104
110
  type Dict = Record<string, unknown>;
105
111
 
@@ -738,6 +744,7 @@ const CHECK_IDS = [
738
744
  'surface-state',
739
745
  'hook-wiring',
740
746
  'stale-orphans',
747
+ 'rule-scope-drift',
741
748
  'manifest-integrity',
742
749
  'lockfile-freshness',
743
750
  'bridge-drift',
@@ -764,6 +771,7 @@ const GLOBAL_CHECK_IDS: ReadonlySet<string> = new Set([
764
771
  'surface-state',
765
772
  'hook-wiring',
766
773
  'stale-orphans',
774
+ 'rule-scope-drift',
767
775
  'mcp-mode',
768
776
  'mcp-beta-readiness',
769
777
  'offline-readiness',
@@ -783,8 +791,20 @@ const MANIFEST_REQUIRED_CHECK_IDS: ReadonlySet<string> = new Set([
783
791
  'unsupported-combos',
784
792
  ]);
785
793
 
786
- /** Project-root-relative path of the ADR-020 global-only consumer marker. */
787
- const BRIDGE_MARKER_RELATIVE = 'agents/.event4u-bridge.yml';
794
+ /**
795
+ * Signals a set-up global-only consumer. The `.event4u-bridge.yml` marker was
796
+ * retired (ADR-020 amendment 2026-07-13); the durable project-side signal is
797
+ * the install-mode marker (written on every install) or the `agents/overrides/`
798
+ * scaffold. Either is sufficient.
799
+ */
800
+ const INSTALL_MODE_MARKER_RELATIVE = 'agents/.agent-state/install-mode.txt';
801
+
802
+ function _is_global_only_consumer(project_root: string): boolean {
803
+ return (
804
+ isFile(path.join(project_root, INSTALL_MODE_MARKER_RELATIVE)) ||
805
+ isDir(path.join(project_root, 'agents', 'overrides'))
806
+ );
807
+ }
788
808
 
789
809
  /** Repair targets that `--repair <id>` accepts. */
790
810
  const REPAIR_IDS = ['wizard-state'] as const;
@@ -1091,15 +1111,16 @@ function _check_global_binary(project_root: string): Dict {
1091
1111
  remedy: 'agent-config upgrade (refresh the global install + plugin)',
1092
1112
  };
1093
1113
  }
1094
- const bridge = path.join(project_root, 'agents', '.event4u-bridge.yml');
1095
- const bridge_note = isFile(bridge) ? '' : ' · no project bridge marker';
1114
+ const consumer_note = _is_global_only_consumer(project_root)
1115
+ ? ''
1116
+ : ' · no project install marker';
1096
1117
  return {
1097
1118
  id: 'global-binary',
1098
1119
  status: 'ok',
1099
- message: `on PATH (${binary}); version ${binary_v || 'unknown'}${bridge_note}`,
1100
- remedy: !bridge_note
1120
+ message: `on PATH (${binary}); version ${binary_v || 'unknown'}${consumer_note}`,
1121
+ remedy: !consumer_note
1101
1122
  ? ''
1102
- : 'agent-config refresh --project (scaffold the bridge marker)',
1123
+ : 'agent-config refresh --project (scaffold agents/overrides/)',
1103
1124
  };
1104
1125
  }
1105
1126
 
@@ -1590,6 +1611,130 @@ function _check_stale_orphans(): Dict {
1590
1611
  };
1591
1612
  }
1592
1613
 
1614
+ /**
1615
+ * Per-file rule projection trees a consumer install writes (basenames match
1616
+ * the shipped `dist/agent-src/rules` tree). The first one that exists and
1617
+ * holds ≥ 1 `.md` rule is the surface we diff against the consumer's scope.
1618
+ */
1619
+ const _RULE_PROJECTION_DIRS: readonly string[] = [
1620
+ '.augment/rules',
1621
+ '.windsurf/rules',
1622
+ '.cursor/rules',
1623
+ '.clinerules',
1624
+ ];
1625
+
1626
+ /**
1627
+ * Rule-scope drift (road-to-request-scoped-rule-load, 9.0 consumer flip).
1628
+ *
1629
+ * After the 9.0 default flip to consumer-scoped rule projection, a consumer
1630
+ * whose settings now exclude maintainer-only workspaces may still carry a
1631
+ * pre-flip FULL/GLOBAL rule projection on disk. This check diffs the scope
1632
+ * the consumer's settings imply against the rules actually projected and
1633
+ * reports the leftover set as an ACTIONABLE diff (not a bare boolean).
1634
+ *
1635
+ * The scope-derived expected set reuses the ONE install-time scoping surface
1636
+ * (`rule_scope.ts`), so doctor's diagnosis can never drift from the install
1637
+ * semantics. `excludedRuleBasenames` returns exactly the rules PRESENT in the
1638
+ * projected tree that the current scope says should NOT arrive.
1639
+ *
1640
+ * Degrades gracefully: `skipped` in the agent-config source repo (the in-repo
1641
+ * `.augment/rules` is the full generated projection, not a consumer surface)
1642
+ * and `skipped` when no projected rule tree is present to inspect.
1643
+ */
1644
+ function _check_rule_scope_drift(project_root: string): Dict {
1645
+ if (_is_source_repo(project_root)) {
1646
+ return {
1647
+ id: 'rule-scope-drift',
1648
+ status: 'skipped',
1649
+ message: 'agent-config source repo — no consumer rule projection to inspect',
1650
+ remedy: '',
1651
+ };
1652
+ }
1653
+
1654
+ // Consumer projection scope (absent / empty settings → legacy-all: every
1655
+ // rule ships, so nothing is ever out of scope).
1656
+ let scope: RuleScope = LEGACY_ALL;
1657
+ const settings_file = project_settings_path(project_root);
1658
+ if (isFile(settings_file)) {
1659
+ const loaded = yamlSafeLoad(readText0(settings_file));
1660
+ const raw =
1661
+ typeof loaded === 'object' && loaded !== null && !Array.isArray(loaded)
1662
+ ? (loaded as Dict)
1663
+ : {};
1664
+ scope = ruleScopeFromSettings(raw);
1665
+ }
1666
+
1667
+ // First projected rule tree that exists and holds a `.md` rule.
1668
+ let rules_dir: string | null = null;
1669
+ let found = 0;
1670
+ for (const rel of _RULE_PROJECTION_DIRS) {
1671
+ const cand = path.join(project_root, rel);
1672
+ if (!isDir(cand)) {
1673
+ continue;
1674
+ }
1675
+ let count = 0;
1676
+ try {
1677
+ for (const name of fs.readdirSync(cand)) {
1678
+ if (name.endsWith('.md') && isFile(path.join(cand, name))) {
1679
+ count += 1;
1680
+ }
1681
+ }
1682
+ } catch {
1683
+ continue;
1684
+ }
1685
+ if (count > 0) {
1686
+ rules_dir = cand;
1687
+ found = count;
1688
+ break;
1689
+ }
1690
+ }
1691
+ if (rules_dir === null) {
1692
+ return {
1693
+ id: 'rule-scope-drift',
1694
+ status: 'skipped',
1695
+ message: 'no projected rule tree found (.augment/rules, .windsurf/rules, …) — nothing to inspect',
1696
+ remedy: '',
1697
+ };
1698
+ }
1699
+
1700
+ let unexpected: string[];
1701
+ try {
1702
+ unexpected = excludedRuleBasenames(rules_dir, scope);
1703
+ } catch (exc) {
1704
+ const rel = relativeTo(rules_dir, project_root) ?? rules_dir;
1705
+ return {
1706
+ id: 'rule-scope-drift',
1707
+ status: 'fail',
1708
+ message: `cannot inspect ${rel}: ${osErrorStr(exc)}`,
1709
+ remedy: 'check read permissions on the projected rule tree',
1710
+ };
1711
+ }
1712
+
1713
+ const rel_dir = relativeTo(rules_dir, project_root) ?? rules_dir;
1714
+ if (unexpected.length === 0) {
1715
+ return {
1716
+ id: 'rule-scope-drift',
1717
+ status: 'ok',
1718
+ message: `rule projection matches scope (${found} rules in ${rel_dir})`,
1719
+ remedy: '',
1720
+ };
1721
+ }
1722
+ const expected = found - unexpected.length;
1723
+ const sample = unexpected.slice(0, 8);
1724
+ const more = unexpected.length - sample.length;
1725
+ const names = sample.join(', ') + (more > 0 ? `, +${more} more` : '');
1726
+ return {
1727
+ id: 'rule-scope-drift',
1728
+ status: 'warn',
1729
+ message:
1730
+ `rule-scope drift in ${rel_dir}: expected ${expected} rules, found ${found} — ` +
1731
+ `${unexpected.length} unexpected (out of scope): ${names}`,
1732
+ remedy:
1733
+ 're-run `agent-config init --project` (or `agent-config global`) to re-project rules ' +
1734
+ 'under the current scope — these are leftover from a pre-9.0 full/global projection',
1735
+ };
1736
+ }
1737
+
1593
1738
  function _check_python_runtime(): Dict {
1594
1739
  // Post-teardown: the package runtime is TypeScript-on-`tsx`. python3 is no
1595
1740
  // longer a runtime dependency, so this check no longer probes for an
@@ -2525,6 +2670,7 @@ function _run_checks(
2525
2670
  'surface-state': _check_surface_state,
2526
2671
  'hook-wiring': _check_hook_wiring,
2527
2672
  'stale-orphans': _check_stale_orphans,
2673
+ 'rule-scope-drift': () => _check_rule_scope_drift(project_root),
2528
2674
  'manifest-integrity': () => _check_manifest_integrity(manifest),
2529
2675
  'lockfile-freshness': () => _check_lockfile_freshness(manifest),
2530
2676
  'bridge-drift': () =>
@@ -2582,8 +2728,8 @@ function _check_bridge_drift_no_manifest(bridge_present: boolean): Dict {
2582
2728
  id: 'bridge-drift',
2583
2729
  status: 'skipped',
2584
2730
  message:
2585
- 'no project lockfile and no bridge marker → drift check ' +
2586
- 'not applicable',
2731
+ 'no project lockfile and no consumer install marker → drift ' +
2732
+ 'check not applicable',
2587
2733
  remedy:
2588
2734
  'run `agent-config init` (project install) or ' +
2589
2735
  '`agent-config refresh --project` (global-only consumer)',
@@ -2604,6 +2750,7 @@ function _run_checks_no_manifest(
2604
2750
  'surface-state': _check_surface_state,
2605
2751
  'hook-wiring': _check_hook_wiring,
2606
2752
  'stale-orphans': _check_stale_orphans,
2753
+ 'rule-scope-drift': () => _check_rule_scope_drift(project_root),
2607
2754
  'manifest-integrity': () => _skipped_manifest_check('manifest-integrity'),
2608
2755
  'lockfile-freshness': () => _skipped_manifest_check('lockfile-freshness'),
2609
2756
  'bridge-drift': () => _check_bridge_drift_no_manifest(bridge_present),
@@ -2647,7 +2794,7 @@ function _run_no_manifest(
2647
2794
  print(` 📍 project_root: ${project_root} (origin: ${origin})`);
2648
2795
  if (bridge_present) {
2649
2796
  print(
2650
- ' ℹ️ global-only consumer: bridge marker present, no ' +
2797
+ ' ℹ️ global-only consumer: install marker present, no ' +
2651
2798
  'project lockfile (expected under ADR-020)',
2652
2799
  );
2653
2800
  print(
@@ -2655,7 +2802,7 @@ function _run_no_manifest(
2655
2802
  'to project-local distributed tools',
2656
2803
  );
2657
2804
  } else {
2658
- eprint(` ⚠️ no project lockfile and no bridge marker at ${project_root}`);
2805
+ eprint(` ⚠️ no project lockfile and no consumer install marker at ${project_root}`);
2659
2806
  eprint(
2660
2807
  ' run `agent-config init` (project install) or ' +
2661
2808
  '`agent-config refresh --project` (global-only consumer)',
@@ -2998,7 +3145,7 @@ function main(argv: string[] | null = null): number {
2998
3145
  const manifest_pth = installed_tools.manifest_path(project_root);
2999
3146
  const manifest = installed_tools.read_manifest(manifest_pth);
3000
3147
  if (manifest === null) {
3001
- const bridge_present = isFile(path.join(project_root, BRIDGE_MARKER_RELATIVE));
3148
+ const bridge_present = _is_global_only_consumer(project_root);
3002
3149
  return _run_no_manifest(opts, project_root, origin, bridge_present);
3003
3150
  }
3004
3151
 
@@ -3099,6 +3246,7 @@ export {
3099
3246
  _check_mcp_mode,
3100
3247
  _check_offline_readiness,
3101
3248
  _check_stale_orphans,
3249
+ _check_rule_scope_drift,
3102
3250
  _check_python_runtime,
3103
3251
  _check_humanizer_runtime,
3104
3252
  _check_mcp_beta_readiness,
@@ -3122,7 +3270,8 @@ export {
3122
3270
  CHECK_IDS,
3123
3271
  GLOBAL_CHECK_IDS,
3124
3272
  MANIFEST_REQUIRED_CHECK_IDS,
3125
- BRIDGE_MARKER_RELATIVE,
3273
+ INSTALL_MODE_MARKER_RELATIVE,
3274
+ _is_global_only_consumer,
3126
3275
  REPAIR_IDS,
3127
3276
  MCP_BETA_GATES,
3128
3277
  STATUS_SYMBOLS,
@@ -16,9 +16,11 @@
16
16
  * global root + Claude plugin hooks are rewritten from the
17
17
  * currently-installed package. Idempotent: a second run is a no-op diff.
18
18
  * - `--project` — refresh the **minimal** project surface ADR-020 permits for
19
- * a consumer: the `agents/.event4u-bridge.yml` marker, an `agents/overrides/`
20
- * scaffold, and the managed `agents/` block in `.gitignore`. Writes **no**
21
- * distributed content. No wizard.
19
+ * a consumer: an `agents/overrides/` scaffold and the managed `agents/`
20
+ * block in `.gitignore`, plus cleanup of any legacy
21
+ * `agents/.event4u-bridge.yml` (the marker was retired — ADR-020 amendment
22
+ * 2026-07-13; the global root is resolved from `~/.event4u/agent-config`).
23
+ * Writes **no** distributed content. No wizard.
22
24
  *
23
25
  * Bare `agent-config refresh` (no scope flag) errors — never a silent global
24
26
  * default (council 2026-05-30).
@@ -39,17 +41,15 @@
39
41
  * `cannot run <cmd[0]>: <err>` line to stderr and returns 1, mirroring the
40
42
  * Python `except OSError`. The runner is injectable for tests.
41
43
  * - `_lib.*` / `scripts.install` / `scripts.sync_gitignore` imports resolve to
42
- * the `.ts` twins. `scripts.install._write_consumer_bridge_marker` is
43
- * module-private in the install twin (not exported); it is replicated here
44
- * byte-for-byte from the install-twin source — its helper chain
45
- * (`_format_global_root_for_marker`, the atomic 0644 write, the UTC stamp,
46
- * `user_global_paths.event4u_root`) is replicated alongside, keeping the
47
- * shared import surface unchanged (same approach as `cmd_update.ts`).
44
+ * the `.ts` twins. `scripts.install._remove_legacy_consumer_bridge_marker`
45
+ * is module-private in the install twin (not exported); it is replicated
46
+ * here — a small `isFile` + `fs.rmSync` guard gated on dev-mode / source
47
+ * repo, matching the install-twin source.
48
48
  * - `Path(__file__).resolve().parents[3]` → `path.resolve(_HERE_DIR, '..',
49
49
  * '..', '..')`. `.is_file()` / `.is_dir()` / `.exists()` →
50
50
  * `_isFile` / `_isDir` / `_exists`. The `packages/` source-marker glob probe
51
51
  * becomes a `readdirSync` scan of `packages/` (the source-marker dir name is
52
- * the `SRC_MARKER_DIR` constant, shared with the bridge-marker guard).
52
+ * the `SRC_MARKER_DIR` constant, shared with the marker-cleanup guard).
53
53
  * - `print(..., file=out/err)` → an injectable `OutSink` defaulting to
54
54
  * `process.stdout` / `process.stderr`. The Python `out`/`err` kwargs default
55
55
  * to `sys.stdout` / `sys.stderr`.
@@ -60,17 +60,13 @@
60
60
  * `except ImportError` branch wording.
61
61
  */
62
62
 
63
- import * as crypto from 'node:crypto';
64
63
  import * as fs from 'node:fs';
65
- import * as os from 'node:os';
66
64
  import * as path from 'node:path';
67
65
  import { spawnSync } from 'node:child_process';
68
66
  import process from 'node:process';
69
67
  import { fileURLToPath, pathToFileURL } from 'node:url';
70
68
 
71
- import * as installed_lock from '../_lib/installed_lock.js';
72
69
  import * as cli_wrapper from '../_lib/cli_wrapper.js';
73
- import * as user_global_paths from '../_lib/user_global_paths.js';
74
70
  import * as sync_gitignore from '../sync_gitignore.js';
75
71
 
76
72
  const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
@@ -149,103 +145,30 @@ const CONSUMER_BRIDGE_MARKER_RELPATH = path.join('agents', '.event4u-bridge.yml'
149
145
  */
150
146
  const SRC_MARKER_DIR = '.agent-src.uncondensed';
151
147
 
152
- /** `Path.resolve()` — absolute, symlink-resolved where possible. */
153
- function resolvePath(p: string): string {
154
- try {
155
- return fs.realpathSync(path.resolve(p));
156
- } catch {
157
- return path.resolve(p);
158
- }
159
- }
160
-
161
- function _format_global_root_for_marker(global_root: string): string {
162
- const home = resolvePath(os.homedir());
163
- const resolved = resolvePath(global_root);
164
- const rel = path.relative(home, resolved);
165
- if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel)) {
166
- return global_root;
167
- }
168
- return `~/${rel.split(path.sep).join('/')}`;
169
- }
170
-
171
- /** `datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")`. */
172
- function utcStamp(now?: Date): string {
173
- const d = now ?? new Date();
174
- const pad = (n: number, w = 2): string => String(n).padStart(w, '0');
175
- return (
176
- `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}` +
177
- `T${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}Z`
178
- );
179
- }
180
-
181
- function mkdirp(p: string): void {
182
- fs.mkdirSync(p, { recursive: true });
183
- }
184
-
185
- /** `tempfile.mkstemp` + write + `os.chmod(0o644)` + `os.replace` (atomic). */
186
- function atomicWrite0644(target: string, body: string, prefix: string): void {
187
- const dir = path.dirname(target);
188
- const tmpName = path.join(
189
- dir,
190
- `${prefix}${process.pid}.${crypto.randomBytes(6).toString('hex')}.yml.tmp`,
191
- );
192
- let fd: number | null = null;
193
- try {
194
- fd = fs.openSync(tmpName, 'wx', 0o644);
195
- fs.writeFileSync(fd, body, 'utf-8');
196
- fs.closeSync(fd);
197
- fd = null;
198
- fs.chmodSync(tmpName, 0o644);
199
- fs.renameSync(tmpName, target);
200
- } catch (err) {
201
- if (fd !== null) {
202
- try {
203
- fs.closeSync(fd);
204
- } catch {
205
- /* fd may already be closed */
206
- }
207
- }
208
- try {
209
- fs.unlinkSync(tmpName);
210
- } catch {
211
- /* best-effort cleanup */
212
- }
213
- throw err;
214
- }
215
- }
216
148
 
217
149
  /**
218
- * Mirrors `install._write_consumer_bridge_marker` (module-private). Returns the
219
- * written path, or `null` when skipped (dev mode / source repo).
150
+ * Mirrors `install._remove_legacy_consumer_bridge_marker` (module-private).
151
+ * The consumer bridge marker was retired (ADR-020 amendment 2026-07-13); the
152
+ * global root is resolved from the well-known `~/.event4u/agent-config` path.
153
+ * `refresh --project` deletes any legacy `agents/.event4u-bridge.yml` a prior
154
+ * install committed. Returns the removed path, or `null` when nothing to do
155
+ * (no marker / dev mode / source repo).
220
156
  */
221
- function _write_consumer_bridge_marker(
157
+ function _remove_legacy_consumer_bridge_marker(
222
158
  project_root: string,
223
- installer_version: string,
224
159
  env: NodeJS.ProcessEnv | null = null,
225
- now: Date | null = null,
226
160
  ): string | null {
227
161
  const env_map = env ?? process.env;
228
162
  if (env_map['AGENT_CONFIG_DEV_MODE'] === '1') return null;
229
163
  if (_isDir(path.join(project_root, SRC_MARKER_DIR))) return null;
230
164
 
231
- const global_root_str = _format_global_root_for_marker(
232
- user_global_paths.event4u_root(env_map),
233
- );
234
- const stamp = utcStamp(now ?? undefined);
235
-
236
- const body =
237
- '# event4u/agent-config — consumer bridge marker (auto-written).\n' +
238
- '# Spec: docs/contracts/consumer-bridge.md (event4u-bridge/v1).\n' +
239
- '# Reader contract: expand ~ against the current $HOME; fail closed\n' +
240
- '# when global_root is missing on disk; never write back through it.\n' +
241
- 'schema: event4u-bridge/v1\n' +
242
- `global_root: ${global_root_str}\n` +
243
- `installed_at: ${stamp}\n` +
244
- `installer_version: ${installer_version}\n`;
245
-
246
165
  const target = path.join(project_root, CONSUMER_BRIDGE_MARKER_RELPATH);
247
- mkdirp(path.dirname(target));
248
- atomicWrite0644(target, body, '.event4u-bridge.');
166
+ if (!_isFile(target)) return null;
167
+ try {
168
+ fs.rmSync(target);
169
+ } catch {
170
+ return null;
171
+ }
249
172
  return target;
250
173
  }
251
174
 
@@ -292,10 +215,10 @@ function _refresh_global(runner: Runner, out: OutSink, err: OutSink): number {
292
215
  /**
293
216
  * True when `project_root` is the agent-config package itself.
294
217
  *
295
- * `_write_consumer_bridge_marker` only guards on a root-level
218
+ * `_remove_legacy_consumer_bridge_marker` only guards on a root-level
296
219
  * `.agent-src.uncondensed/`, which the monorepo keeps under `packages/<pkg>/`
297
- * instead — so the narrow guard misses here and would write a consumer marker
298
- * into the maintainer tree. This broader check (condensed output, packaged
220
+ * instead — so the narrow guard misses here and would touch the maintainer
221
+ * tree. This broader check (condensed output, packaged
299
222
  * source, or the package's own `package.json` name) makes `refresh --project`
300
223
  * a no-op in any agent-config checkout. Consumers use dev-mode, not refresh.
301
224
  */
@@ -350,20 +273,14 @@ function _refresh_project(project_root: string, out: OutSink, err: OutSink): num
350
273
  return 0;
351
274
  }
352
275
 
353
- const version = installed_lock.current_package_version() || '0.0.0';
354
- const marker = _write_consumer_bridge_marker(project_root, version);
355
- if (marker === null) {
356
- _print(
357
- out,
358
- 'ℹ️ refresh --project skipped the bridge marker: this is the ' +
359
- 'agent-config source repo (or AGENT_CONFIG_DEV_MODE=1). Nothing ' +
360
- 'to scaffold in a maintainer checkout.',
361
- );
362
- return 0;
276
+ // Bridge marker retired (ADR-020 amendment 2026-07-13). The global root is
277
+ // resolved from the well-known `~/.event4u/agent-config` path, so there is
278
+ // no per-project marker to scaffold — clean up any legacy one instead.
279
+ const removed = _remove_legacy_consumer_bridge_marker(project_root);
280
+ if (removed !== null) {
281
+ _print(out, `✅ removed legacy bridge marker: ${removed}`);
363
282
  }
364
283
 
365
- _print(out, `✅ bridge marker: ${marker}`);
366
-
367
284
  const overrides = path.join(project_root, 'agents', 'overrides');
368
285
  fs.mkdirSync(overrides, { recursive: true });
369
286
  const keep = path.join(overrides, 'README.md');
@@ -372,8 +289,8 @@ function _refresh_project(project_root: string, out: OutSink, err: OutSink): num
372
289
  keep,
373
290
  '# Project overrides\n\n' +
374
291
  'Project-local overrides/extensions of shared skills, rules, and ' +
375
- 'commands. The only project-side agent surface ADR-020 permits ' +
376
- 'besides the bridge marker. See the `override-management` skill.\n',
292
+ 'commands. The only project-side agent surface ADR-020 permits. ' +
293
+ 'See the `override-management` skill.\n',
377
294
  );
378
295
  }
379
296
  _print(out, `✅ overrides scaffold: ${overrides}`);
@@ -543,7 +460,7 @@ export {
543
460
  _is_source_repo,
544
461
  _refresh_project,
545
462
  _sync_gitignore,
546
- _write_consumer_bridge_marker,
463
+ _remove_legacy_consumer_bridge_marker,
547
464
  ArgparseExit,
548
465
  };
549
466
  export type { Args, Runner };