@event4u/agent-config 1.41.1 → 2.0.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.
@@ -26,6 +26,20 @@
26
26
 
27
27
  schema_version: 1
28
28
 
29
+ # --- Agent config version pin (project-shared) ---
30
+ #
31
+ # Exact semver of @event4u/agent-config this project commits to. The
32
+ # `npx @event4u/agent-config` dispatcher re-execs at this version when
33
+ # the developer's locally-resolved version differs — so every
34
+ # contributor runs the same agent surface regardless of their npm
35
+ # cache state. Bump this together with a tested upgrade. Empty string
36
+ # disables the pin (resolver picks the latest release; only safe for
37
+ # greenfield projects). Ranges (^, ~, >=) are NOT supported.
38
+ # CI guard: a release bump of `package.json` must update this value
39
+ # in lockstep — see scripts/check_template_pin_drift.py (road-to-
40
+ # portable-runtime-and-update-check P3.3).
41
+ agent_config_version: "1.41.2"
42
+
29
43
  # --- Project identity ---
30
44
  project:
31
45
  # Short slug used in agent output, prompts, and PR metadata. Keep it
@@ -3,17 +3,24 @@
3
3
  Phase 1 of road-to-portable-dev-preferences. Single source of truth for
4
4
  how scripts read agent settings — replaces ~15 ad-hoc loaders in P3.
5
5
 
6
- Resolution order (project wins, user-global fills gaps for whitelisted
7
- keys only):
6
+ Resolution order (deepest wins; user-global is whitelist-filtered only):
8
7
 
9
- 1. Project ``./.agent-settings.yml`` (full file, all keys)
10
- 2. ``~/.config/agent-config/agent-settings.yml`` (whitelist only)
11
- 3. Built-in defaults (currently empty)
8
+ N. ``~/.config/agent-config/agent-settings.yml`` (user-global; whitelist only)
9
+ N-1. ``<repo-root>/.agent-settings.yml`` (project-wide; all keys)
10
+ N-2. ``<intermediate-dir>/.agent-settings.yml`` (subsystem-scoped; all keys)
11
+ 1. ``<CWD>/.agent-settings.yml`` (deepest, wins; all keys)
12
+
13
+ ``<repo-root>`` is the nearest ancestor that contains ``.git`` (directory
14
+ **or** file — submodule support). The walk stops there — it never drifts
15
+ into a parent repo or ``$HOME``. When ``cwd`` is ``None`` (default), the
16
+ loader behaves identically to the pre-cascade contract: project file +
17
+ user-global only, no ancestor walk. Back-compat is hard.
12
18
 
13
19
  Whitelisted keys (``MERGEABLE_KEYS``) are exact dotted paths. A
14
20
  non-whitelisted key in the user-global file is silently ignored — the
15
21
  ``verbose=True`` flag surfaces ignored paths via ``logging.info`` for
16
- debugging.
22
+ debugging. Non-root in-project layers (intermediate + CWD) are **not**
23
+ whitelist-filtered — they live inside the project boundary.
17
24
 
18
25
  Contract — pure, read-only, tolerant:
19
26
 
@@ -28,7 +35,7 @@ from __future__ import annotations
28
35
 
29
36
  import logging
30
37
  from pathlib import Path
31
- from typing import Any
38
+ from typing import Any, Iterator
32
39
 
33
40
  logger = logging.getLogger(__name__)
34
41
 
@@ -53,10 +60,70 @@ MERGEABLE_KEYS: tuple[str, ...] = (
53
60
  _DEFAULTS: dict[str, Any] = {}
54
61
 
55
62
 
63
+ def find_project_root(start: Path) -> Path | None:
64
+ """Walk up from ``start`` looking for ``.git`` (file or directory).
65
+
66
+ Returns the first ancestor that contains ``.git`` as a file (submodule
67
+ pointer) or directory (regular checkout), or ``None`` if the walk
68
+ reaches the filesystem root without finding one. The walk stops at
69
+ the project boundary — it never drifts into a parent repo or
70
+ ``$HOME``.
71
+
72
+ Pure read-only; never touches the filesystem beyond ``exists()``
73
+ probes on the ``.git`` entry.
74
+ """
75
+ current = start.resolve() if start.exists() else start
76
+ # ``Path.parents`` excludes ``current`` itself, so probe it first.
77
+ for candidate in [current, *current.parents]:
78
+ git_marker = candidate / ".git"
79
+ if git_marker.exists():
80
+ return candidate
81
+ return None
82
+
83
+
84
+ def _resolve_cascade_paths(
85
+ cwd: Path | None,
86
+ project_path: Path | str | None,
87
+ ) -> list[Path]:
88
+ """Return the ordered cascade of in-project settings files (shallow → deep).
89
+
90
+ When ``cwd`` is provided and ``find_project_root(cwd)`` succeeds, the
91
+ list contains every ``<dir>/.agent-settings.yml`` from the repo root
92
+ down to ``cwd`` (inclusive on both ends), shallowest first. When
93
+ ``cwd`` is ``None`` or no ``.git`` is reached, falls back to the
94
+ single legacy project path — back-compat with the pre-cascade
95
+ loader.
96
+ """
97
+ if cwd is None:
98
+ legacy = Path(project_path) if project_path else Path(DEFAULT_PROJECT_FILE)
99
+ return [legacy]
100
+
101
+ root = find_project_root(cwd)
102
+ if root is None:
103
+ legacy = Path(project_path) if project_path else Path(DEFAULT_PROJECT_FILE)
104
+ return [legacy]
105
+
106
+ cwd_resolved = cwd.resolve()
107
+ # Build the chain root → … → cwd (shallowest first, deepest last).
108
+ chain: list[Path] = []
109
+ cursor = cwd_resolved
110
+ while True:
111
+ chain.append(cursor)
112
+ if cursor == root:
113
+ break
114
+ parent = cursor.parent
115
+ if parent == cursor:
116
+ break
117
+ cursor = parent
118
+ chain.reverse()
119
+ return [d / DEFAULT_PROJECT_FILE for d in chain]
120
+
121
+
56
122
  def load_agent_settings(
57
123
  project_path: Path | str | None = None,
58
124
  user_global_path: Path | str | None = None,
59
125
  verbose: bool = False,
126
+ cwd: Path | None = None,
60
127
  ) -> dict[str, Any]:
61
128
  """Return the merged settings dict.
62
129
 
@@ -65,10 +132,16 @@ def load_agent_settings(
65
132
  ``~/.config/agent-config/agent-settings.yml``. Both arguments accept
66
133
  ``Path`` or ``str``. Pass ``verbose=True`` to log keys present in
67
134
  user-global that are not on the whitelist.
135
+
136
+ ``cwd`` enables the in-project cascade: when provided **and**
137
+ ``find_project_root(cwd)`` reaches a ``.git`` ancestor, the loader
138
+ walks every ``.agent-settings.yml`` from the repo root down to
139
+ ``cwd`` and merges them shallowest → deepest (deepest wins).
140
+ Non-root layers are **not** whitelist-filtered (they live inside the
141
+ project boundary). When ``cwd`` is ``None`` (default), the loader
142
+ falls back to the single ``project_path`` behaviour — back-compat
143
+ with pre-cascade callers.
68
144
  """
69
- project = _read_yaml(
70
- Path(project_path) if project_path else Path(DEFAULT_PROJECT_FILE),
71
- ) or {}
72
145
  user_global_raw = _read_yaml(
73
146
  Path(user_global_path) if user_global_path else DEFAULT_USER_GLOBAL_FILE,
74
147
  ) or {}
@@ -82,12 +155,48 @@ def load_agent_settings(
82
155
  sorted(ignored),
83
156
  )
84
157
 
158
+ cascade = _resolve_cascade_paths(cwd, project_path)
159
+
85
160
  merged: dict[str, Any] = _deep_copy_defaults(_DEFAULTS)
86
161
  _deep_merge(merged, user_global_filtered)
87
- _deep_merge(merged, project)
162
+ for path in cascade:
163
+ layer = _read_yaml(path) or {}
164
+ if layer:
165
+ _deep_merge(merged, layer)
88
166
  return merged
89
167
 
90
168
 
169
+ def iter_setting_overrides(
170
+ project_path: Path | str | None = None,
171
+ user_global_path: Path | str | None = None,
172
+ cwd: Path | None = None,
173
+ ) -> Iterator[tuple[str, Any, Path]]:
174
+ """Yield ``(dotted_key, value, source_path)`` for every leaf setting.
175
+
176
+ Walks the same cascade as :func:`load_agent_settings` and emits one
177
+ tuple per leaf observed at each layer (user-global → repo-root →
178
+ intermediates → CWD). Callers can detect overrides by grouping
179
+ tuples on ``dotted_key`` — the deepest tuple per group wins. Useful
180
+ for ``task settings:trace`` and other banner-only diagnostics.
181
+ Never blocks, never raises on missing files.
182
+ """
183
+ user_global_path_resolved = (
184
+ Path(user_global_path) if user_global_path else DEFAULT_USER_GLOBAL_FILE
185
+ )
186
+ user_global_raw = _read_yaml(user_global_path_resolved) or {}
187
+ user_global_filtered, _ = _filter_whitelist(user_global_raw, MERGEABLE_KEYS)
188
+ if user_global_filtered:
189
+ for key in _leaf_paths(user_global_filtered):
190
+ yield key, _get_dotted(user_global_filtered, key), user_global_path_resolved
191
+
192
+ for path in _resolve_cascade_paths(cwd, project_path):
193
+ layer = _read_yaml(path)
194
+ if not layer:
195
+ continue
196
+ for key in _leaf_paths(layer):
197
+ yield key, _get_dotted(layer, key), path
198
+
199
+
91
200
  def _read_yaml(path: Path) -> dict[str, Any] | None:
92
201
  """Best-effort YAML read; never raises. Returns ``None`` on any failure."""
93
202
  if not path.is_file():
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Shared agent configuration \u2014 skills for AI coding tools (Claude Code, Augment, Cursor, Cline, Windsurf, Gemini CLI).",
9
- "version": "1.41.1"
9
+ "version": "2.0.0"
10
10
  },
11
11
  "plugins": [
12
12
  {
package/CHANGELOG.md CHANGED
@@ -318,6 +318,37 @@ our recommendation order, not its support status.
318
318
  users" tension without removing any path that an existing user
319
319
  might rely on.
320
320
 
321
+ ## [2.0.0](https://github.com/event4u-app/agent-config/compare/1.41.2...2.0.0) (2026-05-12)
322
+
323
+ ### BREAKING CHANGES
324
+
325
+ * **install:** drop composer + npm postinstall, go npx-only ([6bc6c99](https://github.com/event4u-app/agent-config/commit/6bc6c99f57d2a2bcdab03bfacab429ce146dd9b9))
326
+
327
+ ### Features
328
+
329
+ * **cli:** add update + migrate commands, version-pin resolver, CI drift guard ([b1e34fc](https://github.com/event4u-app/agent-config/commit/b1e34fcd7a03d38266e40dd053414fc9d20c9024))
330
+ * **update-check:** add daily npm-registry version probe + banner ([2c4d752](https://github.com/event4u-app/agent-config/commit/2c4d752d5eb28f1c460470668704f5cf403c4046))
331
+ * **settings:** add hierarchical project-settings cascade + agents overlay ([3296885](https://github.com/event4u-app/agent-config/commit/32968855ca17210b1594cea2e79778acb0476f0a))
332
+
333
+ ### Tests
334
+
335
+ * **install:** drop postinstall.sh test cases removed in P0 ([d4e7750](https://github.com/event4u-app/agent-config/commit/d4e7750bcd13b021caa4ccaccff821dc0ae8e537))
336
+
337
+ ### Chores
338
+
339
+ * **template:** mirror agent_settings cascade into work_engine template ([92f4716](https://github.com/event4u-app/agent-config/commit/92f471662c463efec0df591099a3507919396d77))
340
+ * **roadmap:** archive completed portable-runtime-and-update-check ([91e2e4e](https://github.com/event4u-app/agent-config/commit/91e2e4ef75042d2d19a528874ebcac4b94c4046a))
341
+
342
+ Tests: 3257 (+84 since 1.41.2)
343
+
344
+ ## [1.41.2](https://github.com/event4u-app/agent-config/compare/1.41.1...1.41.2) (2026-05-12)
345
+
346
+ ### Documentation
347
+
348
+ * **roadmap:** invert portable-runtime to npx-only distribution ([f8f6594](https://github.com/event4u-app/agent-config/commit/f8f65944490f69528616d21f4aa008c7c3d0bfa9))
349
+
350
+ Tests: 3173 (+0 since 1.41.1)
351
+
321
352
  ## [1.41.1](https://github.com/event4u-app/agent-config/compare/1.41.0...1.41.1) (2026-05-12)
322
353
 
323
354
  ### Documentation
package/README.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Agent Config — Governed Agent System
2
2
 
3
+ > ⚠️ **Breaking change in vX.0** — `agent-config` now ships as an
4
+ > **npx-only runtime**. `composer require` / `npm install --save-dev`
5
+ > are gone, the `--global` symlink scheme is retired. Existing
6
+ > consumers: run `npx @event4u/agent-config migrate`
7
+ > ([guide](docs/migration/v1-to-v2.md)). New consumers: jump to
8
+ > [Quickstart](#quickstart).
9
+
3
10
  > **agent-config is not a runtime, but it ships a deterministic orchestration contract / state machine for host agents.**
4
11
 
5
12
  Give your AI agents an audit-disciplined orchestration contract — testing, Git, CI, code review, and **120+ stack-aware skills** — with quality guardrails built in.
@@ -26,39 +33,40 @@ If none of those apply yet — start with the [Quickstart](#quickstart) and pick
26
33
 
27
34
  ## Quickstart
28
35
 
29
- Two minutes from `composer require` to a better-behaved agent.
36
+ Two minutes from `npx` to a better-behaved agent — no install, no
37
+ vendored package, no postinstall hook.
30
38
 
31
39
  ### For teams (recommended)
32
40
 
33
- Install once in the project — available to everyone who works on it:
41
+ Run once in the project root `npx` resolves the runtime against the
42
+ npm registry on every invocation, and the version pin in
43
+ `.agent-settings.yml` keeps it reproducible:
34
44
 
35
45
  ```bash
36
- # PHP
37
- composer require --dev event4u/agent-config
46
+ # Bootstrap (writes .agent-settings.yml, .augment/, .claude/, …):
47
+ npx @event4u/agent-config init
38
48
 
39
- # JavaScript/TypeScript
40
- npm install --save-dev @event4u/agent-config
49
+ # Any subsequent command:
50
+ npx @event4u/agent-config <command>
41
51
  ```
42
52
 
43
- After installing the package, run the installer to sync the payload and
44
- create `.agent-settings.yml`, `.vscode/settings.json`, `.augment/settings.json`,
45
- and the tool-specific glue:
53
+ The init writes:
46
54
 
47
- ```bash
48
- # PHP / Composer projects — explicit step (Composer does not auto-run it):
49
- php vendor/bin/install.php
50
- # or directly (any environment):
51
- bash vendor/event4u/agent-config/scripts/install
52
-
53
- # npm projects run the installer automatically via postinstall.
54
- # To re-run or override the default profile:
55
- bash node_modules/@event4u/agent-config/scripts/install --profile=balanced
56
- ```
55
+ - `.agent-settings.yml` (including the `agent_config_version` pin)
56
+ - `.vscode/settings.json`, `.augment/settings.json`
57
+ - per-tool glue: `.claude/`, `.cursor/`, `.clinerules/`,
58
+ `.windsurfrules`, `GEMINI.md`, `.github/copilot-instructions.md`
59
+
60
+ → Migrating from a pre-vX.0 install? See
61
+ [`docs/migration/v1-to-v2.md`](docs/migration/v1-to-v2.md). The one-shot
62
+ `npx @event4u/agent-config migrate` removes the legacy
63
+ `composer.json` entry / `node_modules/@event4u/agent-config`,
64
+ deletes the retired `~/.claude/{rules,skills}/event4u/` namespace if
65
+ present, and writes the new `.agent-settings.yml` shape.
57
66
 
58
- **To install:** no Task / Make / build tools`scripts/install` runs a
59
- bash payload sync plus a Python 3 bridge generator (stdlib only, default
60
- on macOS 12.3+ / major Linux distros). Python missing → orchestrator
61
- warns and continues payload-only. Task is needed only for *contributors*
67
+ **To run:** Node 18 and Python 3 (stdlib only default on macOS
68
+ 12.3+ / major Linux distros). Python missing orchestrator warns and
69
+ continues payload-only. Task is needed only for *contributors*
62
70
  rebuilding compressed content — see [CONTRIBUTING.md](CONTRIBUTING.md).
63
71
 
64
72
  **Verify hook coverage** after installing — every supported platform
@@ -410,9 +418,10 @@ kernel set: [`docs/contracts/kernel-membership.md`](docs/contracts/kernel-member
410
418
 
411
419
  ## Supported Tools
412
420
 
413
- ### Project-installed (Composer / npm)
421
+ ### Project-installed (`npx`)
414
422
 
415
- Every developer gets the same behavior. No per-user setup needed.
423
+ Every developer gets the same behavior. No per-user setup needed
424
+ `npx @event4u/agent-config init` writes the per-tool glue listed below.
416
425
 
417
426
  | Tool | Rules | Skills | Commands | How it works |
418
427
  |---|---|---|---|---|
@@ -5,6 +5,18 @@
5
5
  # values here directly or ask the agent (it follows the merge rules in
6
6
  # .augment/guidelines/agent-infra/layered-settings.md).
7
7
 
8
+ # --- Agent config version pin ---
9
+ #
10
+ # Exact semver of the @event4u/agent-config release this project is
11
+ # pinned to. Used by `npx @event4u/agent-config <cmd>` to re-exec at
12
+ # the pinned version when the developer's local resolution differs.
13
+ # Empty string = unpinned (resolver picks the latest release; only safe
14
+ # for greenfield projects). Ranges (^, ~, >=) are NOT supported — the
15
+ # pin replaces lockfile determinism, so it must be exact.
16
+ # See docs/customization.md § "Agent config version pin" and
17
+ # docs/architecture.md § "npx-only distribution + version-pin governance".
18
+ agent_config_version: ""
19
+
8
20
  # --- Cost profile ---
9
21
  #
10
22
  # Master switch that controls which rule tiers load each session.
@@ -376,3 +388,16 @@ memory:
376
388
  # or paths outside the repo root that must not land in intake JSONL.
377
389
  # Example: ["api[_-]?key", "/Users/[a-z]+/Library"]
378
390
  redact_patterns: []
391
+
392
+ # --- Update check ---
393
+ #
394
+ # Daily background check against the npm registry for a newer
395
+ # @event4u/agent-config release. When enabled the dispatcher writes a
396
+ # two-line banner to stderr **after** the subcommand finishes; CI,
397
+ # non-TTY stdout, and `AGENT_CONFIG_NO_UPDATE_CHECK=1` always suppress
398
+ # it regardless of this flag. State is persisted at
399
+ # `~/.config/agent-config/update-check.json` (mode 0600) with a 24 h
400
+ # cadence. See docs/customization.md § "Update check" for the suppression
401
+ # matrix and the road-to-portable-runtime-and-update-check roadmap (P2).
402
+ update_check:
403
+ enabled: true
@@ -70,6 +70,52 @@ them to the relative form when writing into `.agent-src/`. Hardcoding
70
70
  `.agent-src.uncompressed/` in source frontmatter or body links is
71
71
  forbidden and caught by `scripts/check_compressed_paths.py`.
72
72
 
73
+ ### Distribution model — npx-only + version-pin governance
74
+
75
+ Starting with the road-to-portable-runtime cutover, the package ships
76
+ exclusively as a **runtime resolved by `npx @event4u/agent-config`**.
77
+ There is no Composer dependency, no `npm install` step, no `--global`
78
+ symlink scheme. Consumers run:
79
+
80
+ ```bash
81
+ npx @event4u/agent-config init # bootstrap a project
82
+ npx @event4u/agent-config <cmd> # any subsequent command
83
+ ```
84
+
85
+ **Why local installs are gone.** Vendoring the package into every
86
+ consumer's `vendor/` or `node_modules/` created three problems: stale
87
+ runtimes diverging from the published version, build-system coupling
88
+ (Composer post-install hooks, npm postinstall scripts), and a parallel
89
+ "global install" scheme that copied curated skills into `~/.claude/`,
90
+ `~/.cursor/`, `~/.codeium/windsurf/` under an `event4u/` namespace.
91
+ Maintenance cost was high, the abstractions leaked across surfaces, and
92
+ debugging "which version is loaded" was non-trivial. `npx` resolves the
93
+ runtime per invocation against a single npm registry source, which
94
+ collapses all three failure modes.
95
+
96
+ **How the pin replaces lockfile determinism.** A consumer-managed
97
+ `composer.lock` / `package-lock.json` previously froze the runtime
98
+ version per repo. Under npx-only, the equivalent role is played by the
99
+ `agent_config_version` field in the consumer's `.agent-settings.yml`
100
+ (see `config/agent-settings.template.yml`). The dispatcher reads the
101
+ pin on every invocation and re-execs `npx @event4u/agent-config@<pin>`
102
+ if the resolved version diverges (P3.2 pin-resolver). The pin lives in
103
+ the consumer's repo, is reviewed in PRs, and survives `npx`'s own
104
+ cache eviction.
105
+
106
+ **Q1 council rejection + override + pin as substitute.** During Q1
107
+ planning, the architecture council rejected the npx-only proposal on
108
+ the grounds that `npx` resolution introduces a "09:00 vs 09:15
109
+ release skew" window where two developers running the same command
110
+ minutes apart could see different runtimes. The user overrode the
111
+ rejection on the condition that an explicit version pin replaces
112
+ lockfile determinism — the council's concern is real, but the pin
113
+ collapses the skew window to whatever the project's PR cadence is.
114
+ Pin drift across developers becomes a reviewable config change in
115
+ `.agent-settings.yml` rather than an invisible registry-resolution
116
+ race. ADR-pending entry will record the trade-off in full once P3 is
117
+ green.
118
+
73
119
  ### Cloud-bundle pipeline
74
120
 
75
121
  `task build-cloud-bundles-all` produces one ZIP per skill at
@@ -67,30 +67,76 @@ personal.autonomy
67
67
  caveman.speak_scope
68
68
  ```
69
69
 
70
- **Merge order** (lowest → highest precedence):
70
+ **Merge order** (lowest → highest precedence; every layer optional):
71
71
 
72
72
  ```
73
73
  1. Package defaults (shipped by event4u/agent-config)
74
74
  2. ~/.config/agent-config/agent-settings.yml (user-global · whitelist-filtered)
75
- 3. <project>/.agent-settings.yml (project-local · always wins)
75
+ 3. <repo-root>/.agent-settings.yml (project-wide · all keys)
76
+ 4. <intermediate-dir>/.agent-settings.yml (subsystem-scoped · all keys · optional)
77
+ 5. <CWD>/.agent-settings.yml (deepest · all keys · wins)
76
78
  ```
77
79
 
78
- Project-local values **always win**. The user-global file is a
79
- fallback, never a lock. Non-whitelisted keys in the user-global file
80
- are dropped without error adding `personal.theme` there has no
81
- effect.
82
-
83
- The file is created **only on explicit opt-in via `/onboard`**. The
84
- loader at [`scripts/_lib/agent_settings.py`](../scripts/_lib/agent_settings.py)
80
+ `<repo-root>` is the nearest ancestor of the CWD that contains a `.git`
81
+ directory **or** file (submodule support). The walk stops there it
82
+ never drifts into a parent repo or `$HOME`. Callers that omit the
83
+ ``cwd`` argument get the legacy two-layer behaviour (user-global +
84
+ single project file) — back-compat is hard.
85
+
86
+ Project-local values **always win** over user-global. The user-global
87
+ file is a fallback, never a lock. Non-whitelisted keys in the
88
+ user-global file are dropped without error — adding `personal.theme`
89
+ there has no effect.
90
+
91
+ **Whitelist asymmetry.** The six-key whitelist applies **only** to the
92
+ user-global layer. Non-root in-project layers (intermediate +
93
+ ``<CWD>``) carry arbitrary keys — they live inside the project
94
+ boundary, are tracked in git, and reviewed in PRs like any other
95
+ config. Use a subdirectory `.agent-settings.yml` to scope a single
96
+ field (e.g. a `cost_profile` override for `services/heavy-ml/`) without
97
+ duplicating the root file.
98
+
99
+ The user-global file is created **only on explicit opt-in via
100
+ `/onboard`**. The loader at
101
+ [`scripts/_lib/agent_settings.py`](../scripts/_lib/agent_settings.py)
85
102
  is **read-only** — no script can create or mutate it without an
86
103
  explicit `/onboard` confirmation. Edit the file by hand for mid-life
87
104
  changes; `/sync-agent-settings` stays project-scoped and never touches
88
105
  user-global state.
89
106
 
107
+ ### Agent config version pin
108
+
109
+ The top-level `agent_config_version` key pins the project to an exact
110
+ release of `@event4u/agent-config`. Under the npx-only distribution
111
+ model (see [`docs/architecture.md`](architecture.md) §
112
+ *"npx-only distribution + version-pin governance"*), there is no
113
+ local `node_modules/` or `vendor/` lockfile to anchor the runtime —
114
+ the pin is the substitute mechanism.
115
+
116
+ ```yaml
117
+ agent_config_version: "2.0.3" # exact semver, no ranges
118
+ ```
119
+
120
+ Rules:
121
+
122
+ - **Exact semver only.** Ranges (`^2.0`, `~2.0.3`, `>=2.0`) are
123
+ rejected — the pin must be reproducible across the team.
124
+ - **Empty string = unpinned.** The resolver picks the latest release
125
+ on every invocation. Only safe for greenfield projects; production
126
+ consumers should pin.
127
+ - **Owned by the project, not the developer.** Lives in
128
+ `.agent-settings.yml` (committed), reviewed in PRs like any other
129
+ config change. Never merged from `~/.config/agent-config/agent-settings.yml`.
130
+ - **Resolver enforcement.** `npx @event4u/agent-config <cmd>`
131
+ compares the resolved CLI version against the pin; mismatch
132
+ triggers a re-exec at the pinned version
133
+ (`npx @event4u/agent-config@<pin> <cmd>`).
134
+
90
135
  ### Available settings
91
136
 
92
137
  | Setting | Default | Description |
93
138
  |---|---|---|
139
+ | `agent_config_version` | *(empty)* | Exact semver pin of the agent-config release (see above). Empty = unpinned. |
94
140
  | `cost_profile` | `minimal` | Token budget (`minimal`, `balanced`, `full`, `custom`) |
95
141
  | `personal.user_name` | *(empty)* | User's first name for personalized responses |
96
142
  | `personal.minimal_output` | `true` | Suppress intermediate output |
@@ -254,6 +300,76 @@ agents/
254
300
 
255
301
  Module-level documentation goes into `app/Modules/*/agents/`.
256
302
 
303
+ ### `agents/` overlay cascade
304
+
305
+ A subset of `agents/` subdirs participates in the same deepest-wins
306
+ cascade as `.agent-settings.yml` (see *"User-global DX-comfort
307
+ defaults"* above). The cascade is **per-file** by basename — the
308
+ deepest existing `agents/<kind>/<name>.md` wins; the rest are silently
309
+ shadowed.
310
+
311
+ | Subdir | Cascade? | User-global allowed? | Why |
312
+ |---|---|---|---|
313
+ | `agents/overrides/` | ✅ Yes — deepest wins by basename. | ✅ Yes — weakest layer. | Personal developer overrides. |
314
+ | `agents/contexts/` | ✅ Yes — deepest wins by basename. | ❌ No — project-shaped. | Shared knowledge; would leak across projects. |
315
+ | `agents/decisions/` | ✅ Yes — deepest wins by basename. | ❌ No — project-shaped ADRs. | Decisions are repo-bound. |
316
+ | `agents/roadmaps/` | ❌ No — project-rooted only. | ❌ No. | Active delivery plans. |
317
+ | `agents/state/`, `agents/memory/`, `agents/work_engine/`, `agents/.agent-prices.md`, `agents/council-*/` | ❌ No — stateful / session-scoped. | ❌ No. | Per-session state, not shareable. |
318
+
319
+ **User-global asymmetry.** `~/.config/agent-config/agents/overrides/`
320
+ is the only user-global overlay path consulted by the loader. Files
321
+ under `~/.config/agent-config/agents/contexts/` or
322
+ `.../agents/decisions/` are silently skipped — these kinds are
323
+ project-shaped and must not leak across projects.
324
+
325
+ The resolver lives at
326
+ [`scripts/_lib/agents_overlay.py`](../scripts/_lib/agents_overlay.py)
327
+ and is enforced by `scripts/check_overlay_cascade_subdirs.py` — drift
328
+ between the code constants (`CASCADE_ELIGIBLE_KINDS`,
329
+ `USER_GLOBAL_OVERLAY_KINDS`) and the table above breaks the build.
330
+
331
+ ---
332
+
333
+ ## Update check
334
+
335
+ `npx @event4u/agent-config <cmd>` checks the npm registry once per
336
+ 24 h for a newer release of the package and, when one is available,
337
+ writes a two-line banner to **stderr** *after* the subcommand has
338
+ finished. There is no prompt — the user updates when they want with
339
+ `npx @event4u/agent-config update` (Phase 3).
340
+
341
+ ```
342
+ ℹ️ agent-config 1.42.0 available (you have 1.38.0).
343
+ Update: npx @event4u/agent-config update
344
+ ```
345
+
346
+ State is persisted at `~/.config/agent-config/update-check.json`
347
+ (mode `0600`) — sibling of `anthropic.key`, `council-spend.jsonl`.
348
+ The fetch is hard-capped at 1 s and silent on any error.
349
+
350
+ ### Suppression matrix
351
+
352
+ The banner is silently skipped when **any** of the following match:
353
+
354
+ | Condition | Reason |
355
+ |---|---|
356
+ | `CI=1` / `CI=true` / `GITHUB_ACTIONS=true` | CI noise, breaks log scrapers. |
357
+ | `stdout` is not a TTY | Piped / redirected output must stay clean. |
358
+ | `AGENT_CONFIG_NO_UPDATE_CHECK=1` | Per-invocation escape hatch. |
359
+ | `update_check.enabled: false` in `.agent-settings.yml` | Project / user opt-out. |
360
+ | Registry call exceeds 1 s | Network must never delay `npx`. |
361
+ | Registry call raises any exception | Best-effort — failure is silent. |
362
+
363
+ `update_check.enabled` is a **project-scoped** key — it is *not* on
364
+ the user-global whitelist (see [§ Agent Settings](#agent-settings)).
365
+ Each project decides; the user-global file cannot flip it on or off
366
+ for unrelated projects.
367
+
368
+ The decision logic lives at
369
+ [`scripts/_lib/update_check.py`](../scripts/_lib/update_check.py); the
370
+ dispatcher integration lives in [`scripts/agent-config`](../scripts/agent-config)
371
+ (`run_update_check_banner`).
372
+
257
373
  ---
258
374
 
259
375
  ← [Back to README](../README.md)
@@ -518,8 +518,6 @@ Options:
518
518
  --quiet Suppress non-error output
519
519
  --skip-sync Skip payload sync (install.sh)
520
520
  --skip-bridges Skip bridge files (install.py)
521
- --global Ship kernel rules + curated skills to user-scope dirs
522
- --uninstall With --global: remove the event4u/ namespace dir
523
521
  --help, -h Show this help
524
522
  ```
525
523
 
@@ -528,41 +526,16 @@ The underlying stages keep their own CLI surfaces:
528
526
 
529
527
  ---
530
528
 
531
- ## Global user-level install (`--global`)
529
+ ## Global user-level install — retired
532
530
 
533
- `--global` ships a curated subset of kernel rules + top-N skills into
534
- **per-tool user-scope directories**, so the agent has them in every
535
- project on the machine without a per-project install.
536
-
537
- ```bash
538
- # Default: every supported surface, namespaced under event4u/.
539
- bash scripts/install --global
540
-
541
- # Scope to specific surfaces (mirrors the project install --tools flag).
542
- bash scripts/install --global --tools=claude-code,cursor
543
-
544
- # Remove only what we put there — never touches user files.
545
- bash scripts/install --global --uninstall
546
- ```
547
-
548
- | Surface | Target directory |
549
- | ------------- | --------------------------------------------------------------- |
550
- | Claude Code | `~/.claude/rules/event4u/`, `~/.claude/skills/event4u/` |
551
- | Cursor | `~/.cursor/rules/imported/event4u/{rules,skills}/` |
552
- | Windsurf | `~/.codeium/windsurf/global_workflows/event4u/{rules,skills}/` |
553
- | Fallback | `~/.config/agent-config/{rules,skills}/event4u/` |
554
-
555
- The fallback path is always written so an editor we don't yet know
556
- about can still pick the files up.
557
-
558
- **Curation source:** `templates/global-install-manifest.yml`. Edit
559
- post-install to grow or shrink the global set; re-run `--global` to
560
- re-project. `--uninstall` only removes the `event4u/` namespace —
561
- user-added rules / skills under sibling paths stay untouched.
562
-
563
- **When to use:** running multiple unrelated projects where a per-project
564
- install is overkill, or wiring up a new editor (Claude Desktop, Cursor)
565
- that benefits from a baseline set of skills out of the box.
531
+ The previous `--global` symlink scheme (kernel rules + curated skills
532
+ copied into `~/.claude/`, `~/.cursor/`, `~/.codeium/windsurf/`, and
533
+ `~/.config/agent-config/` under an `event4u/` namespace) has been
534
+ **retired** under the npx-only distribution model. Run
535
+ `npx @event4u/agent-config init` per project instead; the
536
+ `agent_config_version` pin in `.agent-settings.yml` keeps every
537
+ invocation reproducible. See [`migration/v1-to-v2.md`](migration/v1-to-v2.md)
538
+ for the upgrade path.
566
539
 
567
540
  ---
568
541