@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.
- package/.agent-src/commands/refine-ticket.md +3 -0
- package/.agent-src/personas/README.md +8 -0
- package/.agent-src/skills/refine-ticket/SKILL.md +3 -0
- package/.agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/.agent-src/user-types/README.md +124 -0
- package/.agent-src/user-types/_template/user-type.md +95 -0
- package/.agent-src/user-types/galabau-field-crew.md +100 -0
- package/.agent-src/user-types/metalworking-shop.md +105 -0
- package/.agent-src/user-types/truck-driver.md +113 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +39 -0
- package/docs/contracts/adr-mcp-runtime.md +128 -0
- package/docs/contracts/adr-user-types-axis.md +127 -0
- package/docs/contracts/user-type-schema.md +146 -0
- package/docs/recruits/_template.md +81 -0
- package/package.json +1 -1
- package/scripts/compress.py +48 -2
- package/scripts/schemas/user-type.schema.json +35 -0
- package/scripts/skill_linter.py +139 -4
- package/scripts/skill_tools/audit_user_type_coverage.py +148 -0
|
@@ -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
package/scripts/compress.py
CHANGED
|
@@ -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
|
+
}
|