@event4u/agent-config 2.1.0 → 2.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.
- package/.agent-src/rules/no-cheap-questions.md +11 -2
- package/.agent-src/skills/readme-writing-package/SKILL.md +24 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +48 -0
- package/README.md +71 -6
- package/docs/architecture.md +1 -1
- package/docs/contracts/tier-3-contrib-plugin.md +129 -0
- package/docs/decisions/ADR-007-agent-discovery-scopes.md +278 -0
- package/docs/decisions/ADR-008-installed-tools-manifest.md +160 -0
- package/docs/decisions/INDEX.md +2 -0
- package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +32 -0
- package/docs/guidelines/agent-infra/installed-tools-manifest.md +135 -0
- package/docs/installation.md +59 -3
- package/docs/setup/per-ide/claude-desktop.md +8 -4
- package/package.json +1 -1
- package/scripts/_cli/cmd_export.py +157 -0
- package/scripts/_cli/cmd_sync.py +162 -0
- package/scripts/_cli/cmd_update.py +23 -1
- package/scripts/_cli/cmd_validate.py +164 -0
- package/scripts/_lib/installed_lock.py +160 -0
- package/scripts/_lib/installed_tools.py +237 -0
- package/scripts/agent-config +62 -0
- package/scripts/install +43 -10
- package/scripts/install.py +973 -12
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
adr: 008
|
|
3
|
+
status: proposed
|
|
4
|
+
date: 2026-05-12
|
|
5
|
+
decision: committed-installed-tools-manifest-separate-from-settings
|
|
6
|
+
supersedes: —
|
|
7
|
+
superseded_by: —
|
|
8
|
+
phase: v2.x · post-global-first-install
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# ADR-008 — Installed-Tools Manifest
|
|
12
|
+
|
|
13
|
+
## Status
|
|
14
|
+
|
|
15
|
+
**Proposed** · 2026-05-12 · pending implementation in Phase 3 of
|
|
16
|
+
[`road-to-global-first-install`](../../agents/roadmaps/road-to-global-first-install.md).
|
|
17
|
+
|
|
18
|
+
Originates from user ask (Matze, 2026-05-12): "Sollten wir auf
|
|
19
|
+
Projektebene festhalten, welche Agents wir initialisiert haben, damit
|
|
20
|
+
bei jedem Sync das Verzeichnis aktualisiert werden kann?" Validated
|
|
21
|
+
through AI Council Round 1 (claude-sonnet-4-5 + gpt-4o, $0.0298 actual,
|
|
22
|
+
both converged on "yes, separate file"). Council session:
|
|
23
|
+
[`agents/council-sessions/2026-05-12-project-settings-and-v1-v2/`](../../agents/council-sessions/2026-05-12-project-settings-and-v1-v2/). <!-- council-ref-allowed: ADR decision trace -->
|
|
24
|
+
|
|
25
|
+
## Context
|
|
26
|
+
|
|
27
|
+
After ADR-007 (global-first install), each developer's AI tooling
|
|
28
|
+
lives in user-scope paths (`~/.claude/`, `~/.augment/`, …). A project
|
|
29
|
+
no longer carries the AI config in its tree — except for tools with
|
|
30
|
+
`workspace > global` precedence (Windsurf, Cline, Gemini-when-project-
|
|
31
|
+
wins) that **must** keep a project-local bridge.
|
|
32
|
+
|
|
33
|
+
**Resulting gap:** a project has no committed record of which AI
|
|
34
|
+
tools it expects. A new team member cloning the repo cannot tell
|
|
35
|
+
whether the codebase was built with Claude Code, Windsurf, both, or
|
|
36
|
+
five others. Onboarding is "ask the team lead, hope they remember".
|
|
37
|
+
|
|
38
|
+
**Two related but orthogonal problems:**
|
|
39
|
+
|
|
40
|
+
1. **Bill of materials** — "Which AIs does this project use?"
|
|
41
|
+
2. **Settings hierarchy** — "How do agents behave in this project?"
|
|
42
|
+
|
|
43
|
+
Today, `.agent-project-settings.yml` (committed) answers #2 (personas,
|
|
44
|
+
quality tools, locked keys). #1 is unanswered.
|
|
45
|
+
|
|
46
|
+
### What we considered
|
|
47
|
+
|
|
48
|
+
| Option | Verdict |
|
|
49
|
+
|---|---|
|
|
50
|
+
| **A.** Add `installed_tools` block to `.agent-project-settings.yml` | **Rejected** — mixes behaviour with bill-of-materials, creates a "god file" that every sync command must parse and partially ignore. Settings ≠ manifest. |
|
|
51
|
+
| **B.** Put manifest at root: `.agent-installed-tools.lock` | Rejected — root is already crowded with 10+ AI dotfiles; adding another worsens it. |
|
|
52
|
+
| **C.** Separate manifest at `agents/installed-tools.lock` | **Accepted** — co-located with project-shared agent docs; clear name; clear job. |
|
|
53
|
+
| **D.** Skip — let team docs / README describe the tool set | Rejected — README drifts, no machine-readable contract, no drift detection. |
|
|
54
|
+
|
|
55
|
+
Council (Sonnet): _"Settings (user prefs, locked keys, override paths)
|
|
56
|
+
≠ Manifest (which tools exist). Mixing them creates a god file."_ Both
|
|
57
|
+
members converged on a separate file; the location split (Sonnet
|
|
58
|
+
favoured `installed-tools.lock`, GPT-4o favoured
|
|
59
|
+
`.project-settings.yml`) resolved in favour of Sonnet on the
|
|
60
|
+
separation-of-concerns argument.
|
|
61
|
+
|
|
62
|
+
## Decision
|
|
63
|
+
|
|
64
|
+
**Adopt option C.** Ship `agents/installed-tools.lock` as the
|
|
65
|
+
committed, schema-versioned bill-of-materials for AI tooling.
|
|
66
|
+
|
|
67
|
+
### Schema (v1)
|
|
68
|
+
|
|
69
|
+
```yaml
|
|
70
|
+
schema_version: 1
|
|
71
|
+
agent_config_version: "2.x.y" # version that wrote the file last
|
|
72
|
+
tools:
|
|
73
|
+
- name: claude-code # matches scripts/install.py _VALID_TOOLS
|
|
74
|
+
scope: global # one of: global, project
|
|
75
|
+
bridge_marker: ~/.claude/PROJECT_MANAGED_BY_AGENT_CONFIG
|
|
76
|
+
installed_at: "2026-05-12"
|
|
77
|
+
- name: windsurf
|
|
78
|
+
scope: project # workspace > global → must live in repo
|
|
79
|
+
bridge_marker: .windsurf/PROJECT_MANAGED_BY_AGENT_CONFIG
|
|
80
|
+
installed_at: "2026-05-12"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Fields:**
|
|
84
|
+
|
|
85
|
+
- `schema_version` — integer; bump on breaking schema changes.
|
|
86
|
+
- `agent_config_version` — last package version that wrote the file.
|
|
87
|
+
- `tools[]` — append-on-init order; not alphabetised (preserves
|
|
88
|
+
installation history for forensics).
|
|
89
|
+
- `tools[].name` — must match `_VALID_TOOLS` in `scripts/install.py`.
|
|
90
|
+
- `tools[].scope` — `global` (user-home install) or `project`
|
|
91
|
+
(workspace-wins tools that need a local bridge).
|
|
92
|
+
- `tools[].bridge_marker` — path to the marker file the installer
|
|
93
|
+
drops to claim ownership. `validate` checks this file exists.
|
|
94
|
+
- `tools[].installed_at` — ISO date; informational only.
|
|
95
|
+
|
|
96
|
+
### Lifecycle
|
|
97
|
+
|
|
98
|
+
1. `init --ai <name>` — adds an entry (idempotent). Existing entry
|
|
99
|
+
for same tool with **same scope** is a no-op. Existing entry with
|
|
100
|
+
**different scope** refuses without `--force` (loud warning:
|
|
101
|
+
"tool X is committed as scope=global; you are about to change it
|
|
102
|
+
to project").
|
|
103
|
+
2. `sync` — reads the lock file, replays every listed tool's install
|
|
104
|
+
(skip if marker present, install if missing). Used by new team
|
|
105
|
+
members.
|
|
106
|
+
3. `validate` — read-only drift check. Exit 1 if any listed marker
|
|
107
|
+
is missing or scope mismatches the file system. **No auto-fix.**
|
|
108
|
+
4. Manual edit — discouraged. Lock file is machine-managed; humans
|
|
109
|
+
edit via CLI subcommands.
|
|
110
|
+
|
|
111
|
+
### Relationship to `.agent-project-settings.yml`
|
|
112
|
+
|
|
113
|
+
| File | Owner | Scope | Example keys |
|
|
114
|
+
|---|---|---|---|
|
|
115
|
+
| `agents/installed-tools.lock` | this ADR | bill of materials | `tools[]`, `scope`, `bridge_marker` |
|
|
116
|
+
| `.agent-project-settings.yml` | layered-settings system | behaviour | `personas.default`, `quality.php.tools`, `locked_keys` |
|
|
117
|
+
|
|
118
|
+
Both committed, both have a single job, never overlap.
|
|
119
|
+
|
|
120
|
+
## Consequences
|
|
121
|
+
|
|
122
|
+
### Positive
|
|
123
|
+
|
|
124
|
+
- Onboarding: `git clone` + `npx @event4u/agent-config sync` brings
|
|
125
|
+
every team member's AI tooling to parity.
|
|
126
|
+
- Drift detection: `validate` catches "team lead added Windsurf but
|
|
127
|
+
forgot to commit the lock-file update".
|
|
128
|
+
- Forensics: install order preserved in `tools[]` order; `installed_at`
|
|
129
|
+
pins approximate timestamps.
|
|
130
|
+
- Separation of concerns: behaviour settings stay clean; manifest
|
|
131
|
+
stays focused.
|
|
132
|
+
|
|
133
|
+
### Negative
|
|
134
|
+
|
|
135
|
+
- New committed file = one more thing to keep in sync with reality.
|
|
136
|
+
Mitigated by **machine-written-only** rule and `validate` CI hook.
|
|
137
|
+
- Scope migration (tool moves between `global` and `project`) needs
|
|
138
|
+
documented playbook in `installed-tools-manifest.md` (Phase 3.5).
|
|
139
|
+
- Single-developer projects gain little — the manifest is overhead
|
|
140
|
+
until a second developer joins. Mitigation: file is optional;
|
|
141
|
+
commands work without it (empty manifest = empty install).
|
|
142
|
+
|
|
143
|
+
### Neutral
|
|
144
|
+
|
|
145
|
+
- File lives under `agents/`, not at repo root — consistent with
|
|
146
|
+
Matze's preference and council Sonnet's argument that root is
|
|
147
|
+
already crowded.
|
|
148
|
+
|
|
149
|
+
## Implementation Plan
|
|
150
|
+
|
|
151
|
+
Tracked as Phase 3 of `road-to-global-first-install` (steps 3.1–3.5).
|
|
152
|
+
Ships in a v2.x minor release **after** Phase 2 lands. Out of scope
|
|
153
|
+
for this ADR.
|
|
154
|
+
|
|
155
|
+
## References
|
|
156
|
+
|
|
157
|
+
- [`ADR-007`](ADR-007-agent-discovery-scopes.md) — global-first install (this ADR depends on it).
|
|
158
|
+
- [`agents/roadmaps/road-to-global-first-install.md`](../../agents/roadmaps/road-to-global-first-install.md) Phase 3.
|
|
159
|
+
- [`agents/council-sessions/2026-05-12-project-settings-and-v1-v2/`](../../agents/council-sessions/2026-05-12-project-settings-and-v1-v2/) — full council transcripts. <!-- council-ref-allowed: ADR decision trace -->
|
|
160
|
+
- [`docs/guidelines/agent-infra/layered-settings.md`](../guidelines/agent-infra/layered-settings.md) — the existing 4-layer settings precedence; this ADR adds a parallel file outside that hierarchy.
|
package/docs/decisions/INDEX.md
CHANGED
|
@@ -10,6 +10,8 @@ _Auto-generated by `scripts/adr/regenerate_index.py`. Do not edit._
|
|
|
10
10
|
| [ADR-004](ADR-004-rule-governance-pruning.md) | Rule Governance Pruning | accepted | 2026-05-08 | — |
|
|
11
11
|
| [ADR-005](ADR-005-subagent-worktrees.md) | Subagent Worktrees No Auto Merge | accepted | 2026-05-09 | — |
|
|
12
12
|
| [ADR-006](ADR-006-skill-tools-python-pilot.md) | Skill Tools Python Pilot Pass | accepted | 2026-05-09 | — |
|
|
13
|
+
| [ADR-007](ADR-007-agent-discovery-scopes.md) | Global Default Install With Export Subcommand | accepted | 2026-05-12 | — |
|
|
14
|
+
| [ADR-008](ADR-008-installed-tools-manifest.md) | Committed Installed Tools Manifest Separate From Settings | proposed | 2026-05-12 | — |
|
|
13
15
|
|
|
14
16
|
## Unnumbered (legacy)
|
|
15
17
|
|
|
@@ -106,3 +106,35 @@ correction pattern.
|
|
|
106
106
|
|
|
107
107
|
Acknowledge once, in the user's language, switch behavior, no
|
|
108
108
|
excuses (mirrors `language-and-tone` § slip handling).
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
## No Cheap Questions — Iron Law 3 detail (paternalistic state options)
|
|
112
|
+
|
|
113
|
+
Companion to `no-cheap-questions` § Iron Law 3. The rule states the
|
|
114
|
+
prohibition; this file lists the patterns and the carve-outs.
|
|
115
|
+
|
|
116
|
+
**Forbidden patterns** (non-exhaustive):
|
|
117
|
+
|
|
118
|
+
- "Stop hier — du hast genug für heute"
|
|
119
|
+
- "Take a break and come back fresh"
|
|
120
|
+
- "Weitermachen wenn frisch"
|
|
121
|
+
- "Du wirkst genervt, sollen wir pausieren?"
|
|
122
|
+
- "Sleep on it"
|
|
123
|
+
- "That's a good stopping point" as a numbered option
|
|
124
|
+
- Any option whose recommendation rests on inferred fatigue,
|
|
125
|
+
frustration, or end-of-day mood.
|
|
126
|
+
|
|
127
|
+
**Carve-outs** — allowed because they cite **observable, in-message**
|
|
128
|
+
evidence, not inferred state:
|
|
129
|
+
|
|
130
|
+
- User said "ich bin müde / done for today / let's stop" **this turn**
|
|
131
|
+
→ ack and stop (instruction, not option).
|
|
132
|
+
- Hard Floor confirmation per `non-destructive-by-default` → "confirm
|
|
133
|
+
or abort" is the option, not "rest".
|
|
134
|
+
- Context-window / freshness threshold tripped per `context-hygiene` →
|
|
135
|
+
cite the threshold ("fresh chat at 75%"), do not infer mood.
|
|
136
|
+
|
|
137
|
+
**The rule of thumb**: every numbered option must be a technical /
|
|
138
|
+
scope / sequencing choice with a real trade-off, not a mood-management
|
|
139
|
+
nudge. If the only remaining differentiator is "you might be tired" →
|
|
140
|
+
drop the option, recommend a concrete next step instead.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Installed-Tools Manifest
|
|
2
|
+
|
|
3
|
+
Project-committed bill of materials for AI tooling. Answers the
|
|
4
|
+
question "which AIs does this project use, where do their bridges live,
|
|
5
|
+
and is everyone on the team on the same set?". Canonical schema is
|
|
6
|
+
ADR-008 ([`docs/decisions/ADR-008-installed-tools-manifest.md`](../../decisions/ADR-008-installed-tools-manifest.md)).
|
|
7
|
+
Phase 3 of [`road-to-global-first-install`](../../../agents/roadmaps/road-to-global-first-install.md).
|
|
8
|
+
|
|
9
|
+
This file lives at **`agents/installed-tools.lock`** — committed,
|
|
10
|
+
machine-managed, and orthogonal to `.agent-project-settings.yml`
|
|
11
|
+
(which owns *behaviour*, not *bill of materials*).
|
|
12
|
+
|
|
13
|
+
## Schema (v1)
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
schema_version: 1
|
|
17
|
+
agent_config_version: "2.x.y" # last package version that wrote the file
|
|
18
|
+
tools:
|
|
19
|
+
- name: claude-code # must match _VALID_TOOLS in scripts/install.py
|
|
20
|
+
scope: global # one of: global, project
|
|
21
|
+
bridge_marker: ~/.claude/ # validate checks this path exists
|
|
22
|
+
installed_at: "2026-05-12"
|
|
23
|
+
- name: roocode
|
|
24
|
+
scope: project # workspace-wins → must live in repo
|
|
25
|
+
bridge_marker: .roo/rules/agent-config.md
|
|
26
|
+
installed_at: "2026-05-12"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
| Field | Owner | Notes |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `schema_version` | machine | bumps on breaking schema changes |
|
|
32
|
+
| `agent_config_version` | machine | last writer's package version; `validate` flags drift |
|
|
33
|
+
| `tools[]` | machine | append-on-init order preserved (not alphabetised) |
|
|
34
|
+
| `tools[].name` | machine | one of the 17 valid IDs in `scripts/install.py` |
|
|
35
|
+
| `tools[].scope` | machine | `global` (user-home) or `project` (workspace bridge) |
|
|
36
|
+
| `tools[].bridge_marker` | machine | absolute / `~`-prefixed for global, repo-relative for project |
|
|
37
|
+
| `tools[].installed_at` | machine | ISO date; informational only |
|
|
38
|
+
|
|
39
|
+
The file is **machine-managed**. Hand-editing is discouraged — every
|
|
40
|
+
mutation goes through `init`, `sync`, or `init --force`.
|
|
41
|
+
|
|
42
|
+
## Workflow
|
|
43
|
+
|
|
44
|
+
### Team onboarding (clone → sync → done)
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
git clone <repo>
|
|
48
|
+
cd <repo>
|
|
49
|
+
npx @event4u/agent-config sync
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`sync` reads `agents/installed-tools.lock`, checks every listed tool's
|
|
53
|
+
bridge marker, and replays `install.py --tools=<id>` for each missing
|
|
54
|
+
one. Tools whose marker is already present are skipped — `sync` is
|
|
55
|
+
idempotent and safe to re-run.
|
|
56
|
+
|
|
57
|
+
### Adding a tool
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx @event4u/agent-config init --tools=<id>
|
|
61
|
+
# or --tools=<id1>,<id2>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`init` writes an entry per tool. Existing entry with the same
|
|
65
|
+
`(name, scope)` → no-op. Entry with **different scope** → loud warning
|
|
66
|
+
and refusal until you pass `--force` (see scope migration below).
|
|
67
|
+
|
|
68
|
+
### Drift detection (CI gate)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npx @event4u/agent-config validate
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Read-only. Exit code 1 if any drift is found. Surfaces three drift
|
|
75
|
+
kinds; no auto-fix.
|
|
76
|
+
|
|
77
|
+
| Kind | Trigger | Fix |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| `marker_missing` | recorded `bridge_marker` does not exist | `agent-config sync` |
|
|
80
|
+
| `scope_divergence` | marker only exists at the *other* scope | `agent-config init --tools=<id> --force` |
|
|
81
|
+
| `version_drift` | manifest's `agent_config_version` ≠ installed package | `agent-config update` then `agent-config init --force` |
|
|
82
|
+
|
|
83
|
+
`--skip-version-check` suppresses the third kind for repositories that
|
|
84
|
+
intentionally pin an older version of the manifest.
|
|
85
|
+
|
|
86
|
+
## Scope migration
|
|
87
|
+
|
|
88
|
+
Moving a tool between `project` and `global` is supported but loud:
|
|
89
|
+
|
|
90
|
+
1. Run `init --tools=<id> --scope=<new> --force`. The installer detects
|
|
91
|
+
the conflict, warns, and rewrites the entry only when `--force` is
|
|
92
|
+
present.
|
|
93
|
+
2. The old bridge is **not** removed automatically — clean up the
|
|
94
|
+
leftover marker yourself (`rm .windsurf/agent-config.md` etc.).
|
|
95
|
+
3. `validate` afterwards confirms the new state.
|
|
96
|
+
|
|
97
|
+
Reasoning: scope is a project-wide decision; flipping it silently
|
|
98
|
+
would surprise other team members who never asked for the change. The
|
|
99
|
+
loud refusal forces an explicit `--force` so the diff is reviewable in
|
|
100
|
+
the next commit.
|
|
101
|
+
|
|
102
|
+
## Relationship to other files
|
|
103
|
+
|
|
104
|
+
| File | What it answers | Layer |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| `agents/installed-tools.lock` | **which AIs?** (this guideline) | bill of materials |
|
|
107
|
+
| `.agent-project-settings.yml` | **how do agents behave?** | layered-settings (team file) |
|
|
108
|
+
| `~/.config/agent-config/installed.lock` | **which package version did I install globally?** | per-developer global lockfile (Phase 1) |
|
|
109
|
+
| `.agent-settings.yml` | **what are my personal preferences in this project?** | layered-settings (developer file) |
|
|
110
|
+
|
|
111
|
+
Each file has one job. They never overlap. The two `.lock` files look
|
|
112
|
+
similar by name but answer different questions: `installed.lock` is
|
|
113
|
+
per-developer / cross-project (the package itself), while
|
|
114
|
+
`installed-tools.lock` is per-project / team-shared (which tools are
|
|
115
|
+
expected in *this* repo).
|
|
116
|
+
|
|
117
|
+
## CI integration
|
|
118
|
+
|
|
119
|
+
Recommended gate (GitHub Actions / GitLab CI):
|
|
120
|
+
|
|
121
|
+
```yaml
|
|
122
|
+
- name: Validate installed-tools manifest
|
|
123
|
+
run: npx @event4u/agent-config validate
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Pair it with `agent-config sync` in your dev-setup script so new
|
|
127
|
+
contributors get a working environment without reading the manifest by
|
|
128
|
+
hand.
|
|
129
|
+
|
|
130
|
+
## References
|
|
131
|
+
|
|
132
|
+
- [`ADR-008`](../../decisions/ADR-008-installed-tools-manifest.md) — manifest decision and schema.
|
|
133
|
+
- [`ADR-007`](../../decisions/ADR-007-agent-discovery-scopes.md) — global-first install (prerequisite).
|
|
134
|
+
- [`docs/installation.md`](../../installation.md) — team-onboarding flow.
|
|
135
|
+
- [`layered-settings.md`](layered-settings.md) — parallel settings hierarchy (orthogonal to this manifest).
|
package/docs/installation.md
CHANGED
|
@@ -1,7 +1,32 @@
|
|
|
1
1
|
# Installation
|
|
2
2
|
|
|
3
|
-
**Principle:**
|
|
4
|
-
|
|
3
|
+
**Principle:** Global-first install (cross-project, in `~/.claude/`,
|
|
4
|
+
`~/.cursor/`, …), opt-in project export when a team wants the config
|
|
5
|
+
committed to a repo. No Task, no Make, no build tools required.
|
|
6
|
+
|
|
7
|
+
> **v2.1+** — the installer detects intent. Running `npx
|
|
8
|
+
> @event4u/create-agent-config init` in `~/` or any directory without a
|
|
9
|
+
> project manifest defaults to **global**. Running it inside a project
|
|
10
|
+
> (`package.json` / `composer.json` / `pyproject.toml` / etc.) defaults
|
|
11
|
+
> to **project**. Pass `--scope=global` or `--scope=project` to override
|
|
12
|
+
> detection. See `--scope` in the CLI help for the full matrix.
|
|
13
|
+
|
|
14
|
+
A global install records itself in `~/.config/agent-config/installed.lock`
|
|
15
|
+
(schema_version, agent_config_version, installed_at, tools[]). `npx
|
|
16
|
+
@event4u/create-agent-config update` keeps that manifest in lockstep
|
|
17
|
+
with the project pin in `.agent-settings.yml`. A version-mismatched
|
|
18
|
+
re-run of `init --scope=global` is refused with exit code 1 until you
|
|
19
|
+
`update` or pass `--force`.
|
|
20
|
+
|
|
21
|
+
To commit a specific tool's config into a project repo, use:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
agent-config export --tool=<id> --output=<path>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
(Idempotent; `--force` overrides drift. `--list` enumerates supported
|
|
28
|
+
tool ids. See [`docs/contracts/command-clusters.md`](contracts/command-clusters.md)
|
|
29
|
+
for the export contract.)
|
|
5
30
|
|
|
6
31
|
## Per-IDE setup — quick index
|
|
7
32
|
|
|
@@ -248,6 +273,7 @@ After initial setup, commit these files:
|
|
|
248
273
|
|
|
249
274
|
```
|
|
250
275
|
.agent-settings.yml ← shared profile (e.g., cost_profile: minimal)
|
|
276
|
+
agents/installed-tools.lock ← AI bill of materials (ADR-008, Phase 3)
|
|
251
277
|
.augment/ ← rules, skills, commands (symlinks)
|
|
252
278
|
.cursor/rules/ ← Cursor rules (symlinks)
|
|
253
279
|
.claude/ ← Claude rules, skills (symlinks)
|
|
@@ -255,7 +281,37 @@ AGENTS.md ← Copilot/Gemini instructions
|
|
|
255
281
|
.github/copilot-instructions.md ← GitHub Copilot instructions
|
|
256
282
|
```
|
|
257
283
|
|
|
258
|
-
|
|
284
|
+
`agents/installed-tools.lock` lists every AI tool the project expects,
|
|
285
|
+
its scope (`global` or `project`), and its bridge marker path. Written
|
|
286
|
+
by `init`, replayed by `sync`, checked by `validate`. Schema and
|
|
287
|
+
workflow: [`docs/guidelines/agent-infra/installed-tools-manifest.md`](guidelines/agent-infra/installed-tools-manifest.md).
|
|
288
|
+
|
|
289
|
+
### Team onboarding — clone → sync → done
|
|
290
|
+
|
|
291
|
+
New team members get every AI bridge online with a single command:
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
git clone <repo>
|
|
295
|
+
cd <repo>
|
|
296
|
+
npx @event4u/agent-config sync
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
`sync` reads `agents/installed-tools.lock` and re-runs the installer
|
|
300
|
+
for every tool whose bridge marker is missing locally. Idempotent —
|
|
301
|
+
re-running after every clone is safe. Tools with markers already in
|
|
302
|
+
place are skipped.
|
|
303
|
+
|
|
304
|
+
Pair it with a CI gate to catch drift in PRs:
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
npx @event4u/agent-config validate
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
`validate` is read-only. Exit 1 on any of: marker missing, scope
|
|
311
|
+
divergence (manifest says `project` but marker only exists at the
|
|
312
|
+
global anchor, or vice versa), version drift (manifest's
|
|
313
|
+
`agent_config_version` ≠ installed package). Full drift catalog and
|
|
314
|
+
fix table: [`installed-tools-manifest.md § Drift detection`](guidelines/agent-infra/installed-tools-manifest.md#drift-detection-ci-gate).
|
|
259
315
|
|
|
260
316
|
---
|
|
261
317
|
|
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
The fastest path to running our skills, rules, and (optionally) the MCP
|
|
4
4
|
server inside Claude Desktop. macOS / Windows / Linux. ~5 minutes.
|
|
5
5
|
|
|
6
|
-
> **TL;DR** —
|
|
7
|
-
>
|
|
8
|
-
>
|
|
9
|
-
>
|
|
6
|
+
> **TL;DR** — Claude Desktop reads from `~/.claude/` (global only, no
|
|
7
|
+
> project-local discovery on macOS). Run `npx @event4u/agent-config
|
|
8
|
+
> global --tools=claude-desktop` once per user, or
|
|
9
|
+
> `npx @event4u/agent-config init --tools=claude-code` per project
|
|
10
|
+
> (Claude Code's project install also covers Desktop on macOS via the
|
|
11
|
+
> shared `~/.claude/` location seeded during `init`). The v1 npm /
|
|
12
|
+
> composer install scheme is retired; the new global-first scheme is
|
|
13
|
+
> ADR-007 and writes through `~/.config/agent-config/installed.lock`.
|
|
10
14
|
|
|
11
15
|
## Prerequisites
|
|
12
16
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""``agent-config export`` — eject a tool's canonical content into the project.
|
|
2
|
+
|
|
3
|
+
Phase 1.5 of road-to-global-first-install.md (ADR-007 D3). Replaces the
|
|
4
|
+
rejected symlink-bridge subcommand: writes a real file with the resolved
|
|
5
|
+
content for a named tool into a user-chosen path so it can be committed,
|
|
6
|
+
shared with the team, or customized in place. Idempotent by default;
|
|
7
|
+
``--force`` overrides content drift. No canonical-path defaults.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import hashlib
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Callable, Optional
|
|
16
|
+
|
|
17
|
+
from scripts.install import (
|
|
18
|
+
AIDER_MARKER,
|
|
19
|
+
CLAUDE_DESKTOP_MARKER,
|
|
20
|
+
CODEX_MARKER,
|
|
21
|
+
CONTINUE_MARKER,
|
|
22
|
+
JETBRAINS_MARKER,
|
|
23
|
+
KILOCODE_MARKER,
|
|
24
|
+
KIRO_MARKER,
|
|
25
|
+
ROOCODE_MARKER,
|
|
26
|
+
ZED_MARKER,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
PACKAGE_ROOT = Path(__file__).resolve().parents[2]
|
|
30
|
+
TEMPLATES_DIR = PACKAGE_ROOT / ".agent-src" / "templates"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _from_template(rel: str) -> Callable[[], str]:
|
|
34
|
+
def _read() -> str:
|
|
35
|
+
path = TEMPLATES_DIR / rel
|
|
36
|
+
if not path.is_file():
|
|
37
|
+
raise FileNotFoundError(
|
|
38
|
+
f"template missing from package: {path} "
|
|
39
|
+
f"(reinstall @event4u/agent-config or report a bug)"
|
|
40
|
+
)
|
|
41
|
+
return path.read_text(encoding="utf-8")
|
|
42
|
+
return _read
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _from_constant(value: str) -> Callable[[], str]:
|
|
46
|
+
def _read() -> str:
|
|
47
|
+
return value
|
|
48
|
+
return _read
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# tool_id → (description, content_provider).
|
|
52
|
+
EXPORT_REGISTRY: "dict[str, tuple[str, Callable[[], str]]]" = {
|
|
53
|
+
"roocode": ("Roo Code marker (.roo/rules/agent-config.md body)",
|
|
54
|
+
_from_constant(ROOCODE_MARKER)),
|
|
55
|
+
"claude-desktop": ("Claude Desktop marker (informational, global-scope tool)",
|
|
56
|
+
_from_constant(CLAUDE_DESKTOP_MARKER)),
|
|
57
|
+
"aider": ("Aider marker (manual `read:` wiring documented inline)",
|
|
58
|
+
_from_constant(AIDER_MARKER)),
|
|
59
|
+
"codex": ("Codex CLI marker (informational — AGENTS.md is canonical)",
|
|
60
|
+
_from_constant(CODEX_MARKER)),
|
|
61
|
+
"continue": ("Continue.dev marker (.continue/rules/agent-config.md body)",
|
|
62
|
+
_from_constant(CONTINUE_MARKER)),
|
|
63
|
+
"kilocode": ("Kilo Code marker (.kilocode/rules/agent-config.md body)",
|
|
64
|
+
_from_constant(KILOCODE_MARKER)),
|
|
65
|
+
"zed": ("Zed marker (informational — .rules at repo root is canonical)",
|
|
66
|
+
_from_constant(ZED_MARKER)),
|
|
67
|
+
"jetbrains": ("JetBrains AI Assistant marker (.jetbrains/agent-config.md body)",
|
|
68
|
+
_from_constant(JETBRAINS_MARKER)),
|
|
69
|
+
"kiro": ("Kiro marker (.kiro/steering/agent-config.md body)",
|
|
70
|
+
_from_constant(KIRO_MARKER)),
|
|
71
|
+
"agents-md": ("AGENTS.md template (Thin-Root entry point — consumer scaffold)",
|
|
72
|
+
_from_template("AGENTS.md")),
|
|
73
|
+
"copilot-instructions": ("GitHub Copilot Code Review instructions template",
|
|
74
|
+
_from_template("copilot-instructions.md")),
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _list_tools(out) -> int:
|
|
79
|
+
print("Available tools for `agent-config export --tool <id>`:", file=out)
|
|
80
|
+
width = max(len(t) for t in EXPORT_REGISTRY) + 2
|
|
81
|
+
for tool_id, (desc, _) in sorted(EXPORT_REGISTRY.items()):
|
|
82
|
+
print(f" {tool_id:<{width}}{desc}", file=out)
|
|
83
|
+
return 0
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _hash(content: str) -> str:
|
|
87
|
+
return hashlib.sha256(content.encode("utf-8")).hexdigest()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _rel(path: Path) -> Path:
|
|
91
|
+
try:
|
|
92
|
+
return path.relative_to(Path.cwd())
|
|
93
|
+
except ValueError:
|
|
94
|
+
return path
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _write(output: Path, content: str, *, force: bool, out, err) -> int:
|
|
98
|
+
if output.exists():
|
|
99
|
+
existing = output.read_text(encoding="utf-8")
|
|
100
|
+
if _hash(existing) == _hash(content):
|
|
101
|
+
print(f"ℹ️ {_rel(output)} already exported (content matches).", file=out)
|
|
102
|
+
return 0
|
|
103
|
+
if not force:
|
|
104
|
+
print(
|
|
105
|
+
f"❌ refusing to overwrite {output} — content differs. "
|
|
106
|
+
f"Pass --force to replace.",
|
|
107
|
+
file=err,
|
|
108
|
+
)
|
|
109
|
+
return 1
|
|
110
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
111
|
+
output.write_text(content, encoding="utf-8")
|
|
112
|
+
print(f"✅ exported to {_rel(output)}", file=out)
|
|
113
|
+
return 0
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def main(argv: Optional[list[str]] = None, *, out=sys.stdout, err=sys.stderr) -> int:
|
|
117
|
+
parser = argparse.ArgumentParser(
|
|
118
|
+
prog="agent-config export",
|
|
119
|
+
description="Eject a tool's resolved content into a user-chosen path.",
|
|
120
|
+
)
|
|
121
|
+
parser.add_argument("--tool", metavar="ID",
|
|
122
|
+
help="Tool to export (see --list for the catalog).")
|
|
123
|
+
parser.add_argument("--output", metavar="PATH",
|
|
124
|
+
help="Destination path (relative to CWD).")
|
|
125
|
+
parser.add_argument("--force", action="store_true",
|
|
126
|
+
help="Overwrite an existing file with non-matching content.")
|
|
127
|
+
parser.add_argument("--list", action="store_true",
|
|
128
|
+
help="Print supported tool IDs with descriptions and exit.")
|
|
129
|
+
args = parser.parse_args(argv)
|
|
130
|
+
|
|
131
|
+
if args.list:
|
|
132
|
+
return _list_tools(out)
|
|
133
|
+
if not args.tool:
|
|
134
|
+
print("❌ --tool is required (see --list for the catalog).", file=err)
|
|
135
|
+
return 2
|
|
136
|
+
if not args.output:
|
|
137
|
+
print("❌ --output is required (no canonical-path defaults).", file=err)
|
|
138
|
+
return 2
|
|
139
|
+
|
|
140
|
+
entry = EXPORT_REGISTRY.get(args.tool)
|
|
141
|
+
if entry is None:
|
|
142
|
+
print(f"❌ unknown tool: {args.tool} (see --list)", file=err)
|
|
143
|
+
return 2
|
|
144
|
+
|
|
145
|
+
_, provider = entry
|
|
146
|
+
try:
|
|
147
|
+
content = provider()
|
|
148
|
+
except FileNotFoundError as exc:
|
|
149
|
+
print(f"❌ {exc}", file=err)
|
|
150
|
+
return 1
|
|
151
|
+
|
|
152
|
+
output = Path(args.output).expanduser().resolve()
|
|
153
|
+
return _write(output, content, force=args.force, out=out, err=err)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
if __name__ == "__main__": # pragma: no cover
|
|
157
|
+
sys.exit(main())
|