@event4u/agent-config 2.18.0 → 2.19.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.
@@ -0,0 +1,128 @@
1
+ ---
2
+ stability: stable
3
+ ---
4
+
5
+ # ADR — MCP server runtime: Anthropic `mcp` Python SDK
6
+
7
+ > **Status:** Decided · 2026-05-10 (recorded 2026-05-15).
8
+ > **Context:**
9
+ > [`mcp-phase-1-scope.md`](mcp-phase-1-scope.md),
10
+ > [`mcp-cloud-scope.md`](mcp-cloud-scope.md).
11
+
12
+ ## Decision
13
+
14
+ The MCP server at `scripts/mcp_server/` runs on **Python 3.11+** using the
15
+ official Anthropic **`mcp` Python SDK** (PyPI; pinned to `mcp==1.27.1`
16
+ per [`scripts/mcp_server/requirements.txt`](../../scripts/mcp_server/requirements.txt)).
17
+ **FastMCP** (the higher-level decorator wrapper) and the **MCP TypeScript SDK**
18
+ are explicitly rejected for this surface.
19
+
20
+ The hosted Cloudflare Worker bridge (`workers/mcp/`) is the only place a
21
+ non-Python runtime is allowed, and it stays bound to the same wire contract
22
+ (see [`mcp-cloud-scope.md`](mcp-cloud-scope.md)).
23
+
24
+ ## Why this was a real question
25
+
26
+ The package already ships Python under `scripts/` (work engine, AI Council,
27
+ skill linter, install driver helpers) and ships zero Node-runtime code paths
28
+ outside the npx dispatcher. Picking a runtime for the MCP server had three
29
+ candidates that all could have shipped:
30
+
31
+ 1. **MCP Python SDK** (low-level `Server` + `stdio_server` handlers).
32
+ 2. **FastMCP** (higher-level Pythonic decorators built on the same SDK).
33
+ 3. **MCP TypeScript SDK** (Node runtime, separate package).
34
+
35
+ Without an ADR, this choice would have stayed implicit in the code and
36
+ re-litigated every time a contributor read `scripts/mcp_server/server.py`.
37
+
38
+ ## Why MCP Python SDK (low-level) wins
39
+
40
+ | Criterion | MCP Python SDK | FastMCP | MCP TypeScript SDK |
41
+ |---|---|---|---|
42
+ | Runtime already in repo | ✅ Python is the `scripts/` runtime | ✅ Same | ❌ Adds Node-runtime path for one server |
43
+ | A0 safety boundary fit (read-only `prompts/list`, `prompts/get`, narrow `tools/*` allowlist) | ✅ Direct handler control matches the [Phase 1 scope contract](mcp-phase-1-scope.md) | ⚠️ Decorator sugar can obscure the unimplemented-tool guard | ✅ Possible but duplicates Python helpers |
44
+ | Import-surface guard (`tests/test_mcp_server.py` asserts no `subprocess`, `os.system`, `os.popen`, no HTTP client in `scripts.mcp_server.prompts/tools`) | ✅ Trivial to enforce — one module set to audit | ⚠️ FastMCP pulls in extra deps that widen the audit surface | ❌ Would need a TS-side equivalent |
45
+ | Reuse of existing project helpers (`scripts/skill_linter.py`, `scripts/chat_history.py`) | ✅ Direct in-process call | ✅ Same | ❌ Cross-runtime IPC or duplicated logic |
46
+ | Pin / supply-chain footprint | One pin (`mcp==1.27.1`) + `PyYAML` | Adds FastMCP version coupling on top | Node toolchain (`npm`, `tsc`, `dist/`) |
47
+ | Smoke-test path | `task mcp:setup && task mcp:run` (already shipped) | Would re-wrap the same SDK | Separate test runner |
48
+
49
+ Evidence the decision is already realised in code:
50
+
51
+ - [`scripts/mcp_server/server.py`](../../scripts/mcp_server/server.py) — uses
52
+ `mcp.server.Server`, `mcp.server.stdio.stdio_server`, `InitializationOptions`
53
+ directly (no FastMCP decorators).
54
+ - [`scripts/mcp_server/__init__.py`](../../scripts/mcp_server/__init__.py) —
55
+ pins `__version__` and declares stability/contract pointer.
56
+ - [`scripts/mcp_server/requirements.txt`](../../scripts/mcp_server/requirements.txt)
57
+ — `mcp==1.27.1`, no FastMCP, no Node tooling.
58
+ - [`scripts/mcp_setup.sh`](../../scripts/mcp_setup.sh) — onboarding writes
59
+ the Claude Desktop config snippet against `python -m scripts.mcp_server`.
60
+
61
+ ## Tool surface (Phase 1 scoping)
62
+
63
+ Locked separately by [`mcp-phase-1-scope.md`](mcp-phase-1-scope.md) Phase 4
64
+ amendment. The current ALLOWLIST is exactly two tools, registered as a
65
+ hardcoded module-level tuple in
66
+ [`scripts/mcp_server/tools.py`](../../scripts/mcp_server/tools.py):
67
+
68
+ | Tool | Mode | Source |
69
+ |---|---|---|
70
+ | `lint_skills` | read-only | wraps `scripts.skill_linter.lint_file` |
71
+ | `chat_history_append` | path-scoped write | wraps `scripts.chat_history.append`; writes restricted to `agents/.agent-chat-history` or `.agent-chat-history` under the consumer root |
72
+
73
+ No `push`, `merge`, `commit`, or prod-write surface is exposed. The
74
+ unimplemented-tool envelope from
75
+ [`mcp-tool-stub-envelope.md`](mcp-tool-stub-envelope.md) governs the rest of
76
+ the [`consumer_tool_catalog.json`](../../scripts/mcp_server/consumer_tool_catalog.json)
77
+ entries.
78
+
79
+ `agent-config init`, `agent-config skills list`, and
80
+ `agent-config council estimate` (the speculative tool surface in the
81
+ step-14 stub) are **not** exposed today. They stay terminal-gated because
82
+ their natural shape is a stateful CLI. The AI Council (claude-sonnet-4-5 +
83
+ gpt-4o, 2026-05-10, 2 rounds, $0.06) converged on a hardcoded module-level
84
+ ALLOWLIST with mandatory path-scoping for any write tool, and locked the
85
+ rule that engine-state-bearing operations stay off the MCP wire until a
86
+ real consumer ask justifies amending the A0 boundary.
87
+
88
+ ## Install surface
89
+
90
+ The step-14 stub speculated about an `agent-config install --mcp` flag.
91
+ That shape was rejected in favour of two existing entrypoints, both
92
+ already shipped:
93
+
94
+ - **One-liner onboarding:** `task mcp:setup` runs
95
+ [`scripts/mcp_setup.sh`](../../scripts/mcp_setup.sh) — creates
96
+ `.venv-mcp/`, installs `mcp`, and prints the Claude Desktop JSON snippet
97
+ the operator pastes into
98
+ `~/Library/Application Support/Claude/claude_desktop_config.json`
99
+ (with the per-OS variants documented in
100
+ [`docs/mcp-server.md`](../mcp-server.md)).
101
+ - **Config writer:** `./agent-config mcp:render --claude-desktop` writes
102
+ the user-scope Claude Desktop config directly.
103
+
104
+ Writing the global Claude Desktop config from the npx dispatcher without
105
+ an operator pasting JSON is **not** part of this contract — Claude Desktop
106
+ restarts and config-merge semantics make silent rewrites a footgun. The
107
+ copy-paste path stays the canonical install shape until non-dev recruit
108
+ evidence under `agents/eval-findings/` demonstrates the manual step is the
109
+ actual adoption blocker.
110
+
111
+ ## Consequences
112
+
113
+ - Adding a third tool to the MCP server is a code-review event against
114
+ `ALLOWLIST` in `scripts/mcp_server/tools.py`. No settings flag, no env
115
+ var, no dynamic registration — see
116
+ [`mcp-phase-1-scope.md`](mcp-phase-1-scope.md) Phase 4 amendment.
117
+ - Picking up a future protocol break (MCP SDK 2.x) is one pin bump in
118
+ `scripts/mcp_server/requirements.txt`, gated on the 12 import-surface +
119
+ behaviour tests in `tests/test_mcp_server.py` staying green.
120
+ - Re-opening FastMCP or the TypeScript SDK requires a new ADR that
121
+ supersedes this one with evidence (Python SDK shipping a deprecation
122
+ or FastMCP closing the safety-audit gap on `tools/*`).
123
+
124
+ ## See also
125
+
126
+ - [`mcp-phase-1-scope.md`](mcp-phase-1-scope.md) — Phase 1–6 hard contract.
127
+ - [`mcp-cloud-scope.md`](mcp-cloud-scope.md) — hosted Worker bridge scope.
128
+ - [`mcp-tool-stub-envelope.md`](mcp-tool-stub-envelope.md) — unimplemented-tool wire shape.
@@ -0,0 +1,127 @@
1
+ ---
2
+ stability: beta
3
+ keep-beta-until: 2026-08-14
4
+ ---
5
+
6
+ # ADR — Runtime user-types axis (review lens, parallel to personas)
7
+
8
+ > **Status:** Decided · 2026-05-15
9
+ > **Source:** user-authored brief (no council session — direct user spec)
10
+ > **Sibling axis (distinct layer):** [`adr-install-user-type-axis`](adr-install-user-type-axis.md) — install-time `personal.user_type` filter; same vocabulary, different layer
11
+
12
+ ## Context
13
+
14
+ The persona axis (`personas/`) was overloaded with two semantics:
15
+
16
+ 1. **Methodology lenses** — `qa`, `senior-engineer`, `critical-challenger`,
17
+ `developer`, `product-owner`. These voices answer: *how* we review.
18
+ 2. **End-user simulations** — proposals like `galabau-field-crew`,
19
+ `truck-driver`, `metalworking-shop`. These voices answer: *who*
20
+ experiences the software.
21
+
22
+ Mixing the two collapses the taxonomy. A `qa` reviewer applies QA
23
+ methodology regardless of which end-user the software serves. A
24
+ `galabau-field-crew` is not a review methodology — it is the end-user
25
+ viewpoint a methodology reviewer should adopt while reviewing.
26
+
27
+ The composition the system needs is orthogonal:
28
+
29
+ ```
30
+ /refine-ticket --personas=qa --user-type=truck-driver PROJ-123
31
+ ```
32
+
33
+ QA methodology applied through a truck-driver end-user lens. Two
34
+ axes, one orthogonal product.
35
+
36
+ ## Decision
37
+
38
+ Split into a parallel axis. Add `.agent-src.uncompressed/user-types/`
39
+ as a first-class directory mirroring the persona pipeline:
40
+
41
+ - Source dir: `.agent-src.uncompressed/user-types/`
42
+ - Schema doc: [`user-type-schema`](user-type-schema.md) — 7-section spine, ≤ 120 lines
43
+ - JSON schema: [`scripts/schemas/user-type.schema.json`](../../scripts/schemas/user-type.schema.json)
44
+ - Linter: `scripts/skill_linter.py § lint_usertype`
45
+ - CLI surface: `/refine-ticket --user-type=<id>` (single id in v1)
46
+ - Composition: `--user-type=` and `--personas=` compose orthogonally
47
+
48
+ Persona surface is **byte-identical** after this work. No persona
49
+ moves, no schema change to `persona-schema.md` or `persona.schema.json`,
50
+ no behaviour change to `--personas=`. The three seed user-types
51
+ (`galabau-field-crew`, `metalworking-shop`, `truck-driver`) are born
52
+ as user-types — existing personas stay as personas.
53
+
54
+ ## Consequences
55
+
56
+ **Additive surface:**
57
+
58
+ - One new CLI flag (`--user-type=`)
59
+ - One new schema doc + JSON schema
60
+ - One new linter hook (`lint_usertype` + classifier branch)
61
+ - One new directory (`user-types/`) projected the same way `personas/`
62
+ is
63
+ - Three seed files at merge time; consumer projects add their own
64
+ domain-specific user-types under `.agent-src/user-types/`
65
+
66
+ **Locked v1 boundaries:**
67
+
68
+ - CLI-only. Skills do NOT declare a default `user-types:` frontmatter
69
+ key in v1. Migration path to v2: if usage patterns show > 3 skills
70
+ citing the same user-type default, add the key and the audit script
71
+ to mirror `recommended_for_user_types` discipline (smaller surface
72
+ now, additive later).
73
+ - Single user-type per invocation (`--user-type=<id>`, not a list).
74
+ Multi-user-type composition deferred to v2 with a one-line note —
75
+ it requires synthesis logic that does not exist yet and would block
76
+ v1 on a non-load-bearing nice-to-have.
77
+ - **Review lens only.** User-types never provide trade execution
78
+ instructions. Guardrails encoded in every file's `Anti-Patterns`
79
+ section per [`user-type-schema § 5`](user-type-schema.md#-5--guardrails-encoded-in-every-anti-patterns-block).
80
+ - **Anti-Generic Quality Bar.** Every user-type encodes ≥ 5 concrete,
81
+ domain-specific review points and ≥ 3 Unique Questions no other
82
+ persona asks verbatim. Generic prose is rejected at lint or review
83
+ time.
84
+
85
+ ## Alternatives considered
86
+
87
+ **Alt-1 — Extend persona schema with a `subtype: end-user` discriminator.**
88
+ Rejected. Same physical file, two semantics, two enforcement paths
89
+ inside one linter hook. Scales worse: every persona-consuming surface
90
+ (`--personas=`, `lint_persona`, `audit_persona_coverage.py`) would
91
+ need a branch on `subtype` to know whether the artefact is a
92
+ methodology lens or an end-user lens. The clean axis split is a
93
+ single fork-point at the classifier; the subtype fork-point recurs at
94
+ every consumption site.
95
+
96
+ **Alt-2 — Reuse the existing `user-types/` (install-time) directory
97
+ for runtime lenses.** Rejected. The install-time axis stores YAML
98
+ configs filtering *which skills load*; the runtime axis stores
99
+ Markdown lenses filtering *whose viewpoint a review adopts*. Same
100
+ vocabulary, completely different content shape (YAML key-value vs.
101
+ Markdown prose + frontmatter), completely different consumer
102
+ (`scripts/install.sh` vs. `refine-ticket`). Co-locating them would
103
+ force a single `kind:` discriminator on a directory whose two halves
104
+ do not share a schema. The separation is in different physical paths
105
+ (`user-types/` root vs. `.agent-src.uncompressed/user-types/`) and
106
+ the vocabulary overlap is deliberate per [`adr-install-user-type-axis`](adr-install-user-type-axis.md).
107
+
108
+ **Alt-3 — Defer the axis until end-user lenses prove themselves in
109
+ the field.** Rejected. The methodology / end-user overload is already
110
+ producing taxonomy drift in the persona README (review-lens vs
111
+ end-user examples mixing). Splitting now is cheaper than splitting
112
+ after three more end-user "personas" land.
113
+
114
+ ## Migration
115
+
116
+ No migration. v1 ships with three seed user-types born under the new
117
+ axis. Existing personas (`qa`, `developer`, `senior-engineer`,
118
+ `product-owner`, `stakeholder`, `critical-challenger`, `ai-agent`,
119
+ plus specialists) stay put. The `personas/README.md` gains one
120
+ cross-link sentence pointing readers at the parallel axis when their
121
+ intent is end-user simulation rather than methodology review.
122
+
123
+ ## See also
124
+
125
+ - [`user-type-schema`](user-type-schema.md) — locked shape, 7-section spine, size budget, quality bar
126
+ - [`persona-schema`](persona-schema.md) — sister axis (untouched by this ADR)
127
+ - [`adr-install-user-type-axis`](adr-install-user-type-axis.md) — install-time `personal.user_type` filter (distinct layer)
@@ -0,0 +1,146 @@
1
+ ---
2
+ stability: beta
3
+ keep-beta-until: 2026-08-14
4
+ ---
5
+
6
+ # User-type Schema — runtime review-lens axis
7
+
8
+ > **Status:** active · **Stability:** beta · **Owner:** step-6-user-types-axis
9
+ > · **Linter:** `scripts/skill_linter.py § lint_usertype`
10
+ > · **Source-of-truth dir:** `.agent-src.uncompressed/user-types/`
11
+ > · **Sibling axis (distinct):** install-time `user-types/` (package root) — see [`adr-install-user-type-axis`](adr-install-user-type-axis.md)
12
+ > · **ADR:** [`adr-user-types-axis`](adr-user-types-axis.md)
13
+
14
+ Locks the canonical user-type shape. A user-type is a **runtime review
15
+ lens** simulating a real end-user of the software under review (a
16
+ galabau field crew, a metalworking shop, a truck driver). It is the
17
+ twin of `personas/` along a different axis: persona = *how* we review
18
+ (methodology — qa, senior-engineer); user-type = *who* we simulate
19
+ (end-user — domain workflow + operational reality).
20
+
21
+ ## § 1 — Frontmatter
22
+
23
+ | Key | Type | Required | Notes |
24
+ |---|---|---|---|
25
+ | `id` | string | yes | lowercase-hyphenated, must match filename stem |
26
+ | `kind` | const `user-type` | yes | discriminator — locks this file as a review-lens user-type, separates it from the install-time user-type-axis YAMLs |
27
+ | `description` | string | yes | one sentence, ≤ 160 chars (linter cap matches persona) |
28
+ | `version` | string | yes | semver; bump on breaking changes |
29
+ | `source` | enum | yes | `package` \| `project` — project-specific is the typical case (consumer-domain end-users) |
30
+
31
+ `user-types:` is NOT a skill-frontmatter key in v1. The axis is
32
+ CLI-only (`/refine-ticket --user-type=<id>`). Skill-level defaults are
33
+ deferred to v2 — see [`adr-user-types-axis`](adr-user-types-axis.md).
34
+
35
+ ## § 2 — Required section spine (locked)
36
+
37
+ User-types share the spine across the axis — no Core/Specialist split,
38
+ no tier enum. Every user-type carries all seven sections:
39
+
40
+ 1. **Focus** — one paragraph. Who this lens is, the operational
41
+ context they work in, and what no other lens catches. End with one
42
+ sentence pinning the boundary: review-lens only, never operational
43
+ instruction source.
44
+ 2. **Daily Workflow** — concrete day-shape, not generic prose. What
45
+ they do at 06:00, 10:00, 15:00; what they look at, what they touch,
46
+ what they wait for.
47
+ 3. **Vocabulary** — domain terms the software must use (or must NOT
48
+ substitute). Bilingual where the trade is bilingual. Plain-language
49
+ over engineer-language where the user is non-technical.
50
+ 4. **Operational Constraints** — mobile / offline / gloves / noise /
51
+ PPE / time pressure / connectivity / lighting / dead-zones /
52
+ hours-of-service / break-windows / shop-floor vs office split.
53
+ Each constraint is a UI / flow signal, not generic empathy.
54
+ 5. **Unique Questions** — ≥ 3 questions no persona asks verbatim.
55
+ Each must be falsifiable against the ticket under review. (Linter
56
+ warns < 3, matches persona heuristic.)
57
+ 6. **Ticket Red Flags** — what this lens would flag as missing or
58
+ unrealistic when reviewing a ticket. Bullet list, each item names a
59
+ concrete signal a generic reviewer would miss.
60
+ 7. **Anti-Patterns** — what this lens must refuse to do. Guardrails
61
+ are non-negotiable here: **review-only, never operational
62
+ instruction**. No trade execution (welding procedure, electrical
63
+ work, structural advice). No dangerous how-to. No medical / legal
64
+ / engineering advice. Generic prose ("consider usability") is
65
+ itself an anti-pattern.
66
+
67
+ `Composes well with` is permitted as an optional eighth section
68
+ (advisory pairings with personas), not budget-counted.
69
+
70
+ ## § 3 — Size budget
71
+
72
+ | Section count | Line cap | Rationale |
73
+ |---|---|---|
74
+ | 7 | ≤ 120 | Matches the persona core budget. Spine is wider than a
75
+ core persona (7 vs 5 sections) but narrower than a wing-3/4 specialist
76
+ (no Critical Rules + Workflows blocks). 120 is the larger of the two
77
+ candidate caps and the persona core uses it for a 5-section spine —
78
+ the extra two sections need the headroom. |
79
+
80
+ Enforced by `lint-skills` against the full file including frontmatter
81
+ and trailing blank line.
82
+
83
+ ## § 4 — Anti-Generic Quality Bar (merge gate)
84
+
85
+ Every user-type must encode **≥ 5 concrete, domain-specific review
86
+ points** across `Daily Workflow`, `Vocabulary`, `Operational
87
+ Constraints`, and `Ticket Red Flags`. Generic prose is REJECTED at
88
+ lint or review time:
89
+
90
+ - ❌ "consider mobile usability" → ✅ "capacitive touch fails with
91
+ wet leather gloves at 4 °C; tap targets ≥ 60 px or voice command"
92
+ - ❌ "think about offline" → ✅ "no signal in cellar yards; queue
93
+ changes locally, conflict-resolve on the morning brief"
94
+ - ❌ "users want reports" → ✅ "end-of-day proof = timestamped photo
95
+ + customer signature + GPS fix; anything less is a billing dispute"
96
+
97
+ The Reviewer test: a generic reviewer persona could not have produced
98
+ the `Unique Questions` or `Ticket Red Flags` of this file. If they
99
+ could, the file is generic.
100
+
101
+ ## § 5 — Guardrails (encoded in every Anti-Patterns block)
102
+
103
+ User-types are review lenses, not operational manuals. Every file's
104
+ `## Anti-Patterns` section MUST explicitly forbid:
105
+
106
+ - Trade-execution instructions (welding procedure, electrical work,
107
+ structural advice, anything that could harm if followed)
108
+ - Dangerous how-to (chemical handling, equipment operation, work-at-
109
+ height procedures)
110
+ - Medical / legal / engineering advice that requires a licensed
111
+ practitioner
112
+
113
+ Allowed and encouraged: workflow realism, ticket gap analysis,
114
+ terminology correction, mobile / offline / safety / approval signals
115
+ as ticket-requirement signals.
116
+
117
+ ## § 6 — Schema enforcement
118
+
119
+ The linter (`scripts/skill_linter.py § lint_usertype`) enforces:
120
+
121
+ - frontmatter shape (table in § 1)
122
+ - `kind` const value
123
+ - required sections per § 2
124
+ - size budget per § 3
125
+ - ≥ 3 bullets in `Unique Questions`
126
+ - `id` matches filename stem
127
+ - description ≤ 160 chars
128
+
129
+ Authors must use the template at
130
+ `.agent-src.uncompressed/user-types/_template/user-type.md`.
131
+
132
+ ## § 7 — Versioning
133
+
134
+ Section rename / add / remove → ADR + linter update + user-type
135
+ migrations in the same PR. Size-cap tightening is breaking when it
136
+ forces existing user-types to lose content; size-cap loosening is
137
+ non-breaking. The `kind` const is locked — renaming requires a major
138
+ version bump and a separate ADR.
139
+
140
+ ## See also
141
+
142
+ - [`persona-schema`](persona-schema.md) — sister axis (methodology vs end-user)
143
+ - [`adr-user-types-axis`](adr-user-types-axis.md) — why the axis split exists
144
+ - [`adr-install-user-type-axis`](adr-install-user-type-axis.md) — the install-time `user_type` axis (distinct layer, same vocabulary)
145
+ - `.agent-src.uncompressed/user-types/README.md` — authoring entry point
146
+ - `.agent-src.uncompressed/user-types/_template/user-type.md` — template starter
@@ -0,0 +1,81 @@
1
+ # Recruit — `<short-handle>`
2
+
3
+ > **step-13 Phase 1 intake template.** One file per recruit. Filename:
4
+ > `<YYYY-MM-DD>-<source>-<handle>.md` (e.g. `2026-05-20-indiehackers-jsmith.md`).
5
+ > The maintainer fills this in **before** the recruit's first session; the
6
+ > session-log section gets appended live during the walkthrough.
7
+
8
+ ## Identity & source
9
+
10
+ - **Handle / display name:** `<as-they-want-to-be-credited>`
11
+ - **Source channel:** (indie-hackers | r/ContentWritingJobs | product-hunt | direct-dm | other)
12
+ - **First contact:** YYYY-MM-DD via `<thread-url-or-DM-context>`
13
+ - **Role / self-description:** `<one-line, in their words>`
14
+ - **User-type fit:** (consultant | creator | founder | finance | ops | gtm | other)
15
+ - **Tool host they already use:** (claude-desktop | claude-code | cursor | windsurf | copilot | chatgpt-web | none)
16
+
17
+ ## Consent
18
+
19
+ - **Attribution:** (full-name-OK | first-name-OK | role-only | fully-anonymous)
20
+ - **Screenshot publication:** (yes | yes-with-redaction | no)
21
+ - **Verbatim quote publication:** (yes | yes-with-review | no)
22
+ - **Case-study publication if outcome lands:** (yes | maybe-revisit | no)
23
+ - **Consent record:** `<link-to-dm-or-email-thread-or-signed-form>`
24
+
25
+ > Consent gate per step-13 Phase 1 row 3 — a recruit without an explicit
26
+ > consent record cannot anchor `agents/eval-findings/`. Anonymised
27
+ > finding is acceptable; missing consent record is not.
28
+
29
+ ## Pre-session readiness
30
+
31
+ - [ ] Recruit has Claude Desktop installed *(or equivalent MCP host)*
32
+ - [ ] Recruit has a real task ready to bring to the session
33
+ - [ ] Maintainer has [`docs/mcp-server.md`](../mcp-server.md) open in another tab
34
+ - [ ] Maintainer has stopwatch / screen-recording ready for the install-time
35
+ measurement (Phase 1 row 2 — `< 10 minutes` gate)
36
+
37
+ ## Session log
38
+
39
+ Append-only during the walkthrough. Timestamp every observation;
40
+ "silent" minutes are also a signal.
41
+
42
+ ```
43
+ HH:MM start — recruit shares screen, opens Claude Desktop
44
+ HH:MM install step 1 (`task mcp:setup`): outcome / friction / quote
45
+ HH:MM install step 2 (Claude Desktop config paste): outcome / friction / quote
46
+ HH:MM first invocation attempt: prompt / which skill fired / verdict
47
+ ...
48
+ HH:MM end — total install time: __ min __ s
49
+ ```
50
+
51
+ ## Friction inventory
52
+
53
+ At least **two** real friction points (per step-12 Phase 7 L130 spirit).
54
+ Empty list = the session wasn't real.
55
+
56
+ 1. <where they stalled, in their words> → <what we fixed / parked>
57
+ 2. <...>
58
+
59
+ ## Outcome verdict
60
+
61
+ - **MCP setup time:** `__ minutes __ seconds` (gate: `< 10 min`)
62
+ - **First useful invocation reached without terminal?:** (yes | no | partial)
63
+ - **Recruit would recommend to a peer?:** (yes | maybe | no | declined-to-answer)
64
+ - **Eligible to anchor `agents/eval-findings/`?:** (yes | no — reason)
65
+
66
+ ## Cross-links
67
+
68
+ - **Eval-finding file (if logged):** `agents/eval-findings/YYYY-MM-DD-<slug>.md`
69
+ - **Case-study file (if subject opted in):** `docs/case-studies/YYYY-MM-DD-<type>-<slug>.md`
70
+ - **Roadmap rows closed by this session:**
71
+ - step-13 P1 row 1 (recruit) — yes / no
72
+ - step-13 P1 row 2 (`< 10 min` gate) — yes / no
73
+ - step-13 P1 row 3 (consent record) — yes / no
74
+
75
+ ## Maintainer follow-up
76
+
77
+ - [ ] Eval-finding committed
78
+ - [ ] Recruit thanked + sent the published finding link
79
+ - [ ] Skill-description deltas filed *(any friction that maps to a
80
+ skill description widening or a missing trigger phrase)*
81
+ - [ ] Case-study slot offered *(if outcome was useful + consent allows)*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@event4u/agent-config",
3
- "version": "2.18.0",
3
+ "version": "2.19.0",
4
4
  "description": "Shared agent configuration \u2014 skills, rules, commands, guidelines, and templates for AI coding tools",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -346,6 +346,7 @@ TOOL_DIRS = {
346
346
  SKILLS_SOURCE = PROJECT_ROOT / ".agent-src" / "skills"
347
347
  COMMANDS_SOURCE = PROJECT_ROOT / ".agent-src" / "commands"
348
348
  PERSONAS_SOURCE = PROJECT_ROOT / ".agent-src" / "personas"
349
+ USER_TYPES_SOURCE = PROJECT_ROOT / ".agent-src" / "user-types"
349
350
  CLAUDE_SKILLS_DIR = PROJECT_ROOT / ".claude" / "skills"
350
351
 
351
352
  PERSONA_TOOL_DIRS = {
@@ -353,6 +354,11 @@ PERSONA_TOOL_DIRS = {
353
354
  ".cursor/personas": "../../.agent-src/personas",
354
355
  }
355
356
 
357
+ USER_TYPE_TOOL_DIRS = {
358
+ ".claude/user-types": "../../.agent-src/user-types",
359
+ ".cursor/user-types": "../../.agent-src/user-types",
360
+ }
361
+
356
362
  # Map tool-projection directories to the canonical tool ID used by
357
363
  # `.agent-tools.yml`. Directories not in this map are always emitted.
358
364
  _DIR_TOOL_ID = {
@@ -361,6 +367,8 @@ _DIR_TOOL_ID = {
361
367
  ".clinerules": "cline",
362
368
  ".claude/personas": "claude-code",
363
369
  ".cursor/personas": "cursor",
370
+ ".claude/user-types": "claude-code",
371
+ ".cursor/user-types": "cursor",
364
372
  }
365
373
 
366
374
 
@@ -901,6 +909,43 @@ def generate_persona_symlinks() -> int:
901
909
  return total
902
910
 
903
911
 
912
+ def generate_user_type_symlinks() -> int:
913
+ """Create symlink directories for user-types (.claude/user-types/, .cursor/user-types/).
914
+
915
+ Symlinks each user-type .md file from .agent-src/user-types/ into tool-specific
916
+ directories. Excludes README.md and _template/ — those are authoring scaffolding,
917
+ not user-type lenses.
918
+ """
919
+ if not USER_TYPES_SOURCE.exists():
920
+ print(" ⚠️ .agent-src/user-types/ not found — skipping user-types")
921
+ return 0
922
+
923
+ user_types = sorted([
924
+ f.name for f in USER_TYPES_SOURCE.glob("*.md") if f.stem != "README"
925
+ ])
926
+ tool_dirs = _filter_tool_dirs(USER_TYPE_TOOL_DIRS)
927
+ total = 0
928
+ for tool_dir, rel_prefix in tool_dirs.items():
929
+ target_dir = PROJECT_ROOT / tool_dir
930
+ target_dir.mkdir(parents=True, exist_ok=True)
931
+
932
+ # Clean stale symlinks
933
+ for item in target_dir.iterdir():
934
+ if item.is_symlink() and item.name not in user_types and item.name != "README.md":
935
+ item.unlink()
936
+
937
+ for user_type in user_types:
938
+ link = target_dir / user_type
939
+ target = Path(rel_prefix) / user_type
940
+ if link.exists() or link.is_symlink():
941
+ link.unlink()
942
+ link.symlink_to(target)
943
+ total += 1
944
+
945
+ info(f" ✅ Created {total} user-type symlinks across {len(tool_dirs)} tool directories ({len(user_types)} user-types each)")
946
+ return total
947
+
948
+
904
949
  def generate_tools() -> None:
905
950
  """Generate all tool-specific directories and files.
906
951
 
@@ -916,13 +961,14 @@ def generate_tools() -> None:
916
961
  skills = generate_claude_skills() if _tool_active("claude-code") else 0
917
962
  commands = generate_claude_commands() if _tool_active("claude-code") else 0
918
963
  personas = generate_persona_symlinks()
964
+ user_types = generate_user_type_symlinks()
919
965
  cursor_mdc = generate_cursor_mdc_rules() if _tool_active("cursor") else 0
920
966
  windsurf_modern = generate_windsurf_modern_rules() if _tool_active("windsurf") else 0
921
967
  cursor_cmds = generate_cursor_commands() if _tool_active("cursor") else 0
922
968
  windsurf_wf = generate_windsurf_workflows() if _tool_active("windsurf") else 0
923
969
  summary = (
924
970
  f"✅ generate-tools — rules={rules} skills={skills} "
925
- f"commands={commands} personas={personas} "
971
+ f"commands={commands} personas={personas} user_types={user_types} "
926
972
  f"cursor_mdc={cursor_mdc} windsurf_rules={windsurf_modern} "
927
973
  f"cursor_commands={cursor_cmds} windsurf_workflows={windsurf_wf} "
928
974
  f"windsurfrules={windsurfrules}"
@@ -943,7 +989,7 @@ def generate_tools() -> None:
943
989
  # them to symlinks (everything else is always symlinked).
944
990
 
945
991
  # Subdirectories of .agent-src/ that map into .augment/ as symlinks.
946
- AUGMENT_SYMLINK_DIRS = ("skills", "commands", "guidelines", "personas", "templates", "contexts", "scripts")
992
+ AUGMENT_SYMLINK_DIRS = ("skills", "commands", "guidelines", "personas", "user-types", "templates", "contexts", "scripts")
947
993
  # Top-level files to symlink into .augment/ (README, etc.)
948
994
  AUGMENT_SYMLINK_FILES = ("README.md",)
949
995
 
@@ -0,0 +1,35 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://github.com/event4u-app/agent-config/scripts/schemas/user-type.schema.json",
4
+ "title": "User-type frontmatter",
5
+ "$comment": "Source: docs/contracts/user-type-schema.md. The runtime review-lens axis seeded under .agent-src.uncompressed/user-types/. Distinct from the install-time user-type-axis YAMLs in user-types/ (root) which carry their own user-type-axis.schema.json.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["id", "kind", "description", "version", "source"],
9
+ "properties": {
10
+ "id": {
11
+ "type": "string",
12
+ "pattern": "^[a-z][a-z0-9-]*$",
13
+ "description": "Must match the user-type filename stem."
14
+ },
15
+ "kind": {
16
+ "type": "string",
17
+ "const": "user-type",
18
+ "description": "Discriminator — locks this file as a review-lens user-type, distinct from the install-time user-type-axis YAMLs."
19
+ },
20
+ "description": {
21
+ "type": "string",
22
+ "minLength": 1,
23
+ "maxLength": 240
24
+ },
25
+ "version": {
26
+ "type": "string",
27
+ "pattern": "^[0-9]+(\\.[0-9]+){0,2}$"
28
+ },
29
+ "source": {
30
+ "type": "string",
31
+ "enum": ["package", "project"],
32
+ "description": "Project-specific the typical case — most end-user simulations are consumer-domain, not package-defaults."
33
+ }
34
+ }
35
+ }