@event4u/agent-config 2.3.0 → 2.4.1
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.
- package/.agent-src/commands/onboard.md +14 -9
- package/.agent-src/skills/ai-council/SKILL.md +5 -3
- package/.agent-src/skills/using-git-worktrees/SKILL.md +1 -1
- package/.agent-src/templates/agents/agent-project-settings.example.yml +4 -3
- package/.agent-src/templates/scripts/work_engine/_lib/agent_settings.py +29 -7
- package/.agent-src/templates/scripts/work_engine/_lib/user_global_paths.py +249 -0
- package/.agent-src/templates/scripts/work_engine/hooks/settings.py +8 -5
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +39 -0
- package/config/agent-settings.template.yml +5 -3
- package/docs/catalog.md +5 -3
- package/docs/contracts/installed-tools-lockfile.md +4 -0
- package/docs/customization.md +23 -17
- package/docs/decisions/ADR-007-agent-discovery-scopes.md +6 -0
- package/docs/decisions/ADR-009-event4u-namespace.md +188 -0
- package/docs/decisions/INDEX.md +1 -0
- package/docs/guidelines/agent-infra/installed-tools-manifest.md +1 -1
- package/docs/guidelines/agent-infra/layered-settings.md +6 -4
- package/docs/installation.md +3 -2
- package/docs/migration/v1-to-v2.md +45 -0
- package/docs/setup/per-ide/claude-desktop.md +107 -65
- package/package.json +1 -1
- package/scripts/_cli/cmd_uninstall.py +17 -7
- package/scripts/_cli/cmd_update.py +11 -7
- package/scripts/_lib/agent_settings.py +29 -7
- package/scripts/_lib/agents_overlay.py +15 -4
- package/scripts/_lib/claude_desktop_bundler.py +150 -0
- package/scripts/_lib/installed_lock.py +56 -4
- package/scripts/_lib/installed_tools.py +1 -1
- package/scripts/_lib/update_check.py +29 -5
- package/scripts/_lib/user_global_paths.py +249 -0
- package/scripts/ai_council/__init__.py +4 -3
- package/scripts/ai_council/budget_guard.py +34 -4
- package/scripts/ai_council/bundler.py +2 -0
- package/scripts/ai_council/clients.py +28 -7
- package/scripts/install.py +149 -49
- package/scripts/install_anthropic_key.sh +5 -3
- package/scripts/install_openai_key.sh +5 -3
- package/scripts/skill_trigger_eval.py +13 -2
|
@@ -40,9 +40,11 @@ One line: one-time setup, six questions, one at a time (iron law from
|
|
|
40
40
|
|
|
41
41
|
### 2. Offer user-global cross-project defaults
|
|
42
42
|
|
|
43
|
-
Detect whether `~/.
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
Detect whether `~/.event4u/agent-config/agent-settings.yml` exists (or
|
|
44
|
+
legacy `~/.config/agent-config/agent-settings.yml`, read as fallback by
|
|
45
|
+
every loader). New path namespaces every package-owned user-global
|
|
46
|
+
artefact under one root — same place `anthropic.key`, `openai.key`,
|
|
47
|
+
`council-spend.jsonl` now live.
|
|
46
48
|
|
|
47
49
|
- **File exists** → skip step entirely. Re-onboarding never overwrites
|
|
48
50
|
user-global file silently.
|
|
@@ -54,7 +56,7 @@ XDG-style, matches existing `~/.config/agent-config/` dir used for
|
|
|
54
56
|
→ empty → first-time setup, ask:
|
|
55
57
|
|
|
56
58
|
```
|
|
57
|
-
> A user-global config at ~/.
|
|
59
|
+
> A user-global config at ~/.event4u/agent-config/agent-settings.yml lets
|
|
58
60
|
> you carry your DX-comfort defaults (name, IDE, autonomy, cost profile,
|
|
59
61
|
> communication style) across every project that uses event4u/agent-config.
|
|
60
62
|
>
|
|
@@ -177,7 +179,7 @@ Skip unless step 2 captured explicit "yes". Re-confirm intent in one line —
|
|
|
177
179
|
never silent-write a file outside project tree:
|
|
178
180
|
|
|
179
181
|
```
|
|
180
|
-
> Writing ~/.
|
|
182
|
+
> Writing ~/.event4u/agent-config/agent-settings.yml with the six
|
|
181
183
|
> mergeable keys mirrored from this project's choices:
|
|
182
184
|
>
|
|
183
185
|
> name: {personal.user_name or ""}
|
|
@@ -191,10 +193,12 @@ never silent-write a file outside project tree:
|
|
|
191
193
|
> 2. Cancel — keep settings project-local only
|
|
192
194
|
```
|
|
193
195
|
|
|
194
|
-
`1` → ensure `~/.
|
|
196
|
+
`1` → ensure `~/.event4u/agent-config/` exists (`mkdir -p`, mode `0700`;
|
|
197
|
+
migration shim in `scripts/install.py` moves any legacy
|
|
198
|
+
`~/.config/agent-config/` files into new namespace on first run),
|
|
195
199
|
then write file with mode `0600`. Schema is **flat-or-nested YAML keyed on
|
|
196
200
|
dotted paths** in whitelist documented in
|
|
197
|
-
[`scripts/_lib/agent_settings.py`](
|
|
201
|
+
[`scripts/_lib/agent_settings.py`](../../scripts/_lib/agent_settings.py).
|
|
198
202
|
Use same section-aware merge rules from
|
|
199
203
|
[`layered-settings`](../docs/guidelines/agent-infra/layered-settings.md#section-aware-merge-rules)
|
|
200
204
|
**only if file unexpectedly already exists** between step 2 and this step
|
|
@@ -263,7 +267,8 @@ Skip this block in cloud surfaces (no settings file, no log path).
|
|
|
263
267
|
`ide`).
|
|
264
268
|
- **User-global file is opt-in, one-shot, never silent.** Step 2 captures
|
|
265
269
|
intent, step 9 re-confirms before actual write. If
|
|
266
|
-
`~/.
|
|
270
|
+
`~/.event4u/agent-config/agent-settings.yml` (or legacy
|
|
271
|
+
`~/.config/agent-config/agent-settings.yml`) already exists when
|
|
267
272
|
`/onboard` starts, step 2 is skipped entirely — re-onboarding never
|
|
268
273
|
silently rewrites developer's cross-project defaults. Use
|
|
269
274
|
`/sync-agent-settings` (project-scoped only) or edit file manually for
|
|
@@ -282,4 +287,4 @@ cloud agent should proceed without invoking it.
|
|
|
282
287
|
- [`set-cost-profile`](set-cost-profile.md) — isolated profile change
|
|
283
288
|
- [`layered-settings`](../docs/guidelines/agent-infra/layered-settings.md) — merge rules for mid-life edits
|
|
284
289
|
- [`agent-settings` template](../templates/agent-settings.md) — settings reference
|
|
285
|
-
- [`scripts/_lib/agent_settings.py`](
|
|
290
|
+
- [`scripts/_lib/agent_settings.py`](../../scripts/_lib/agent_settings.py) — centralized loader + whitelist that consumes the user-global file
|
|
@@ -89,7 +89,7 @@ travel changes.
|
|
|
89
89
|
|
|
90
90
|
| Mode | Client | Billable | Transport | Status |
|
|
91
91
|
|---|---|---|---|---|
|
|
92
|
-
| `api` | `AnthropicClient` / `OpenAIClient` | yes | provider SDK + key from `~/.config/agent-config/<provider>.key` | shipped |
|
|
92
|
+
| `api` | `AnthropicClient` / `OpenAIClient` | yes | provider SDK + key from `~/.event4u/agent-config/<provider>.key` (legacy `~/.config/agent-config/<provider>.key` read as fallback) | shipped |
|
|
93
93
|
| `manual` | `ManualClient` | no | `stdout` (prompt block) + `stdin` (user pastes the web-UI reply, terminated by a line containing only `END`) | shipped (Phase 2b) |
|
|
94
94
|
|
|
95
95
|
Resolution lives in `scripts/ai_council/modes.py`:
|
|
@@ -338,7 +338,8 @@ Real failure modes seen in the wild:
|
|
|
338
338
|
|
|
339
339
|
The bundler's redaction pass strips:
|
|
340
340
|
|
|
341
|
-
- Paths matching `~/.
|
|
341
|
+
- Paths matching `~/.event4u/agent-config/*.key` and the legacy
|
|
342
|
+
`~/.config/agent-config/*.key`.
|
|
342
343
|
- Lines starting with `Authorization:`.
|
|
343
344
|
- `key = …`, `secret = …`, `token = …`, `password = …` assignments.
|
|
344
345
|
- `sk-ant-…` and `sk-…` token-like strings.
|
|
@@ -358,7 +359,8 @@ per-invocation caps from `ai_council.cost_budget`:
|
|
|
358
359
|
- `max_calls` — maximum number of council members per invocation.
|
|
359
360
|
- `daily_limit_usd` — rolling 24h spend cap across all `/council`
|
|
360
361
|
invocations. `0` disables. Persists in
|
|
361
|
-
`~/.
|
|
362
|
+
`~/.event4u/agent-config/council-spend.jsonl` (mode 0600; legacy
|
|
363
|
+
`~/.config/agent-config/council-spend.jsonl` read as fallback). Breach
|
|
362
364
|
fires `on_overrun(event)` with `event.breach_kind == "daily"` and,
|
|
363
365
|
if the callback returns False or is absent, tags the member
|
|
364
366
|
`daily_budget_exceeded` instead of `cost_budget_exceeded`.
|
|
@@ -97,7 +97,7 @@ Ask format:
|
|
|
97
97
|
|
|
98
98
|
> 1. `.worktrees/` — project-local, hidden
|
|
99
99
|
> 2. `worktrees/` — project-local, visible
|
|
100
|
-
> 3. `~/.
|
|
100
|
+
> 3. `~/.event4u/agent-config/worktrees/<project>/` — global
|
|
101
101
|
|
|
102
102
|
**Recommendation: 1 — `.worktrees/`** — project-local keeps the worktree next to the repo (easy cleanup), and the leading dot keeps it out of `ls`. Caveat: pick 3 if multiple repos must share a single worktree root.
|
|
103
103
|
|
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
#
|
|
7
7
|
# Precedence (lowest → highest):
|
|
8
8
|
# 1. Package defaults (shipped by event4u/agent-config)
|
|
9
|
-
# 2. ~/.
|
|
9
|
+
# 2. ~/.event4u/agent-config/agent-settings.yml — user-global DX-comfort
|
|
10
10
|
# defaults (whitelist: name, ide, cost_profile, personal.bot_icon,
|
|
11
11
|
# personal.autonomy, caveman.speak_scope). Created on opt-in via
|
|
12
|
-
# /onboard; project-local files always win.
|
|
12
|
+
# /onboard; project-local files always win. Legacy
|
|
13
|
+
# ~/.config/agent-config/agent-settings.yml is read as a fallback.
|
|
13
14
|
# 3. This file (.agent-project-settings.yml) — team defaults
|
|
14
15
|
# 4. .agent-settings.yml — developer overrides (gitignored)
|
|
15
16
|
#
|
|
@@ -38,7 +39,7 @@ schema_version: 1
|
|
|
38
39
|
# CI guard: a release bump of `package.json` must update this value
|
|
39
40
|
# in lockstep — see scripts/check_template_pin_drift.py (road-to-
|
|
40
41
|
# portable-runtime-and-update-check P3.3).
|
|
41
|
-
agent_config_version: "
|
|
42
|
+
agent_config_version: "2.3.0"
|
|
42
43
|
|
|
43
44
|
# --- Project identity ---
|
|
44
45
|
project:
|
|
@@ -5,11 +5,18 @@ how scripts read agent settings — replaces ~15 ad-hoc loaders in P3.
|
|
|
5
5
|
|
|
6
6
|
Resolution order (deepest wins; user-global is whitelist-filtered only):
|
|
7
7
|
|
|
8
|
-
N. ``~/.
|
|
8
|
+
N. ``~/.event4u/agent-config/agent-settings.yml`` (user-global; whitelist only)
|
|
9
9
|
N-1. ``<repo-root>/.agent-settings.yml`` (project-wide; all keys)
|
|
10
10
|
N-2. ``<intermediate-dir>/.agent-settings.yml`` (subsystem-scoped; all keys)
|
|
11
11
|
1. ``<CWD>/.agent-settings.yml`` (deepest, wins; all keys)
|
|
12
12
|
|
|
13
|
+
The user-global path is resolved via the sibling
|
|
14
|
+
:mod:`work_engine._lib.user_global_paths` module (vendored from
|
|
15
|
+
``scripts/_lib/user_global_paths.py`` so the engine stays self-contained
|
|
16
|
+
when shipped into consumer projects) with a read-fallback to the legacy
|
|
17
|
+
``~/.config/agent-config/agent-settings.yml`` so pre-2.4 installs keep
|
|
18
|
+
working during the namespace migration.
|
|
19
|
+
|
|
13
20
|
``<repo-root>`` is the nearest ancestor that contains ``.git`` (directory
|
|
14
21
|
**or** file — submodule support). The walk stops there — it never drifts
|
|
15
22
|
into a parent repo or ``$HOME``. When ``cwd`` is ``None`` (default), the
|
|
@@ -37,12 +44,26 @@ import logging
|
|
|
37
44
|
from pathlib import Path
|
|
38
45
|
from typing import Any, Iterator
|
|
39
46
|
|
|
47
|
+
from . import user_global_paths
|
|
48
|
+
|
|
40
49
|
logger = logging.getLogger(__name__)
|
|
41
50
|
|
|
42
51
|
DEFAULT_PROJECT_FILE = ".agent-settings.yml"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
USER_GLOBAL_FILENAME = "agent-settings.yml"
|
|
53
|
+
|
|
54
|
+
#: Canonical write target under the new ``~/.event4u/agent-config/``
|
|
55
|
+
#: namespace. Reads route through :func:`_resolve_user_global_file` so
|
|
56
|
+
#: pre-2.4 installs are still picked up from ``~/.config/agent-config/``
|
|
57
|
+
#: until the migration shim copies them across.
|
|
58
|
+
DEFAULT_USER_GLOBAL_FILE = user_global_paths.write_target(USER_GLOBAL_FILENAME)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _resolve_user_global_file() -> Path:
|
|
62
|
+
"""Return the active user-global settings path with legacy fallback."""
|
|
63
|
+
found = user_global_paths.resolve_with_fallback(USER_GLOBAL_FILENAME)
|
|
64
|
+
if found is not None:
|
|
65
|
+
return found
|
|
66
|
+
return DEFAULT_USER_GLOBAL_FILE
|
|
46
67
|
|
|
47
68
|
#: Exact dotted paths allowed to cascade from user-global into the merged
|
|
48
69
|
#: settings. Anything not listed here is silently ignored when present in
|
|
@@ -129,7 +150,8 @@ def load_agent_settings(
|
|
|
129
150
|
|
|
130
151
|
``project_path`` defaults to ``./.agent-settings.yml`` (CWD-relative).
|
|
131
152
|
``user_global_path`` defaults to
|
|
132
|
-
``~/.
|
|
153
|
+
``~/.event4u/agent-config/agent-settings.yml`` (with a read fallback
|
|
154
|
+
to the legacy ``~/.config/agent-config/agent-settings.yml``). Both arguments accept
|
|
133
155
|
``Path`` or ``str``. Pass ``verbose=True`` to log keys present in
|
|
134
156
|
user-global that are not on the whitelist.
|
|
135
157
|
|
|
@@ -143,7 +165,7 @@ def load_agent_settings(
|
|
|
143
165
|
with pre-cascade callers.
|
|
144
166
|
"""
|
|
145
167
|
user_global_raw = _read_yaml(
|
|
146
|
-
Path(user_global_path) if user_global_path else
|
|
168
|
+
Path(user_global_path) if user_global_path else _resolve_user_global_file(),
|
|
147
169
|
) or {}
|
|
148
170
|
|
|
149
171
|
user_global_filtered, ignored = _filter_whitelist(
|
|
@@ -181,7 +203,7 @@ def iter_setting_overrides(
|
|
|
181
203
|
Never blocks, never raises on missing files.
|
|
182
204
|
"""
|
|
183
205
|
user_global_path_resolved = (
|
|
184
|
-
Path(user_global_path) if user_global_path else
|
|
206
|
+
Path(user_global_path) if user_global_path else _resolve_user_global_file()
|
|
185
207
|
)
|
|
186
208
|
user_global_raw = _read_yaml(user_global_path_resolved) or {}
|
|
187
209
|
user_global_filtered, _ = _filter_whitelist(user_global_raw, MERGEABLE_KEYS)
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""Vendor-namespaced user-global path resolution.
|
|
2
|
+
|
|
3
|
+
Phase 1 of road-to-event4u-namespace-and-claude-desktop.md. Single source
|
|
4
|
+
of truth for "where does this package keep user-global state on disk?".
|
|
5
|
+
Replaces hard-coded ``~/.config/agent-config/`` literals scattered across
|
|
6
|
+
``scripts/_lib`` and ``scripts/ai_council``.
|
|
7
|
+
|
|
8
|
+
Resolution order:
|
|
9
|
+
|
|
10
|
+
1. ``$EVENT4U_CONFIG_HOME`` — full path override (testing + power users).
|
|
11
|
+
2. ``~/.event4u/agent-config/`` — vendor-namespaced source-of-truth.
|
|
12
|
+
|
|
13
|
+
For backward compatibility during the transition, ``legacy_xdg_root()``
|
|
14
|
+
exposes the old ``~/.config/agent-config/`` path so loaders can read
|
|
15
|
+
state written by pre-2.4 installs. Writers should never target the
|
|
16
|
+
legacy path; the auto-migration shim (Phase 3) copies state once into
|
|
17
|
+
the new namespace.
|
|
18
|
+
|
|
19
|
+
Contract — pure, read-only, never auto-creates directories.
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
import shutil
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Optional
|
|
27
|
+
|
|
28
|
+
#: Marker suffix for in-progress entry copies during migration. A copy
|
|
29
|
+
#: that crashes mid-flight leaves ``<name><suffix><pid>`` behind so the
|
|
30
|
+
#: next run can clean it up before retrying — instead of treating a
|
|
31
|
+
#: partial subdir as a completed copy.
|
|
32
|
+
_PARTIAL_SUFFIX = ".event4u-partial-"
|
|
33
|
+
|
|
34
|
+
#: Environment variable that overrides ``event4u_root()`` outright.
|
|
35
|
+
#: Accepts a full path (``~`` expanded). Primarily used by tests; power
|
|
36
|
+
#: users may also point this at a custom location.
|
|
37
|
+
EVENT4U_HOME_ENV = "EVENT4U_CONFIG_HOME"
|
|
38
|
+
|
|
39
|
+
#: Vendor-namespaced default. Relative to the user's home directory.
|
|
40
|
+
DEFAULT_EVENT4U_ROOT_RELATIVE = Path(".event4u") / "agent-config"
|
|
41
|
+
|
|
42
|
+
#: Legacy XDG-shaped default written by pre-2.4 installs. Read-only
|
|
43
|
+
#: fallback during the transition; never the target of a write.
|
|
44
|
+
LEGACY_XDG_ROOT_RELATIVE = Path(".config") / "agent-config"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def event4u_root(env: Optional[dict] = None) -> Path:
|
|
48
|
+
"""Return the active user-global root directory.
|
|
49
|
+
|
|
50
|
+
Honours ``EVENT4U_CONFIG_HOME`` first, falls back to
|
|
51
|
+
``~/.event4u/agent-config/``. Never creates the directory.
|
|
52
|
+
"""
|
|
53
|
+
env_map = env if env is not None else os.environ
|
|
54
|
+
override = env_map.get(EVENT4U_HOME_ENV)
|
|
55
|
+
if override:
|
|
56
|
+
return Path(override).expanduser()
|
|
57
|
+
return Path.home() / DEFAULT_EVENT4U_ROOT_RELATIVE
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def legacy_xdg_root() -> Path:
|
|
61
|
+
"""Return the pre-2.4 user-global root at ``~/.config/agent-config/``.
|
|
62
|
+
|
|
63
|
+
Used by loaders during the transition to read settings, lockfiles,
|
|
64
|
+
and keys written before the namespace migration ran. Writers MUST
|
|
65
|
+
NOT target this path — only ``event4u_root()`` is a valid write
|
|
66
|
+
target. Never creates the directory.
|
|
67
|
+
"""
|
|
68
|
+
return Path.home() / LEGACY_XDG_ROOT_RELATIVE
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def resolve_with_fallback(
|
|
72
|
+
relative_name: str,
|
|
73
|
+
*,
|
|
74
|
+
env: Optional[dict] = None,
|
|
75
|
+
) -> Optional[Path]:
|
|
76
|
+
"""Resolve a named file/dir under the user-global root, with legacy fallback.
|
|
77
|
+
|
|
78
|
+
Returns the new-namespace path if it exists on disk, otherwise the
|
|
79
|
+
legacy XDG path if it exists, otherwise ``None``. Callers that need
|
|
80
|
+
the *write target* (regardless of existence) should use
|
|
81
|
+
``event4u_root() / relative_name`` directly.
|
|
82
|
+
|
|
83
|
+
``relative_name`` is a forward-slash separated string (e.g.
|
|
84
|
+
``"installed.lock"`` or ``"agents/global"``). It is treated as a
|
|
85
|
+
path fragment relative to the chosen root; absolute paths are
|
|
86
|
+
rejected with ``ValueError``.
|
|
87
|
+
"""
|
|
88
|
+
fragment = Path(relative_name)
|
|
89
|
+
if fragment.is_absolute():
|
|
90
|
+
raise ValueError(
|
|
91
|
+
f"resolve_with_fallback expects a relative path, got {relative_name!r}"
|
|
92
|
+
)
|
|
93
|
+
new_path = event4u_root(env=env) / fragment
|
|
94
|
+
if new_path.exists():
|
|
95
|
+
return new_path
|
|
96
|
+
legacy_path = legacy_xdg_root() / fragment
|
|
97
|
+
if legacy_path.exists():
|
|
98
|
+
return legacy_path
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def write_target(relative_name: str, *, env: Optional[dict] = None) -> Path:
|
|
103
|
+
"""Return the canonical write target for a named user-global file/dir.
|
|
104
|
+
|
|
105
|
+
Always rooted at ``event4u_root()`` — writers never target the
|
|
106
|
+
legacy XDG path. Caller is responsible for ``mkdir(parents=True)``
|
|
107
|
+
on the parent before writing. Never creates the directory itself.
|
|
108
|
+
"""
|
|
109
|
+
fragment = Path(relative_name)
|
|
110
|
+
if fragment.is_absolute():
|
|
111
|
+
raise ValueError(
|
|
112
|
+
f"write_target expects a relative path, got {relative_name!r}"
|
|
113
|
+
)
|
|
114
|
+
return event4u_root(env=env) / fragment
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
#: Breadcrumb dropped into the legacy root after a successful migration.
|
|
118
|
+
#: Tells the user where their state now lives and how to clean up. The
|
|
119
|
+
#: legacy tree itself is never auto-deleted — only the user does that.
|
|
120
|
+
MIGRATION_BREADCRUMB_NAME = "MIGRATED.md"
|
|
121
|
+
|
|
122
|
+
_BREADCRUMB_TEMPLATE = """# Migrated to `~/.event4u/agent-config/`
|
|
123
|
+
|
|
124
|
+
This directory (`~/.config/agent-config/`) is the **legacy** location
|
|
125
|
+
for `event4u/agent-config` user-global state. As of v2.4 the canonical
|
|
126
|
+
location is `~/.event4u/agent-config/`.
|
|
127
|
+
|
|
128
|
+
The migration shim has already copied your settings, keys, lockfiles,
|
|
129
|
+
and overrides into the new namespace. File modes (0600 on keys) were
|
|
130
|
+
preserved. Loaders prefer the new path but still read from this tree
|
|
131
|
+
as a fallback, so removing it is safe **once you've confirmed** the
|
|
132
|
+
new location is working.
|
|
133
|
+
|
|
134
|
+
## To clean up
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
rm -rf ~/.config/agent-config
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Why the move
|
|
141
|
+
|
|
142
|
+
`~/.config/` is a generic XDG-shaped directory shared by many tools.
|
|
143
|
+
`~/.event4u/agent-config/` is vendor-namespaced and avoids collisions
|
|
144
|
+
with unrelated CLIs. See
|
|
145
|
+
`agents/roadmaps/road-to-event4u-namespace-and-claude-desktop.md` for
|
|
146
|
+
the full rationale.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def migrate_legacy_namespace(
|
|
151
|
+
*,
|
|
152
|
+
env: Optional[dict] = None,
|
|
153
|
+
legacy_root_override: Optional[Path] = None,
|
|
154
|
+
) -> bool:
|
|
155
|
+
"""Copy pre-2.4 user-global state from legacy XDG root into the new namespace.
|
|
156
|
+
|
|
157
|
+
Idempotent and safe to call on every install / init. Returns ``True``
|
|
158
|
+
if a copy ran during this invocation, ``False`` when the migration
|
|
159
|
+
was already complete or there was nothing to migrate.
|
|
160
|
+
|
|
161
|
+
Contract:
|
|
162
|
+
|
|
163
|
+
- Never auto-deletes the legacy tree — that's the user's call (the
|
|
164
|
+
breadcrumb at ``~/.config/agent-config/MIGRATED.md`` documents it).
|
|
165
|
+
- Preserves file modes via ``shutil.copytree(..., copy_function=copy2)``
|
|
166
|
+
so 0600 key files stay 0600 after the copy.
|
|
167
|
+
- If the new root already exists with any content, the migration
|
|
168
|
+
treats it as already-done and only writes the breadcrumb (if
|
|
169
|
+
missing) — never overwrites new-namespace state.
|
|
170
|
+
- If the legacy root is missing or empty, the function is a no-op.
|
|
171
|
+
- Per-entry atomic write: each entry is copied to a sibling
|
|
172
|
+
``<name>.event4u-partial-<pid>`` and then ``os.replace``'d into
|
|
173
|
+
the final name. If a previous run crashed mid-``copytree``, the
|
|
174
|
+
leftover ``*.event4u-partial-*`` siblings are cleaned up at the
|
|
175
|
+
top of the next run before retrying — a partial directory is
|
|
176
|
+
never mistaken for a completed copy.
|
|
177
|
+
|
|
178
|
+
``legacy_root_override`` is for tests; production callers leave it ``None``.
|
|
179
|
+
"""
|
|
180
|
+
legacy_root = (
|
|
181
|
+
legacy_root_override if legacy_root_override is not None else legacy_xdg_root()
|
|
182
|
+
)
|
|
183
|
+
new_root = event4u_root(env=env)
|
|
184
|
+
|
|
185
|
+
if not legacy_root.exists() or not legacy_root.is_dir():
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
# Skip the migrated-breadcrumb itself when checking for content so a
|
|
189
|
+
# second invocation does not loop on its own marker.
|
|
190
|
+
legacy_entries = [
|
|
191
|
+
p for p in legacy_root.iterdir() if p.name != MIGRATION_BREADCRUMB_NAME
|
|
192
|
+
]
|
|
193
|
+
if not legacy_entries:
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
# Real content check ignores partial-copy debris from a prior
|
|
197
|
+
# interrupted run; otherwise the breadcrumb would be written for
|
|
198
|
+
# a half-finished migration and retry would never run.
|
|
199
|
+
new_has_content = new_root.exists() and any(
|
|
200
|
+
not _is_partial_entry(p) for p in new_root.iterdir()
|
|
201
|
+
)
|
|
202
|
+
if new_has_content:
|
|
203
|
+
_ensure_breadcrumb(legacy_root)
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
new_root.mkdir(parents=True, exist_ok=True)
|
|
207
|
+
_purge_partial_entries(new_root)
|
|
208
|
+
|
|
209
|
+
for entry in legacy_entries:
|
|
210
|
+
target = new_root / entry.name
|
|
211
|
+
if target.exists():
|
|
212
|
+
continue
|
|
213
|
+
staging = new_root / f"{entry.name}{_PARTIAL_SUFFIX}{os.getpid()}"
|
|
214
|
+
if staging.exists():
|
|
215
|
+
_remove_path(staging)
|
|
216
|
+
if entry.is_dir():
|
|
217
|
+
shutil.copytree(entry, staging, copy_function=shutil.copy2)
|
|
218
|
+
else:
|
|
219
|
+
shutil.copy2(entry, staging)
|
|
220
|
+
os.replace(staging, target)
|
|
221
|
+
|
|
222
|
+
_ensure_breadcrumb(legacy_root)
|
|
223
|
+
return True
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _is_partial_entry(path: Path) -> bool:
|
|
227
|
+
return _PARTIAL_SUFFIX in path.name
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _purge_partial_entries(new_root: Path) -> None:
|
|
231
|
+
"""Remove ``*.event4u-partial-*`` leftovers from a previous interrupted run."""
|
|
232
|
+
for entry in new_root.iterdir():
|
|
233
|
+
if _is_partial_entry(entry):
|
|
234
|
+
_remove_path(entry)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _remove_path(path: Path) -> None:
|
|
238
|
+
if path.is_dir() and not path.is_symlink():
|
|
239
|
+
shutil.rmtree(path)
|
|
240
|
+
else:
|
|
241
|
+
path.unlink(missing_ok=True)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _ensure_breadcrumb(legacy_root: Path) -> None:
|
|
245
|
+
"""Write the ``MIGRATED.md`` breadcrumb into ``legacy_root`` if absent."""
|
|
246
|
+
breadcrumb = legacy_root / MIGRATION_BREADCRUMB_NAME
|
|
247
|
+
if breadcrumb.exists():
|
|
248
|
+
return
|
|
249
|
+
breadcrumb.write_text(_BREADCRUMB_TEMPLATE, encoding="utf-8")
|
|
@@ -17,8 +17,9 @@ settings.py``):
|
|
|
17
17
|
Per road-to-portable-dev-preferences P3, the YAML read goes through
|
|
18
18
|
:func:`work_engine._lib.agent_settings.load_agent_settings`, which
|
|
19
19
|
cascades the whitelisted ``cost_profile`` (and other DX-comfort keys)
|
|
20
|
-
from ``~/.
|
|
21
|
-
|
|
20
|
+
from ``~/.event4u/agent-config/agent-settings.yml`` (legacy
|
|
21
|
+
``~/.config/agent-config/agent-settings.yml`` read as fallback) when
|
|
22
|
+
the project file omits them. Project values always win.
|
|
22
23
|
"""
|
|
23
24
|
from __future__ import annotations
|
|
24
25
|
|
|
@@ -67,9 +68,11 @@ def load_hook_settings(
|
|
|
67
68
|
``settings_path`` defaults to ``./.agent-settings.yml`` relative to
|
|
68
69
|
the current working directory — same convention as chat-history.
|
|
69
70
|
``user_global_path`` defaults to
|
|
70
|
-
``~/.
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
``~/.event4u/agent-config/agent-settings.yml`` (with a read fallback
|
|
72
|
+
to the legacy ``~/.config/agent-config/agent-settings.yml``) and
|
|
73
|
+
only cascades the whitelisted DX-comfort keys (currently
|
|
74
|
+
``cost_profile``) when the project file omits them. See
|
|
75
|
+
road-to-portable-dev-preferences P3.
|
|
73
76
|
"""
|
|
74
77
|
path = Path(settings_path) if settings_path else Path(DEFAULT_SETTINGS_FILE)
|
|
75
78
|
raw = load_agent_settings(
|
package/CHANGELOG.md
CHANGED
|
@@ -343,6 +343,45 @@ our recommendation order, not its support status.
|
|
|
343
343
|
users" tension without removing any path that an existing user
|
|
344
344
|
might rely on.
|
|
345
345
|
|
|
346
|
+
## [2.4.1](https://github.com/event4u-app/agent-config/compare/2.4.0...2.4.1) (2026-05-13)
|
|
347
|
+
|
|
348
|
+
### Bug Fixes
|
|
349
|
+
|
|
350
|
+
* **install:** write lockfile to canonical event4u namespace ([ca57607](https://github.com/event4u-app/agent-config/commit/ca5760785f150903bcad74e278b59e534f83634d))
|
|
351
|
+
* **install:** source global deploy from .agent-src/ subdirectories ([f151caf](https://github.com/event4u-app/agent-config/commit/f151caf854d9ce8519bc244d441a7c8bf73e7fde))
|
|
352
|
+
|
|
353
|
+
Tests: 3521 (+0 since 2.4.0)
|
|
354
|
+
|
|
355
|
+
## [2.4.0](https://github.com/event4u-app/agent-config/compare/2.3.0...2.4.0) (2026-05-13)
|
|
356
|
+
|
|
357
|
+
### Features
|
|
358
|
+
|
|
359
|
+
* **install:** Claude Desktop ZIP bundle deployment ([9f2a00c](https://github.com/event4u-app/agent-config/commit/9f2a00cfd752232abdcf16c6e3440c46f5c9d596))
|
|
360
|
+
* **install:** event4u namespace and auto-migration shim ([a58570e](https://github.com/event4u-app/agent-config/commit/a58570ecff885a7d50350c4281e675d7ff34f912))
|
|
361
|
+
|
|
362
|
+
### Bug Fixes
|
|
363
|
+
|
|
364
|
+
* **agent_settings:** align source with template via relative import ([a85b6c8](https://github.com/event4u-app/agent-config/commit/a85b6c82284310876d075a924918052fc7dbabdb))
|
|
365
|
+
* **work_engine:** vendor user_global_paths into template for self-contained import ([f111c4e](https://github.com/event4u-app/agent-config/commit/f111c4e60de6cd60fbe81635029f9f34437a5ead))
|
|
366
|
+
* **migrate:** atomic per-entry write with partial-debris purge ([5bd5aad](https://github.com/event4u-app/agent-config/commit/5bd5aadf0085657ec98eee049dda440d05b06801))
|
|
367
|
+
|
|
368
|
+
### Documentation
|
|
369
|
+
|
|
370
|
+
* **roadmap:** mark Steps 5-6 done (commit + PR opened) ([8492b7f](https://github.com/event4u-app/agent-config/commit/8492b7fd3cfd76075a64d5de770db2c5013e02dd))
|
|
371
|
+
* **adr:** ADR-009 rollback section + AI Council convergence ([cc78adc](https://github.com/event4u-app/agent-config/commit/cc78adc0a9d0fbc51b2a65f35d8e0f13c1456a2f))
|
|
372
|
+
* **contracts:** mark installed-tools-lockfile as beta ([90991da](https://github.com/event4u-app/agent-config/commit/90991da2f840ebae978fc4df9996c01324816404))
|
|
373
|
+
* **adr:** ADR-009 event4u namespace + Claude Desktop ZIP bundles ([c707bd3](https://github.com/event4u-app/agent-config/commit/c707bd31211f94ace858738b7aee8d3898f0da88))
|
|
374
|
+
* **roadmap:** add road-to-event4u-namespace-and-claude-desktop ([d92706b](https://github.com/event4u-app/agent-config/commit/d92706bd53621ff3a225db6eb9ee490f17f613d2))
|
|
375
|
+
|
|
376
|
+
### Chores
|
|
377
|
+
|
|
378
|
+
* **onboard:** rephrase 'event4u-owned' as 'package-owned' ([4bdd43b](https://github.com/event4u-app/agent-config/commit/4bdd43b7a6637a36cfa377045b6cd0f3f51b0fd1))
|
|
379
|
+
* **template:** bump agent_config_version pin to 2.3.0 ([1180bf2](https://github.com/event4u-app/agent-config/commit/1180bf263f2f33fb5b41b13439c92cfbdc3a70d8))
|
|
380
|
+
* **index:** regenerate index + catalog for accurate counts ([655a5eb](https://github.com/event4u-app/agent-config/commit/655a5eb81ce6744548a6f50451f66576a7c2b5ac))
|
|
381
|
+
* **sync:** regenerate derived outputs for event4u namespace ([c277a94](https://github.com/event4u-app/agent-config/commit/c277a94f881ad4702ed2e97cebf6c5a7c8471a35))
|
|
382
|
+
|
|
383
|
+
Tests: 3521 (+31 since 2.3.0)
|
|
384
|
+
|
|
346
385
|
## [2.3.0](https://github.com/event4u-app/agent-config/compare/2.2.2...2.3.0) (2026-05-13)
|
|
347
386
|
|
|
348
387
|
### Features
|
|
@@ -237,8 +237,9 @@ worktrees:
|
|
|
237
237
|
# roadmap, diff, prompt, or file set. Council members never see the
|
|
238
238
|
# host agent's reasoning — only the artefact + a neutral system prompt.
|
|
239
239
|
#
|
|
240
|
-
# Tokens are NEVER stored here. They live in ~/.
|
|
241
|
-
# <provider>.key (mode 0600
|
|
240
|
+
# Tokens are NEVER stored here. They live in ~/.event4u/agent-config/
|
|
241
|
+
# <provider>.key (mode 0600; legacy ~/.config/agent-config/<provider>.key
|
|
242
|
+
# is read as a fallback), installed via:
|
|
242
243
|
# bash scripts/install_anthropic_key.sh
|
|
243
244
|
# bash scripts/install_openai_key.sh
|
|
244
245
|
#
|
|
@@ -396,7 +397,8 @@ memory:
|
|
|
396
397
|
# two-line banner to stderr **after** the subcommand finishes; CI,
|
|
397
398
|
# non-TTY stdout, and `AGENT_CONFIG_NO_UPDATE_CHECK=1` always suppress
|
|
398
399
|
# it regardless of this flag. State is persisted at
|
|
399
|
-
# `~/.
|
|
400
|
+
# `~/.event4u/agent-config/update-check.json` (mode 0600; legacy
|
|
401
|
+
# `~/.config/agent-config/update-check.json` read as fallback) with a 24 h
|
|
400
402
|
# cadence. See docs/customization.md § "Update check" for the suppression
|
|
401
403
|
# matrix and the road-to-portable-runtime-and-update-check roadmap (P2).
|
|
402
404
|
update_check:
|
package/docs/catalog.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# agent-config — Public Catalog
|
|
2
2
|
|
|
3
|
-
Consumer-facing catalog of all **
|
|
3
|
+
Consumer-facing catalog of all **408 public artefacts** shipped by
|
|
4
4
|
this package. Internal package-maintenance rules and deprecation shims
|
|
5
5
|
are excluded.
|
|
6
6
|
|
|
@@ -186,7 +186,7 @@ are excluded.
|
|
|
186
186
|
| skill | [`voc-extract`](../.agent-src/skills/voc-extract/SKILL.md) | | Use when extracting Voice-of-Customer themes from existing artefacts — GH issues, PR threads, Sentry patterns. Triggers on 'what are users saying', 'recurring complaints', 'top themes'. |
|
|
187
187
|
| skill | [`websocket`](../.agent-src/skills/websocket/SKILL.md) | | Use when building real-time features — WebSocket broadcasting, live updates, presence channels, connection state — even when the user just says 'push this to the client live'. |
|
|
188
188
|
|
|
189
|
-
## Rules (
|
|
189
|
+
## Rules (58)
|
|
190
190
|
|
|
191
191
|
| kind | name | type | description |
|
|
192
192
|
|---|---|---|---|
|
|
@@ -210,6 +210,7 @@ are excluded.
|
|
|
210
210
|
| rule | [`domain-adoption-policy`](../.agent-src/rules/domain-adoption-policy.md) | auto | Adopting a new domain track (mobile, ML, blockchain, IoT, gaming) — gates import on demand, ownership, CI fit, Sunset compatibility BEFORE harvest |
|
|
211
211
|
| rule | [`downstream-changes`](../.agent-src/rules/downstream-changes.md) | auto | After EVERY code edit, find ALL downstream changes needed to existing files, including callers, tests, imports, types, and documentation |
|
|
212
212
|
| rule | [`e2e-testing`](../.agent-src/rules/e2e-testing.md) | auto | Playwright E2E tests — locators, assertions, Page Objects, fixtures, CI, and flaky test prevention |
|
|
213
|
+
| rule | [`external-reference-deep-dive`](../.agent-src/rules/external-reference-deep-dive.md) | auto | When the user names an external repo, file, URL, or artifact as a reference — fetch the actual tree and inspect, never summarize from README or metadata |
|
|
213
214
|
| rule | [`guidelines`](../.agent-src/rules/guidelines.md) | manual | Writing or reviewing code — check relevant guideline before writing or reviewing code |
|
|
214
215
|
| rule | [`improve-before-implement`](../.agent-src/rules/improve-before-implement.md) | auto | Before implementing features or architectural changes — validate the request against existing code, challenge weak requirements, suggest improvements |
|
|
215
216
|
| rule | [`invite-challenge`](../.agent-src/rules/invite-challenge.md) | auto | Before executing a complex plan or non-trivial design — ask 'am I solving the right problem?' and pause for user confirmation, even when no ambiguity |
|
|
@@ -359,7 +360,7 @@ are excluded.
|
|
|
359
360
|
| command | [`upstream-contribute`](../.agent-src/commands/upstream-contribute.md) | | Contribute a learning, skill, rule, or fix from a consumer project back to the shared agent-config package |
|
|
360
361
|
| command | [`work`](../.agent-src/commands/work.md) | | Drive a free-form prompt end-to-end through refine → score → plan → implement → test → verify → report — Option-A loop over the `work_engine` Python engine, confidence-band gated, no auto-git. |
|
|
361
362
|
|
|
362
|
-
## Guidelines (
|
|
363
|
+
## Guidelines (70)
|
|
363
364
|
|
|
364
365
|
| kind | name | category | description |
|
|
365
366
|
|---|---|---|---|
|
|
@@ -374,6 +375,7 @@ are excluded.
|
|
|
374
375
|
| guideline | [`direct-answers-demos`](../docs/guidelines/agent-infra/direct-answers-demos.md) | agent-infra | |
|
|
375
376
|
| guideline | [`engineering-memory-data-format`](../docs/guidelines/agent-infra/engineering-memory-data-format.md) | agent-infra | |
|
|
376
377
|
| guideline | [`first-principles`](../docs/guidelines/agent-infra/first-principles.md) | agent-infra | |
|
|
378
|
+
| guideline | [`installed-tools-manifest`](../docs/guidelines/agent-infra/installed-tools-manifest.md) | agent-infra | |
|
|
377
379
|
| guideline | [`inversion-thinking`](../docs/guidelines/agent-infra/inversion-thinking.md) | agent-infra | |
|
|
378
380
|
| guideline | [`ios-simulator-guide`](../docs/guidelines/agent-infra/ios-simulator-guide.md) | agent-infra | |
|
|
379
381
|
| guideline | [`language-and-tone-examples`](../docs/guidelines/agent-infra/language-and-tone-examples.md) | agent-infra | |
|