@event4u/agent-config 1.34.0 → 1.35.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/roadmap/process-full.md +17 -15
- package/.agent-src/contexts/execution/roadmap-process-loop.md +11 -10
- package/.agent-src/personas/discovery-lead.md +99 -0
- package/.agent-src/personas/product-owner.md +71 -52
- package/.agent-src/personas/revops-maintainer.md +100 -0
- package/.agent-src/personas/tech-writer.md +99 -0
- package/.agent-src/skills/competitive-positioning/SKILL.md +152 -0
- package/.agent-src/skills/customer-research/SKILL.md +116 -0
- package/.agent-src/skills/decision-record/SKILL.md +78 -3
- package/.agent-src/skills/discovery-interview/SKILL.md +152 -0
- package/.agent-src/skills/launch-readiness/SKILL.md +156 -0
- package/.agent-src/skills/release-comms/SKILL.md +123 -0
- package/.agent-src/skills/roadmap-writing/SKILL.md +1 -1
- package/.agent-src/skills/stakeholder-tradeoff/SKILL.md +91 -3
- package/.agent-src/skills/voc-extract/SKILL.md +164 -0
- package/.agent-src/templates/roadmaps.md +9 -0
- package/.claude-plugin/marketplace.json +7 -1
- package/CHANGELOG.md +34 -0
- package/README.md +2 -2
- package/docs/architecture.md +2 -2
- package/docs/catalog.md +6 -3
- package/docs/contracts/context-spine.md +133 -0
- package/docs/contracts/file-ownership-matrix.json +110 -0
- package/docs/contracts/mental-models.md +336 -0
- package/docs/guidelines/cross-role-handoff.md +127 -0
- package/package.json +1 -1
- package/scripts/lint_context_spine_usage.py +133 -0
- package/scripts/lint_roadmap_complexity.py +37 -0
- package/scripts/schemas/skill.schema.json +9 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Cross-Role Handoff
|
|
2
|
+
|
|
3
|
+
Wing-specific prose for senior-tier skills handing off across role
|
|
4
|
+
boundaries. The mechanical contract — initiator → delegated(input) →
|
|
5
|
+
output, lint rules, worktree boundary — lives in
|
|
6
|
+
[`docs/contracts/cross-wing-handoff.md`](../contracts/cross-wing-handoff.md).
|
|
7
|
+
This guideline covers **when a role hands off to another role**,
|
|
8
|
+
**how to phrase the routing**, and the **L4 / C8 boundary**.
|
|
9
|
+
|
|
10
|
+
## Wings at a glance
|
|
11
|
+
|
|
12
|
+
The senior catalog spans four wings. Each wing owns a cognition
|
|
13
|
+
cluster and emits artifacts the other wings can consume.
|
|
14
|
+
|
|
15
|
+
| Wing | Cluster | Senior skills (anchor examples) |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| **1. Engineering** | Code, architecture, debugging, review | `architecture-review-lens`, `bug-analyzer`, `judge-bug-hunter`, `blast-radius-analyzer` |
|
|
18
|
+
| **2. Product + Foundation** | Discovery, refinement, decisions | `po-discovery`, `refine-ticket`, `decision-record`, `rice-prioritization` |
|
|
19
|
+
| **3. GTM + Growth** | Customers, comms, funnel, channels | `customer-research` (L1), `release-comms` (L2), `funnel-analysis` |
|
|
20
|
+
| **4. Money + Strategy + Ops** | Unit economics, OKRs, capacity, risk | `unit-economics-modeling`, `okr-tree-modeling`, `dcf-modeling`, `risk-officer` |
|
|
21
|
+
|
|
22
|
+
A handoff is **cross-role** when the initiator and the delegate live
|
|
23
|
+
in different wings (or in different cognition clusters within the
|
|
24
|
+
same wing). Same-cluster delegation is normal composition and does
|
|
25
|
+
not need this guideline.
|
|
26
|
+
|
|
27
|
+
## When to hand off
|
|
28
|
+
|
|
29
|
+
A senior skill SHOULD hand off — not absorb — when any of the four
|
|
30
|
+
fires:
|
|
31
|
+
|
|
32
|
+
1. **Different cognition cluster.** The downstream step needs a
|
|
33
|
+
different mode of thinking (numbers vs. narrative; user vs.
|
|
34
|
+
system; risk vs. design). Absorbing it dilutes the skill.
|
|
35
|
+
2. **Different artifact owner.** The output naturally lives under a
|
|
36
|
+
different role's catalog (e.g. `forecast-band.json` belongs to
|
|
37
|
+
Wing-4, not Wing-2).
|
|
38
|
+
3. **Tier-mismatch risk.** Inlining the step would require
|
|
39
|
+
downgrading to a non-senior delegate; the cross-wing-handoff
|
|
40
|
+
linter blocks tier mismatches.
|
|
41
|
+
4. **Re-use evidence.** The step is already cited by ≥ 2 other
|
|
42
|
+
senior skills; absorbing it duplicates cognition.
|
|
43
|
+
|
|
44
|
+
If none fire, keep the step inline. Cross-role plumbing is not free.
|
|
45
|
+
|
|
46
|
+
## How to phrase the handoff
|
|
47
|
+
|
|
48
|
+
Two surfaces in the senior-skill template carry routing:
|
|
49
|
+
|
|
50
|
+
- **`## Related Skills` § *WHEN NOT to use this*** — the routing
|
|
51
|
+
list. One bullet per peer that owns the cognition the user might
|
|
52
|
+
expect from this skill but is wrong to ask here. Format:
|
|
53
|
+
*"X is the actual question — route to [`<peer>`](../<peer>/SKILL.md)"*.
|
|
54
|
+
- **`## Procedure` Composes line** — when the skill **does** call
|
|
55
|
+
another skill mid-procedure, declare it on a line beginning with
|
|
56
|
+
`Composes [`<peer>`](...)` so the linter can match the call site
|
|
57
|
+
to the delegate's `## Input` block.
|
|
58
|
+
|
|
59
|
+
The delegated skill's `## Input` block names the fields the initiator
|
|
60
|
+
must pass. Drift between the two is the failure the contract catches.
|
|
61
|
+
|
|
62
|
+
## Decision tree
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
Need a downstream cognition step?
|
|
66
|
+
├── Same wing + same cluster? → keep inline (normal composition).
|
|
67
|
+
├── Different cluster, but no shipped senior peer?
|
|
68
|
+
│ └── Implement inline; flag for next plate's audit (the cluster
|
|
69
|
+
│ might need its own senior).
|
|
70
|
+
├── Different cluster + shipped senior peer + ≥ 2 reuse citations?
|
|
71
|
+
│ └── HAND OFF: declare in WHEN NOT block + Composes line.
|
|
72
|
+
└── Cross-wing chain (≥ 3 senior steps, ≥ 30 min each)?
|
|
73
|
+
└── Use `subagent-orchestration` mode 6 (worktrees) per
|
|
74
|
+
cross-wing-handoff.md § 3.
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## L4 / C8 composition boundary
|
|
78
|
+
|
|
79
|
+
Council Q3 (2026-05-05) locks the disambiguation between L4
|
|
80
|
+
`stakeholder-tradeoff` and the sibling C8 `code-review-multi-lens`:
|
|
81
|
+
|
|
82
|
+
- **L4 fires** when a request crosses two stakeholder lenses
|
|
83
|
+
(engineering ↔ PO, PO ↔ ops, ops ↔ infra) and the trade-off is
|
|
84
|
+
**not yet code**. Output is a trade-off matrix + recommendation +
|
|
85
|
+
dissent log; the artifact is consumable by a roadmap or PR
|
|
86
|
+
description, not by a diff.
|
|
87
|
+
- **C8 fires** when the request **is already code** — PR open, draft
|
|
88
|
+
branch under review, or a diff supplied as input. Output is a
|
|
89
|
+
multi-lens code review (security · architecture · tests · quality)
|
|
90
|
+
bound to file:line spans.
|
|
91
|
+
- **C8 → L4 escalation.** A C8 verdict that surfaces a stakeholder
|
|
92
|
+
conflict — e.g. test-coverage judge fails but PO insists on
|
|
93
|
+
shipping — becomes **input to L4**. The escalation is one-way:
|
|
94
|
+
L4 produces the dissent log that decides whether C8's verdict
|
|
95
|
+
is overridden, with the override recorded in
|
|
96
|
+
[`decision-record`](../../.agent-src.uncompressed/skills/decision-record/SKILL.md).
|
|
97
|
+
|
|
98
|
+
The boundary keeps the two skills sharp — neither absorbs the other —
|
|
99
|
+
and gives the agent a deterministic rule for which one to load when
|
|
100
|
+
both look applicable.
|
|
101
|
+
|
|
102
|
+
## Worked example
|
|
103
|
+
|
|
104
|
+
A PO refining a ticket (Wing-2) hits a sentence like *"the cheapest
|
|
105
|
+
acquisition channel is paid search, but only if CAC payback < 6 months"*:
|
|
106
|
+
|
|
107
|
+
1. Wing-2 is the initiator (`refine-ticket`).
|
|
108
|
+
2. The CAC question is Wing-4 cognition — `unit-economics-modeling`.
|
|
109
|
+
3. `refine-ticket` hands off via:
|
|
110
|
+
- WHEN NOT entry: *"CAC / payback questions — route to
|
|
111
|
+
[`unit-economics-modeling`](../unit-economics-modeling/SKILL.md)"*.
|
|
112
|
+
- Composes line in the procedure step that needs the answer.
|
|
113
|
+
4. The delegate's `## Input` block lists the fields (channel,
|
|
114
|
+
cohort, time horizon); `refine-ticket` passes them.
|
|
115
|
+
5. The output (`payback-band.md`) feeds the AC of the original ticket.
|
|
116
|
+
|
|
117
|
+
No cluster collision, no tier mismatch, no untyped drift.
|
|
118
|
+
|
|
119
|
+
## See also
|
|
120
|
+
|
|
121
|
+
- [`docs/contracts/cross-wing-handoff.md`](../contracts/cross-wing-handoff.md)
|
|
122
|
+
— the mechanical contract this guideline cites.
|
|
123
|
+
- [`docs/contracts/context-spine.md`](../contracts/context-spine.md)
|
|
124
|
+
— orthogonal context-slot mechanism, often used together with a
|
|
125
|
+
handoff (e.g. delegate reads `team` slot the initiator opted in to).
|
|
126
|
+
- `.agent-src.uncompressed/skills/subagent-orchestration/SKILL.md` § mode 6
|
|
127
|
+
— when the chain runs in fresh worktrees.
|
package/package.json
CHANGED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Context-spine usage linter.
|
|
3
|
+
|
|
4
|
+
Closes the lint gap left after `scripts/schemas/skill.schema.json`
|
|
5
|
+
gained the `context_spine` enum: a skill can declare
|
|
6
|
+
`context_spine: [product]` in frontmatter without ever citing the
|
|
7
|
+
slot in its body, and the schema check will not catch it.
|
|
8
|
+
|
|
9
|
+
This linter enforces the author checklist in
|
|
10
|
+
`docs/contracts/context-spine.md` § 6: for every slot declared in
|
|
11
|
+
frontmatter, the skill body MUST cite the slot at least once.
|
|
12
|
+
A citation is any of these tokens:
|
|
13
|
+
|
|
14
|
+
- the literal path `agents/context-spine/<slot>.md`
|
|
15
|
+
- the slot name in bold: ``**<slot>**``
|
|
16
|
+
- the slot name in inline code: `` `<slot>` ``
|
|
17
|
+
|
|
18
|
+
Cap: ≤ 150 LOC, stdlib only. Hooked into `task ci` via
|
|
19
|
+
`task lint-context-spine-usage`.
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import re
|
|
24
|
+
import sys
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
QUIET = "--quiet" in sys.argv
|
|
28
|
+
|
|
29
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
30
|
+
SKILL_GLOBS = (
|
|
31
|
+
".agent-src.uncompressed/skills/**/SKILL.md",
|
|
32
|
+
".agent-src/skills/**/SKILL.md",
|
|
33
|
+
)
|
|
34
|
+
VALID_SLOTS = ("product", "team", "repo")
|
|
35
|
+
|
|
36
|
+
CONTEXT_SPINE_PAT = re.compile(
|
|
37
|
+
r"^context_spine:\s*\[([^\]]*)\]\s*$", re.MULTILINE
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _frontmatter_and_body(text: str) -> tuple[str, str]:
|
|
42
|
+
if not text.startswith("---\n"):
|
|
43
|
+
return "", text
|
|
44
|
+
end = text.find("\n---\n", 4)
|
|
45
|
+
if end == -1:
|
|
46
|
+
return "", text
|
|
47
|
+
return text[4:end], text[end + 5 :]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _read_spine(fm: str) -> list[str] | None:
|
|
51
|
+
m = CONTEXT_SPINE_PAT.search(fm)
|
|
52
|
+
if m is None:
|
|
53
|
+
return None
|
|
54
|
+
raw = m.group(1).strip()
|
|
55
|
+
if not raw:
|
|
56
|
+
return []
|
|
57
|
+
return [s.strip().strip("'\"") for s in raw.split(",") if s.strip()]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _slot_cited(body: str, slot: str) -> bool:
|
|
61
|
+
"""A slot is cited if any of three forms appears in the body."""
|
|
62
|
+
forms = (
|
|
63
|
+
f"agents/context-spine/{slot}.md",
|
|
64
|
+
f"**{slot}**",
|
|
65
|
+
f"`{slot}`",
|
|
66
|
+
)
|
|
67
|
+
return any(form in body for form in forms)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def lint_skill(path: Path) -> list[str]:
|
|
71
|
+
text = path.read_text(encoding="utf-8")
|
|
72
|
+
fm, body = _frontmatter_and_body(text)
|
|
73
|
+
if not fm:
|
|
74
|
+
return []
|
|
75
|
+
slots = _read_spine(fm)
|
|
76
|
+
if slots is None:
|
|
77
|
+
return []
|
|
78
|
+
problems: list[str] = []
|
|
79
|
+
for slot in slots:
|
|
80
|
+
if slot not in VALID_SLOTS:
|
|
81
|
+
problems.append(
|
|
82
|
+
f"unknown_context_spine_slot: '{slot}' "
|
|
83
|
+
f"(valid: {', '.join(VALID_SLOTS)})"
|
|
84
|
+
)
|
|
85
|
+
continue
|
|
86
|
+
if not _slot_cited(body, slot):
|
|
87
|
+
problems.append(
|
|
88
|
+
f"declared context_spine slot '{slot}' is never cited "
|
|
89
|
+
f"in the skill body — add `**{slot}**`, `` `{slot}` ``, "
|
|
90
|
+
f"or a link to `agents/context-spine/{slot}.md` "
|
|
91
|
+
f"(see docs/contracts/context-spine.md § 6)"
|
|
92
|
+
)
|
|
93
|
+
return problems
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def main() -> int:
|
|
97
|
+
skills: list[Path] = []
|
|
98
|
+
for pattern in SKILL_GLOBS:
|
|
99
|
+
skills.extend(sorted(REPO_ROOT.glob(pattern)))
|
|
100
|
+
if not skills:
|
|
101
|
+
print("❌ no SKILL.md files matched", file=sys.stderr)
|
|
102
|
+
return 1
|
|
103
|
+
failed = 0
|
|
104
|
+
declared = 0
|
|
105
|
+
for skill in skills:
|
|
106
|
+
rel = skill.relative_to(REPO_ROOT)
|
|
107
|
+
problems = lint_skill(skill)
|
|
108
|
+
text = skill.read_text(encoding="utf-8")
|
|
109
|
+
fm, _ = _frontmatter_and_body(text)
|
|
110
|
+
if fm and CONTEXT_SPINE_PAT.search(fm):
|
|
111
|
+
declared += 1
|
|
112
|
+
if problems:
|
|
113
|
+
failed += 1
|
|
114
|
+
print(f"❌ {rel}", file=sys.stderr)
|
|
115
|
+
for p in problems:
|
|
116
|
+
print(f" - {p}", file=sys.stderr)
|
|
117
|
+
if failed:
|
|
118
|
+
print(
|
|
119
|
+
f"\n❌ {failed} skill(s) failed context-spine usage lint "
|
|
120
|
+
f"({declared} skill(s) declare a spine)",
|
|
121
|
+
file=sys.stderr,
|
|
122
|
+
)
|
|
123
|
+
return 1
|
|
124
|
+
if not QUIET:
|
|
125
|
+
print(
|
|
126
|
+
f"✅ {declared} skill(s) declare context_spine; "
|
|
127
|
+
f"all declared slots are cited in the body"
|
|
128
|
+
)
|
|
129
|
+
return 0
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
if __name__ == "__main__":
|
|
133
|
+
sys.exit(main())
|
|
@@ -35,6 +35,29 @@ COMPLEXITY_PAT = re.compile(
|
|
|
35
35
|
r"^complexity:\s*(lightweight|structural)\s*$", re.MULTILINE
|
|
36
36
|
)
|
|
37
37
|
|
|
38
|
+
# Plate / horizon detection — template rule 16 forbids time-boxed plates
|
|
39
|
+
# in roadmaps. Patterns match the authoring devices we are retiring.
|
|
40
|
+
PLATE_PATS: tuple[tuple[re.Pattern[str], str], ...] = (
|
|
41
|
+
(re.compile(r"^##\s+Horizon\b", re.MULTILINE | re.IGNORECASE),
|
|
42
|
+
"'## Horizon' section header"),
|
|
43
|
+
(re.compile(r"\b\d+-week\s+(visible\s+)?plate\b", re.IGNORECASE),
|
|
44
|
+
"'N-week (visible) plate' phrasing"),
|
|
45
|
+
(re.compile(r"\bvisible\s+plate\b", re.IGNORECASE),
|
|
46
|
+
"'visible plate' phrasing"),
|
|
47
|
+
(re.compile(r"\b(in|out)-of-plate\b", re.IGNORECASE),
|
|
48
|
+
"'in-of-plate' / 'out-of-plate' marker"),
|
|
49
|
+
(re.compile(r"\bout-of-horizon\b", re.IGNORECASE),
|
|
50
|
+
"'out-of-horizon' marker"),
|
|
51
|
+
(re.compile(r"\bIn-plate\??\b"),
|
|
52
|
+
"'In-plate' / 'In-plate?' label"),
|
|
53
|
+
(re.compile(r"\bOut-of-plate\b"),
|
|
54
|
+
"'Out-of-plate' label"),
|
|
55
|
+
(re.compile(r"inside\s+(the\s+|\d+-week\s+)?plate", re.IGNORECASE),
|
|
56
|
+
"'inside the plate' phrasing"),
|
|
57
|
+
(re.compile(r"outside\s+(the\s+|\d+-week\s+)?plate", re.IGNORECASE),
|
|
58
|
+
"'outside the plate' phrasing"),
|
|
59
|
+
)
|
|
60
|
+
|
|
38
61
|
|
|
39
62
|
def _frontmatter(text: str) -> str:
|
|
40
63
|
if not text.startswith("---\n"):
|
|
@@ -73,6 +96,19 @@ def _check_lightweight(text: str, line_count: int, problems: list[str]) -> None:
|
|
|
73
96
|
)
|
|
74
97
|
|
|
75
98
|
|
|
99
|
+
def _check_no_plate(text: str, problems: list[str]) -> None:
|
|
100
|
+
"""Detect time-boxed plate / horizon framing forbidden by template rule 16."""
|
|
101
|
+
for pat, label in PLATE_PATS:
|
|
102
|
+
m = pat.search(text)
|
|
103
|
+
if m is None:
|
|
104
|
+
continue
|
|
105
|
+
line = text.count("\n", 0, m.start()) + 1
|
|
106
|
+
problems.append(
|
|
107
|
+
f"plate/horizon convention detected ({label}) at line {line} — "
|
|
108
|
+
f"forbidden by templates/roadmaps.md rule 16"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
76
112
|
def lint_roadmap(path: Path) -> list[str]:
|
|
77
113
|
text = path.read_text(encoding="utf-8")
|
|
78
114
|
line_count = text.count("\n") + (1 if text and not text.endswith("\n") else 0)
|
|
@@ -87,6 +123,7 @@ def lint_roadmap(path: Path) -> list[str]:
|
|
|
87
123
|
return problems
|
|
88
124
|
if complexity == "lightweight":
|
|
89
125
|
_check_lightweight(text, line_count, problems)
|
|
126
|
+
_check_no_plate(text, problems)
|
|
90
127
|
return problems
|
|
91
128
|
|
|
92
129
|
|
|
@@ -67,6 +67,15 @@
|
|
|
67
67
|
"enum": ["deep"],
|
|
68
68
|
"description": "Optional reasoning-depth marker for AI Council invocations triggered by this skill. The only accepted value is 'deep'; omit the key for default depth (setting 'standard' is rejected — every frontmatter byte counts against the context window, and 'standard' is the implicit default). 'deep' instructs the host agent to pass --depth deep to council_cli, which floors rounds at max(ai_council.deep_min_rounds, ai_council.min_rounds). Use for architecture, refactoring, or bug-diagnosis skills. See .agent-src.uncompressed/skills/ai-council/SKILL.md."
|
|
69
69
|
},
|
|
70
|
+
"context_spine": {
|
|
71
|
+
"type": "array",
|
|
72
|
+
"uniqueItems": true,
|
|
73
|
+
"items": {
|
|
74
|
+
"type": "string",
|
|
75
|
+
"enum": ["product", "team", "repo"]
|
|
76
|
+
},
|
|
77
|
+
"description": "Senior-skill opt-in for the tri-slot context spine. Declares which slots under agents/context-spine/ the skill expects to read (product, team, repo). Council Q1 (KEEP-3) locks the slot count at 3; additions require ≥ 2 citing skills + ADR per docs/contracts/context-spine.md § 5."
|
|
78
|
+
},
|
|
70
79
|
"execution": {
|
|
71
80
|
"type": "object",
|
|
72
81
|
"additionalProperties": false,
|