@event4u/agent-config 2.6.1 → 2.8.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.
Files changed (164) hide show
  1. package/.agent-src/commands/agent-handoff.md +1 -0
  2. package/.agent-src/commands/agent-status.md +1 -0
  3. package/.agent-src/commands/agents/audit.md +1 -0
  4. package/.agent-src/commands/agents/init.md +1 -0
  5. package/.agent-src/commands/agents/optimize.md +1 -0
  6. package/.agent-src/commands/agents.md +1 -0
  7. package/.agent-src/commands/analyze-reference-repo.md +1 -0
  8. package/.agent-src/commands/bug-fix.md +1 -0
  9. package/.agent-src/commands/bug-investigate.md +1 -0
  10. package/.agent-src/commands/challenge-me/vision.md +1 -0
  11. package/.agent-src/commands/challenge-me/with-docs.md +1 -0
  12. package/.agent-src/commands/challenge-me.md +1 -0
  13. package/.agent-src/commands/chat-history/import.md +1 -0
  14. package/.agent-src/commands/chat-history/learn.md +1 -0
  15. package/.agent-src/commands/chat-history/show.md +1 -0
  16. package/.agent-src/commands/chat-history.md +1 -0
  17. package/.agent-src/commands/check-current-md.md +1 -0
  18. package/.agent-src/commands/commit/in-chunks.md +1 -0
  19. package/.agent-src/commands/commit.md +1 -0
  20. package/.agent-src/commands/compress.md +1 -0
  21. package/.agent-src/commands/context/create.md +1 -0
  22. package/.agent-src/commands/context/refactor.md +1 -0
  23. package/.agent-src/commands/context.md +1 -0
  24. package/.agent-src/commands/cost-report.md +1 -0
  25. package/.agent-src/commands/council/default.md +1 -0
  26. package/.agent-src/commands/council/design.md +1 -0
  27. package/.agent-src/commands/council/optimize.md +1 -0
  28. package/.agent-src/commands/council/pr.md +1 -0
  29. package/.agent-src/commands/council.md +1 -0
  30. package/.agent-src/commands/create-pr/description-only.md +1 -0
  31. package/.agent-src/commands/create-pr.md +1 -0
  32. package/.agent-src/commands/e2e-heal.md +1 -0
  33. package/.agent-src/commands/e2e-plan.md +1 -0
  34. package/.agent-src/commands/estimate-ticket.md +1 -0
  35. package/.agent-src/commands/feature/dev.md +1 -0
  36. package/.agent-src/commands/feature/explore.md +1 -0
  37. package/.agent-src/commands/feature/plan.md +1 -0
  38. package/.agent-src/commands/feature/refactor.md +1 -0
  39. package/.agent-src/commands/feature/roadmap.md +1 -0
  40. package/.agent-src/commands/feature.md +1 -0
  41. package/.agent-src/commands/fix/ci.md +1 -0
  42. package/.agent-src/commands/fix/portability.md +1 -0
  43. package/.agent-src/commands/fix/pr-bot-comments.md +1 -0
  44. package/.agent-src/commands/fix/pr-comments.md +1 -0
  45. package/.agent-src/commands/fix/pr-developer-comments.md +1 -0
  46. package/.agent-src/commands/fix/refs.md +1 -0
  47. package/.agent-src/commands/fix/seeder.md +1 -0
  48. package/.agent-src/commands/fix.md +1 -0
  49. package/.agent-src/commands/grill-me.md +1 -0
  50. package/.agent-src/commands/implement-ticket.md +1 -0
  51. package/.agent-src/commands/jira-ticket.md +1 -0
  52. package/.agent-src/commands/judge/on-diff.md +1 -0
  53. package/.agent-src/commands/judge/solo.md +1 -0
  54. package/.agent-src/commands/judge/steps.md +1 -0
  55. package/.agent-src/commands/judge.md +1 -0
  56. package/.agent-src/commands/memory/add.md +1 -0
  57. package/.agent-src/commands/memory/load.md +1 -0
  58. package/.agent-src/commands/memory/mine-session.md +1 -0
  59. package/.agent-src/commands/memory/promote.md +1 -0
  60. package/.agent-src/commands/memory/propose.md +1 -0
  61. package/.agent-src/commands/memory.md +1 -0
  62. package/.agent-src/commands/mode.md +1 -0
  63. package/.agent-src/commands/module/create.md +1 -0
  64. package/.agent-src/commands/module/explore.md +1 -0
  65. package/.agent-src/commands/module.md +1 -0
  66. package/.agent-src/commands/onboard.md +1 -0
  67. package/.agent-src/commands/optimize/agents-dir.md +1 -0
  68. package/.agent-src/commands/optimize/augmentignore.md +1 -0
  69. package/.agent-src/commands/optimize/rtk.md +1 -0
  70. package/.agent-src/commands/optimize/skills.md +1 -0
  71. package/.agent-src/commands/optimize-prompt.md +1 -0
  72. package/.agent-src/commands/optimize.md +1 -0
  73. package/.agent-src/commands/orchestrate.md +1 -0
  74. package/.agent-src/commands/override/create.md +1 -0
  75. package/.agent-src/commands/override/manage.md +1 -0
  76. package/.agent-src/commands/override.md +1 -0
  77. package/.agent-src/commands/package-reset.md +1 -0
  78. package/.agent-src/commands/package-test.md +1 -0
  79. package/.agent-src/commands/prepare-for-review.md +1 -0
  80. package/.agent-src/commands/project-analyze.md +1 -0
  81. package/.agent-src/commands/project-health.md +1 -0
  82. package/.agent-src/commands/quality-fix.md +1 -0
  83. package/.agent-src/commands/refine-ticket.md +1 -0
  84. package/.agent-src/commands/research/deep.md +1 -0
  85. package/.agent-src/commands/research/report.md +1 -0
  86. package/.agent-src/commands/research.md +1 -0
  87. package/.agent-src/commands/review-changes.md +1 -0
  88. package/.agent-src/commands/review-routing.md +1 -0
  89. package/.agent-src/commands/roadmap/ai-council.md +1 -0
  90. package/.agent-src/commands/roadmap/create.md +1 -0
  91. package/.agent-src/commands/roadmap/process-full.md +1 -0
  92. package/.agent-src/commands/roadmap/process-phase.md +1 -0
  93. package/.agent-src/commands/roadmap/process-step.md +1 -0
  94. package/.agent-src/commands/roadmap.md +1 -0
  95. package/.agent-src/commands/rule-compliance-audit.md +1 -0
  96. package/.agent-src/commands/set-cost-profile.md +1 -0
  97. package/.agent-src/commands/sync-agent-settings.md +1 -0
  98. package/.agent-src/commands/sync-gitignore/fix.md +1 -0
  99. package/.agent-src/commands/sync-gitignore.md +1 -0
  100. package/.agent-src/commands/tests/create.md +1 -0
  101. package/.agent-src/commands/tests/execute.md +1 -0
  102. package/.agent-src/commands/tests.md +1 -0
  103. package/.agent-src/commands/threat-model.md +1 -0
  104. package/.agent-src/commands/update-form-request-messages.md +1 -0
  105. package/.agent-src/commands/upstream-contribute.md +1 -0
  106. package/.agent-src/commands/work.md +1 -0
  107. package/.agent-src/personas/cmo.md +122 -0
  108. package/.agent-src/personas/customer-success-lead.md +126 -0
  109. package/.agent-src/personas/growth-pm.md +134 -0
  110. package/.agent-src/personas/revops.md +125 -0
  111. package/.agent-src/skills/activation-design/SKILL.md +160 -0
  112. package/.agent-src/skills/churn-prevention/SKILL.md +156 -0
  113. package/.agent-src/skills/content-funnel-design/SKILL.md +170 -0
  114. package/.agent-src/skills/deal-qualification-meddic/SKILL.md +165 -0
  115. package/.agent-src/skills/editorial-calendar/SKILL.md +161 -0
  116. package/.agent-src/skills/expansion-playbook/SKILL.md +171 -0
  117. package/.agent-src/skills/forecast-accuracy/SKILL.md +157 -0
  118. package/.agent-src/skills/fundraising-narrative/SKILL.md +189 -0
  119. package/.agent-src/skills/funnel-analysis/SKILL.md +26 -2
  120. package/.agent-src/skills/gtm-launch/SKILL.md +165 -0
  121. package/.agent-src/skills/messaging-architecture/SKILL.md +184 -0
  122. package/.agent-src/skills/onboarding-design/SKILL.md +158 -0
  123. package/.agent-src/skills/pipeline-strategy/SKILL.md +159 -0
  124. package/.agent-src/skills/positioning-strategy/SKILL.md +177 -0
  125. package/.agent-src/skills/retention-loops/SKILL.md +161 -0
  126. package/.agent-src/skills/subagent-orchestration/SKILL.md +1 -1
  127. package/.agent-src/skills/voice-and-tone-design/SKILL.md +163 -0
  128. package/.agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  129. package/.claude-plugin/marketplace.json +17 -2
  130. package/AGENTS.md +4 -4
  131. package/CHANGELOG.md +60 -2257
  132. package/README.md +59 -33
  133. package/docs/architecture/augment-projection.md +70 -0
  134. package/docs/architecture/claude-bundle.md +77 -0
  135. package/docs/architecture/compression.md +67 -0
  136. package/docs/architecture/multi-tool-projection.md +72 -0
  137. package/docs/architecture.md +32 -55
  138. package/docs/archive/CHANGELOG-pre-2.2.0.md +2138 -0
  139. package/docs/archive/CHANGELOG-pre-2.7.0.md +185 -0
  140. package/docs/catalog.md +19 -3
  141. package/docs/contracts/CHANGELOG-conventions.md +121 -0
  142. package/docs/contracts/adr-gtm-context-spine.md +115 -0
  143. package/docs/contracts/command-surface-tiers.md +145 -0
  144. package/docs/contracts/context-spine.md +50 -12
  145. package/docs/contracts/cross-wing-handoff.md +3 -3
  146. package/docs/contracts/mcp-cloud-scope.md +193 -21
  147. package/docs/contracts/mcp-phase-1-scope.md +1 -0
  148. package/docs/contracts/persona-schema.md +20 -3
  149. package/docs/decisions/ADR-007-agent-discovery-scopes.md +67 -0
  150. package/docs/guidelines/gtm-handoff.md +114 -0
  151. package/docs/setup/enterprise-and-offline.md +201 -0
  152. package/docs/setup/per-ide/augment.md +37 -25
  153. package/package.json +1 -1
  154. package/scripts/_bootstrap_tier_frontmatter.py +151 -0
  155. package/scripts/agent-config +146 -83
  156. package/scripts/hermetic-install.sh +235 -0
  157. package/scripts/install.py +8 -1
  158. package/scripts/lint_command_tiers.py +115 -0
  159. package/scripts/lint_context_spine_usage.py +4 -1
  160. package/scripts/mcp_server/__init__.py +5 -0
  161. package/scripts/schemas/command.schema.json +5 -0
  162. package/scripts/schemas/persona.schema.json +5 -0
  163. package/scripts/schemas/skill.schema.json +2 -2
  164. package/scripts/skill_linter.py +177 -3
@@ -12,6 +12,11 @@
12
12
  "pattern": "^[a-z][a-z0-9-]*(:[a-z][a-z0-9-]*)?$",
13
13
  "$comment": "Top-level commands use the bare slug (`commit`). Nested cluster commands under `commands/<cluster>/<sub>.md` use the colon form (`council:default`) to mirror Claude Code's `/cluster:sub` rendering. Directory slug for `.claude/skills/` is the hyphenated form (`council-default`), generated by compress.py."
14
14
  },
15
+ "tier": {
16
+ "type": "integer",
17
+ "enum": [0, 1, 2],
18
+ "description": "Command-surface tier per docs/contracts/command-surface-tiers.md. 0 = daily-driver (rendered in default `./agent-config --help`), 1 = power-user (rendered with `--tier=1`), 2 = maintenance/internal (rendered only with `--tier=all`). Default for new commands is 2 — promotion is gated by ADR criteria, never by author preference."
19
+ },
15
20
  "description": {
16
21
  "type": "string",
17
22
  "minLength": 1,
@@ -26,6 +26,11 @@
26
26
  "type": "string",
27
27
  "enum": ["core", "specialist"]
28
28
  },
29
+ "wing": {
30
+ "type": "integer",
31
+ "enum": [1, 2, 3, 4],
32
+ "description": "Cognition wing per docs/contracts/package-self-orientation.md § The four wings. Optional. Raises the specialist size cap (persona-schema § 4)."
33
+ },
29
34
  "mode": {
30
35
  "type": "string",
31
36
  "enum": ["developer", "reviewer", "tester", "product-owner", "incident", "planner"]
@@ -72,9 +72,9 @@
72
72
  "uniqueItems": true,
73
73
  "items": {
74
74
  "type": "string",
75
- "enum": ["product", "team", "repo"]
75
+ "enum": ["product", "team", "repo", "channel-stage", "funnel-stage", "customer-segment"]
76
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."
77
+ "description": "Senior-skill opt-in for the context spine. Declares which slots under agents/context-spine/ the skill expects to read. Cross-wing slots (product, team, repo) are locked at 3 by council Q1 (KEEP-3); wing-scoped slots (channel-stage, funnel-stage, customer-segment — Wing-3 GTM) follow the per-wing ADR track in docs/contracts/context-spine.md § 5. Wing-3 slots authorized by docs/contracts/adr-gtm-context-spine.md."
78
78
  },
79
79
  "execution": {
80
80
  "type": "object",
@@ -57,6 +57,15 @@ REQUIRED_PERSONA_SECTIONS = REQUIRED_PERSONA_SECTIONS_CORE
57
57
  VALID_PERSONA_TIERS = {"core", "specialist"}
58
58
  # Locked in docs/contracts/persona-schema.md § 4: core ≤ 120, specialist ≤ 100.
59
59
  PERSONA_LINE_BUDGETS = {"core": 120, "specialist": 100}
60
+ # Wing-scoped overrides — Wing-3 (GTM) and Wing-4 (Money/Strategy/Ops) carry
61
+ # denser cognition (funnel × channel × lifecycle, or finance × org × strategy)
62
+ # than Wing-1/2 specialists, so the line cap rises to keep the seven-section
63
+ # spine intact without amputating workflows. Persona-schema.md § 4 wing matrix.
64
+ VALID_PERSONA_WINGS = {1, 2, 3, 4}
65
+ PERSONA_LINE_BUDGETS_BY_WING = {
66
+ ("specialist", 3): 140,
67
+ ("specialist", 4): 140,
68
+ }
60
69
 
61
70
 
62
71
  REQUIRED_SKILL_SECTIONS = [
@@ -161,6 +170,47 @@ VALID_EXECUTION_HANDLERS = {"none", "shell", "php", "node", "internal"}
161
170
  VALID_EXECUTION_SAFETY_MODES = {"strict"}
162
171
  VALID_EXECUTION_FIELDS = {"type", "handler", "timeout_seconds", "safety_mode", "allowed_tools", "command"}
163
172
 
173
+ # --- Wing-3 GTM cognition-boundary patterns (council Q7 / iter-2 OQ3) ---
174
+ # Triggered only when a skill's context_spine declares a Wing-3 slot.
175
+ # See docs/contracts/adr-gtm-context-spine.md and
176
+ # agents/roadmaps/road-to-gtm-and-growth.md § G2.
177
+ WING3_SPINE_SLOTS = {"channel-stage", "funnel-stage", "customer-segment"}
178
+
179
+ CONTEXT_SPINE_INLINE_PATTERN = re.compile(
180
+ r'^context_spine:\s*\[(.*?)\]\s*$', re.MULTILINE
181
+ )
182
+
183
+ # agent-operability: external SaaS URLs the agent would have to auth against
184
+ WING3_SAAS_URL_PATTERN = re.compile(
185
+ r"https?://[\w.-]*\.(salesforce|hubspot|marketo|pardot|mailchimp|"
186
+ r"intercom|amplitude|mixpanel|segment|klaviyo|sendgrid|mailgun|"
187
+ r"pendo|gong|outreach|salesloft|apollo)\.(com|io)\b",
188
+ re.IGNORECASE,
189
+ )
190
+
191
+ # vendor-independence: brand / SDK / platform slugs that lock cognition
192
+ WING3_VENDOR_BLACKLIST = re.compile(
193
+ r"\b(salesforce|hubspot|marketo|pardot|mailchimp|intercom|drift|"
194
+ r"klaviyo|sendgrid|mailgun|amplitude|mixpanel|pendo|gong|"
195
+ r"outreach\.io|salesloft|apollo\.io|zendesk|freshworks)\b",
196
+ re.IGNORECASE,
197
+ )
198
+
199
+ # transferability: stack-locked tooling instructions
200
+ WING3_STACK_LOCKED_PATTERN = re.compile(
201
+ r"\b(npm install|pip install|composer require|gem install|"
202
+ r"cargo add|yarn add|pnpm add|bundle add)\s+[\w@/.-]+",
203
+ re.IGNORECASE,
204
+ )
205
+
206
+ # channel-agnosticism: channel-specific tactical prescriptions
207
+ WING3_CHANNEL_TACTIC_PATTERN = re.compile(
208
+ r"\b(email subject line|tweet length|linkedin (post|ad)|"
209
+ r"facebook ad|google ads?|tiktok (post|video)|instagram (post|reel)|"
210
+ r"sms character limit|cold email template)\b",
211
+ re.IGNORECASE,
212
+ )
213
+
164
214
 
165
215
  @dataclass
166
216
  class Issue:
@@ -605,6 +655,11 @@ def lint_skill(path: Path, text: str) -> LintResult:
605
655
  if tier_match and tier_match.group(1) == "senior":
606
656
  issues.extend(lint_senior_tier_blocks(text))
607
657
 
658
+ # --- Wing-3 GTM cognition-boundary check (council Q7 / adr-gtm-context-spine.md) ---
659
+ spine_slots = parse_context_spine(frontmatter)
660
+ if spine_slots and any(s in WING3_SPINE_SLOTS for s in spine_slots):
661
+ issues.extend(lint_wing3_boundaries(text))
662
+
608
663
  procedure_block = find_procedure_block(text)
609
664
  if procedure_block is not None:
610
665
  if not procedure_block:
@@ -993,6 +1048,98 @@ def lint_senior_tier_blocks(text: str) -> List[Issue]:
993
1048
  return issues
994
1049
 
995
1050
 
1051
+ def parse_context_spine(frontmatter: str) -> Optional[List[str]]:
1052
+ """Parse `context_spine:` from frontmatter.
1053
+
1054
+ Supports the inline form `context_spine: [a, b, c]` (most skills) and
1055
+ the block form via `_parse_yaml_list`. Returns the slot list, ``[]``
1056
+ for an explicitly empty array, or ``None`` if the key is absent.
1057
+ """
1058
+ match = CONTEXT_SPINE_INLINE_PATTERN.search(frontmatter)
1059
+ if match is not None:
1060
+ inner = match.group(1).strip()
1061
+ if not inner:
1062
+ return []
1063
+ return [s.strip().strip('"').strip("'") for s in inner.split(",") if s.strip()]
1064
+ block = _parse_yaml_list(frontmatter, "context_spine")
1065
+ return block
1066
+
1067
+
1068
+ def _strip_wing3_carve_outs(text: str) -> str:
1069
+ """Remove fenced code, inline backticks, the ``## Do NOT`` block, and
1070
+ ``**WHEN NOT to use this**`` bullets so legitimate citations of vendor
1071
+ names (as off-scope examples) do not trip Wing-3 boundary checks.
1072
+ """
1073
+ text = re.sub(r"```[^\n]*\n.*?```", "", text, flags=re.DOTALL)
1074
+ text = re.sub(r"`[^`]+`", "", text)
1075
+ text = re.sub(
1076
+ r"^##\s+Do NOT\s*$.*?(?=^##\s+|\Z)",
1077
+ "", text, flags=re.MULTILINE | re.DOTALL,
1078
+ )
1079
+ text = re.sub(
1080
+ r"\*\*WHEN NOT to use this\*\*.*?(?=\*\*WHEN|^##\s+|\Z)",
1081
+ "", text, flags=re.DOTALL | re.IGNORECASE,
1082
+ )
1083
+ return text
1084
+
1085
+
1086
+ def lint_wing3_boundaries(text: str) -> List[Issue]:
1087
+ """Four Wing-3 GTM cognition-boundary checks.
1088
+
1089
+ Triggered when a skill's ``context_spine`` declares at least one
1090
+ Wing-3 slot (channel-stage, funnel-stage, customer-segment). Enforces
1091
+ council Q7 / iter-2 OQ3 verdict that GTM cognition stays:
1092
+
1093
+ - **agent-operability** — no external SaaS URLs the agent would auth against.
1094
+ - **vendor-independence** — no platform / SDK / brand slugs.
1095
+ - **transferability** — no stack-locked tooling instructions.
1096
+ - **channel-agnosticism** — no channel-specific tactical prescriptions.
1097
+
1098
+ Carve-outs: fenced code, inline backticks, the ``## Do NOT`` block,
1099
+ and ``**WHEN NOT to use this**`` lists — so authors can cite a vendor
1100
+ as off-scope without tripping the linter.
1101
+ """
1102
+ issues: List[Issue] = []
1103
+ body = _strip_wing3_carve_outs(text)
1104
+
1105
+ match = WING3_SAAS_URL_PATTERN.search(body)
1106
+ if match:
1107
+ issues.append(Issue(
1108
+ "warning", "wing3_agent_operability",
1109
+ f"Wing-3 skill cites external SaaS URL `{match.group(0)}` outside "
1110
+ f"carve-outs — cognition skills must operate without SaaS auth "
1111
+ f"(council Q7 boundary)",
1112
+ ))
1113
+
1114
+ match = WING3_VENDOR_BLACKLIST.search(body)
1115
+ if match:
1116
+ issues.append(Issue(
1117
+ "warning", "wing3_vendor_independence",
1118
+ f"Wing-3 skill names vendor `{match.group(0)}` outside carve-outs "
1119
+ f"— keep cognition vendor-agnostic (council Q7 boundary)",
1120
+ ))
1121
+
1122
+ match = WING3_STACK_LOCKED_PATTERN.search(body)
1123
+ if match:
1124
+ issues.append(Issue(
1125
+ "warning", "wing3_transferability",
1126
+ f"Wing-3 skill includes stack-locked instruction `{match.group(0)}` "
1127
+ f"outside carve-outs — cognition should transfer across stacks "
1128
+ f"(council Q7 boundary)",
1129
+ ))
1130
+
1131
+ match = WING3_CHANNEL_TACTIC_PATTERN.search(body)
1132
+ if match:
1133
+ issues.append(Issue(
1134
+ "warning", "wing3_channel_agnosticism",
1135
+ f"Wing-3 skill prescribes channel-specific tactic "
1136
+ f"`{match.group(0)}` outside carve-outs — keep cognition "
1137
+ f"channel-agnostic (council Q7 boundary)",
1138
+ ))
1139
+
1140
+ return issues
1141
+
1142
+
996
1143
  def lint_execution_metadata(execution: dict) -> List[Issue]:
997
1144
  """Validate the execution block of a skill."""
998
1145
  issues: List[Issue] = []
@@ -1436,6 +1583,27 @@ def lint_persona(path: Path, text: str) -> LintResult:
1436
1583
  f"Persona tier `{parsed['tier']}` must be one of {sorted(VALID_PERSONA_TIERS)}",
1437
1584
  ))
1438
1585
 
1586
+ # wing — optional; when present must be one of {1,2,3,4} (per
1587
+ # docs/contracts/package-self-orientation.md § The four wings).
1588
+ wing_match = re.search(r'^wing:\s*"?(\d+)"?\s*$', frontmatter, re.MULTILINE)
1589
+ if wing_match:
1590
+ try:
1591
+ wing_value = int(wing_match.group(1))
1592
+ if wing_value in VALID_PERSONA_WINGS:
1593
+ parsed["wing"] = wing_value
1594
+ else:
1595
+ issues.append(Issue(
1596
+ "error",
1597
+ "invalid_wing",
1598
+ f"Persona wing `{wing_value}` must be one of {sorted(VALID_PERSONA_WINGS)}",
1599
+ ))
1600
+ except ValueError:
1601
+ issues.append(Issue(
1602
+ "error",
1603
+ "invalid_wing",
1604
+ f"Persona wing `{wing_match.group(1)}` must be an integer 1–4",
1605
+ ))
1606
+
1439
1607
  # description length
1440
1608
  if "description" in parsed and len(parsed["description"]) > 160:
1441
1609
  issues.append(Issue(
@@ -1473,15 +1641,21 @@ def lint_persona(path: Path, text: str) -> LintResult:
1473
1641
  f"Persona has {bullet_count} unique questions (target ≥ 3)",
1474
1642
  ))
1475
1643
 
1476
- # Size budget by tier
1644
+ # Size budget by tier — wing-overrides apply when the persona declares a
1645
+ # `wing:` field; defaults to the tier baseline otherwise.
1477
1646
  if "tier" in parsed and parsed["tier"] in PERSONA_LINE_BUDGETS:
1478
- budget = PERSONA_LINE_BUDGETS[parsed["tier"]]
1647
+ tier_value = parsed["tier"]
1648
+ wing_value = parsed.get("wing")
1649
+ budget = PERSONA_LINE_BUDGETS_BY_WING.get(
1650
+ (tier_value, wing_value), PERSONA_LINE_BUDGETS[tier_value]
1651
+ )
1479
1652
  line_count = len(text.splitlines())
1480
1653
  if line_count > budget:
1654
+ scope = f"{tier_value}" if wing_value is None else f"{tier_value}, wing {wing_value}"
1481
1655
  issues.append(Issue(
1482
1656
  "warning",
1483
1657
  "size_budget",
1484
- f"Persona has {line_count} lines ({parsed['tier']} budget ≤ {budget})",
1658
+ f"Persona has {line_count} lines ({scope} budget ≤ {budget})",
1485
1659
  ))
1486
1660
 
1487
1661
  # H1 heading