bmad-plus 0.22.0 → 0.23.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 (40) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +23 -13
  3. package/SECURITY.md +1 -1
  4. package/package.json +4 -1
  5. package/readme-international/README.de.md +23 -13
  6. package/readme-international/README.es.md +23 -13
  7. package/readme-international/README.fr.md +23 -13
  8. package/src/bmad-plus/agents/agent-orchestrator/SKILL.md +5 -1
  9. package/src/bmad-plus/data/role-triggers.yaml +34 -0
  10. package/src/bmad-plus/module-help.csv +2 -0
  11. package/src/bmad-plus/module.yaml +21 -0
  12. package/src/bmad-plus/packs/pack-design/SKILL.md +72 -0
  13. package/src/bmad-plus/packs/pack-design/bmad-skill-manifest.yaml +13 -0
  14. package/src/bmad-plus/packs/pack-design/design-engine.md +126 -0
  15. package/src/bmad-plus/packs/pack-design/intake.yaml +105 -0
  16. package/src/bmad-plus/skills/bmad-plus-autopilot/SKILL.md +7 -0
  17. package/src/bmad-plus/skills/bmad-plus-intake/SKILL.md +87 -0
  18. package/src/bmad-plus/skills/bmad-plus-intake/ref/intake-format.md +112 -0
  19. package/src/bmad-plus/skills/bmad-plus-parallel/SKILL.md +18 -0
  20. package/src/bmad-plus/skills/bmad-plus-swarm/SKILL.md +115 -0
  21. package/src/bmad-plus/skills/bmad-plus-swarm/ref/definition.md +188 -0
  22. package/tools/build/adapters.config.js +2 -0
  23. package/tools/build/generate-adapters.js +25 -1
  24. package/tools/build/generate.js +269 -7
  25. package/tools/build/intake-contract.js +522 -0
  26. package/tools/cli/bmad-plus-cli.js +2 -0
  27. package/tools/cli/commands/install.js +28 -1
  28. package/tools/cli/commands/intake.js +124 -0
  29. package/tools/cli/commands/swarm.js +119 -0
  30. package/tools/cli/commands/uninstall.js +17 -0
  31. package/tools/cli/i18n.js +20 -0
  32. package/tools/cli/lib/intake.js +202 -0
  33. package/tools/cli/lib/pack-copy.js +95 -5
  34. package/tools/cli/lib/packs.js +97 -7
  35. package/tools/cli/lib/swarm-deal.js +116 -0
  36. package/tools/cli/lib/swarm-defaults.js +6 -0
  37. package/tools/cli/lib/swarm-tally.js +195 -0
  38. package/tools/cli/lib/swarm-validate.js +589 -0
  39. package/tools/cli/lib/swarm.js +712 -0
  40. package/tools/cli/lib/update-transaction.js +8 -4
@@ -0,0 +1,115 @@
1
+ ---
2
+ name: bmad-plus-swarm
3
+ description: Moderate a swarm of sub-agents under a budget — simulated buyers dealt from a profile, a jury of lenses, or one worker per page — each reading one brief and writing one validated answer that `bmad-plus swarm` tallies with its disclosure first. Use when a pack declares a swarm, when someone asks for a panel, a jury, a simulated audience, a before/after read of a pitch, or when independent units of work should be fanned out to parallel sub-agents.
4
+ ---
5
+
6
+ # Moderating a swarm
7
+
8
+ ## Overview
9
+
10
+ A swarm is many sub-agents doing the same kind of work once each: a hundred simulated buyers
11
+ reading a pitch, five jurors holding one lens each, one writer per page. The moderator (you)
12
+ never does the units' work. The runtime (`bmad-plus swarm`) never calls a model. Between the two
13
+ sits a file protocol: a **card** per unit, frozen at init; a **brief** rendered from the card; an
14
+ **answer** the sub-agent writes as JSON and the runtime validates; a **tally** over valid answers
15
+ only, with a disclosure sentence on its first line.
16
+
17
+ ```
18
+ definition + profile|list|units ─► init ─► cards.json ─► briefs ─► one sub-agent per brief
19
+ │
20
+ tally.md ◄── tally ◄── check (missing / invalid) ◄── answers/<unit>.json ◄──┘
21
+ ```
22
+
23
+ Read `ref/definition.md` for the four file contracts with one example each.
24
+
25
+ ## The four kinds
26
+
27
+ | Kind | Units come from | Typical use | Tally |
28
+ |---|---|---|---|
29
+ | `panel` | dealt from a **profile** by exact quotas, seeded | simulated buyers, readers, users | rate by group, counts, quotes, numbers |
30
+ | `jury` | **listed** in the definition, one `lens` each | adversarial read of a pitch, a page, a plan | counts and quotes per lens |
31
+ | `fanout` | **supplied** at init, one disjoint write `scope` each | pages to write, scenes to render, modules to document | rate and numbers; exportable as a Nexus plan |
32
+
33
+ A `panel` and a `jury` run as **session sub-agents**: you launch them, wait for them, read their
34
+ files. A `fanout` can run the same way, or be exported with `swarm plan` as a **durable Nexus
35
+ plan** when the work must survive the session, needs an independent check per unit, or edits the
36
+ repository (each unit then owns an exclusive write scope and Nexus verifies its answer file).
37
+
38
+ ## The budget
39
+
40
+ `_bmad/config.yaml` holds `swarm.budget` (default 26 sub-agents per run), `swarm.wave` (default 10
41
+ launched together) and `swarm.media_credits: ask`. An absent block means these defaults.
42
+
43
+ - `init` refuses a run larger than the budget with status `needs-approval` and exit code 2. It
44
+ writes no brief. **Ask the user**, in one sentence with the number: "This panel needs 100
45
+ sub-agents; the budget is 26. Run it?" Only after a human yes, re-run `init` with `--approved`
46
+ and the seed the refusal printed. Never pass `--approved` on your own judgement.
47
+ - Paid generation (images, video, audio) is **never** covered by a budget: ask before each run
48
+ that would spend credits, whatever `budget` says.
49
+ - Launch one wave at a time; wait for the whole wave before the next. The wave size is the
50
+ configured one unless `briefs --wave N` says otherwise.
51
+
52
+ ## Protocol
53
+
54
+ 1. **Load the definition** the pack ships next to the agent that moderates it, and the inputs it
55
+ needs: a profile (panel), nothing more (jury), or a units file you write from the request
56
+ (fanout). Every path is project-relative with `/`.
57
+ 2. **Init.** `bmad-plus swarm init --definition FILE [--profile FILE | --units FILE]
58
+ [--context NAME=FILE]... [--n N] [--seed N] --json`. A panel needs `--n`; the seed is drawn and
59
+ recorded when absent. Keep the printed `run` id and `seed`.
60
+ 3. **Approval** when exit code 2: ask, then re-run `init --approved --seed <seed>`.
61
+ 4. **Briefs.** `bmad-plus swarm briefs RUN --json` prints the waves, each unit with the absolute
62
+ path of its brief and of the answer it must write.
63
+ 5. **Launch a wave**: one sub-agent per brief, with exactly this instruction and nothing else:
64
+ *"Read `<brief path>` and follow it exactly; it is your whole instruction."* Do not summarise
65
+ the brief, do not add context, do not merge two units into one sub-agent.
66
+ 6. **Wait for the wave**, then `bmad-plus swarm check RUN --json`. It lists every unit whose
67
+ answer is missing or invalid, with the reason.
68
+ 7. **Second chance, once.** Re-run `briefs` (it only renders units without a valid answer) and
69
+ launch the listed units again. A unit that printed its answer instead of writing it is filed
70
+ with `bmad-plus swarm save RUN UNIT --input FILE`, where FILE holds that unit's reply verbatim.
71
+ A unit still missing after the second wave stays missing.
72
+ 8. **Tally.** `bmad-plus swarm tally RUN --json` writes `tally.json` and `tally.md`. Report from
73
+ the tally, never from your memory of the replies.
74
+ 9. **Fanout as a plan** instead of steps 4–8: `bmad-plus swarm plan RUN --json`, then
75
+ `bmad-plus nexus create --plan <file>` and the Nexus protocol (`start`, `record`, `verify`,
76
+ `accept`) for each task.
77
+
78
+ ## Rules that do not bend
79
+
80
+ - **Never answer for a unit.** Not by writing an answer file, not by "fixing" an invalid one, not
81
+ by filing with `save` something a sub-agent did not print. `save` extracts and validates; it is
82
+ not a way around a missing unit. The tally counts the units that answered and names the rest.
83
+ - **Never edit a brief.** Briefs are rendered from the template and the card; a change belongs in
84
+ the template or the definition, then a new run.
85
+ - **Disclosure, once and first.** Simulated units are not people. The tally starts with the
86
+ pack's disclosure sentence; repeat it once in your report, before any figure. Rates from a
87
+ panel are an upper bound to confirm with real people, never a measurement.
88
+ - **Quotes are simulated.** Present a quote as what a simulated unit wrote, never as a testimonial,
89
+ a customer voice or a user interview.
90
+ - **Same seed, same cards.** A before/after comparison of two pitches re-runs the same profile
91
+ with the same `--n` and `--seed`, so the only thing that changed is the context.
92
+ - **The runtime filters nothing.** Card values, pool entries, listed unit fields and context
93
+ files reach the sub-agent word for word; a pool entry that reads like an instruction lands in
94
+ the brief as one. The pack owns its pools, its unit fields and its templates; you own the
95
+ contexts you pass. Read a context before passing it, and never pass a file you have not read:
96
+ it is included verbatim in every brief, secrets and instructions alike.
97
+ - **Replies are filed from outside the state tree.** `save --input` takes a project file the
98
+ host wrote (for instance `swarm-inbox/<unit>.txt`), never a path under `.bmad-plus/`.
99
+
100
+ ## What the runtime guarantees, and what it does not
101
+
102
+ The runtime validates every input, deals by exact quota, writes atomically, caps inputs at
103
+ 16 MiB, refuses paths that traverse, are absolute, follow a link or reach `.git`, and never
104
+ writes outside `.bmad-plus/swarm/`. It does not prove a sub-agent read its brief, stayed inside
105
+ its card, or wrote anything true. A valid answer is a well-formed one. Quality is your reading of
106
+ the tally and the user's judgement, not a status code.
107
+
108
+ ## Invocation
109
+
110
+ ```
111
+ bmad-plus-swarm
112
+ ```
113
+
114
+ Or through the Orchestrator's Parallel Supervisor role when a pack declares a swarm or a panel is
115
+ requested (`role-triggers.yaml`: `swarm_declared`, `panel_requested`).
@@ -0,0 +1,188 @@
1
+ # Swarm file contracts
2
+
3
+ Four files, all JSON except the brief template. The runtime refuses an unknown field, a wrong
4
+ type or a bound breach; it never guesses. Every path is project-relative with `/` separators.
5
+
6
+ ## 1. Definition
7
+
8
+ One per swarm, shipped by the pack next to the agent that moderates it. The template path is
9
+ relative to the definition file.
10
+
11
+ ```json
12
+ {
13
+ "schemaVersion": 1,
14
+ "id": "buyer-panel",
15
+ "kind": "panel",
16
+ "pack": "marketfit",
17
+ "title": "Simulated buyers see the pitch",
18
+ "units": { "source": "profile", "idPrefix": "B" },
19
+ "brief": { "template": "briefs/buyer.md", "context": ["pitch"] },
20
+ "answer": {
21
+ "required": ["id", "buys", "reason_code", "reason"],
22
+ "fields": {
23
+ "buys": { "type": "boolean" },
24
+ "reason_code": { "type": "enum", "values": ["price", "habit", "need", "trust", "other"] },
25
+ "reason": { "type": "string", "max": 600 },
26
+ "price_floor": { "type": "number", "min": 0 }
27
+ }
28
+ },
29
+ "tally": {
30
+ "rate": "buys",
31
+ "groupBy": ["segment", "behaviour"],
32
+ "counts": ["reason_code"],
33
+ "quotes": { "reason": 2 },
34
+ "numbers": ["price_floor"]
35
+ },
36
+ "disclosure": "Simulated units are not people. Read rates as an upper bound and confirm with real ones."
37
+ }
38
+ ```
39
+
40
+ | Field | Rule |
41
+ |---|---|
42
+ | `id` | 1–64 lowercase letters, digits or `-`; it opens the run id |
43
+ | `kind` / `units.source` | `panel`/`profile`, `jury`/`list`, `fanout`/`input` — the pair must match |
44
+ | `units.idPrefix` | 1–4 letters; required for a panel, optional for a jury whose entries lack an `id`, absent for a fanout |
45
+ | `units.list` | jury only: 1–1000 entries, each with a `lens` string and any other string, number or boolean fields; an optional `id` |
46
+ | `pack` | optional pack id, recorded in the run |
47
+ | `brief.template` | plain relative path, no `.` or `..` parts |
48
+ | `brief.context` | names of context files the moderator passes at init; each is required |
49
+ | `answer.fields` | at most 50; `id` is implicit and always required |
50
+ | field types | `boolean`; `integer` and `number` with optional `min`/`max`; `string` with `min`/`max` length (default 0–4000, ceiling 20000); `enum` with 1–50 distinct `values`; `list` of nonempty strings with `maxItems` (default 50, ceiling 200) |
51
+ | `tally.rate` | a boolean field |
52
+ | `tally.groupBy` | up to 5 card fields; every card must carry them |
53
+ | `tally.counts` | enum fields |
54
+ | `tally.quotes` | string field → quotes kept per group (1–10) |
55
+ | `tally.numbers` | integer or number fields |
56
+ | `disclosure` | 1–500 characters, printed first in every tally |
57
+
58
+ ## 2. Profile (panel)
59
+
60
+ ```json
61
+ {
62
+ "schemaVersion": 1,
63
+ "subject": "Orbit espresso bar",
64
+ "target": "Urban professionals who buy coffee most days",
65
+ "dimensions": [
66
+ {
67
+ "name": "segment",
68
+ "values": [
69
+ { "value": "early career", "share": 0.45, "ranges": { "income": [38000, 85000] } },
70
+ { "value": "mid career", "share": 0.4, "ranges": { "income": [85000, 150000] } },
71
+ { "value": "high earner", "share": 0.15, "ranges": { "income": [150000, 210000] } }
72
+ ]
73
+ },
74
+ {
75
+ "name": "behaviour",
76
+ "values": [
77
+ { "value": "daily regular", "share": 0.2, "note": "same drink, same place, most mornings" },
78
+ { "value": "explorer", "share": 0.5 },
79
+ { "value": "occasional", "share": 0.3, "note": "a treat, not a routine" }
80
+ ]
81
+ }
82
+ ],
83
+ "ranges": { "age": [24, 38] },
84
+ "pools": { "objection": ["too steep for a drink", "my place is closer"] }
85
+ }
86
+ ```
87
+
88
+ - 1–10 dimensions, each with 1–50 values whose shares add up to 1. Each dimension is dealt by
89
+ exact quota (largest remainder), then shuffled by the seeded generator: 20 cards from the
90
+ profile above hold 9, 8 and 3 segments whatever the seed.
91
+ - A range on a value is sampled when that value is dealt; every value of a dimension declares the
92
+ same range names. Top-level ranges are sampled per card. Integer bounds deal integers; other
93
+ bounds deal numbers with two decimals.
94
+ - One entry of each pool is dealt per card.
95
+ - A value's `note` becomes the card field `<dimension>_note` (empty for values without one).
96
+ - Card fields are the dimension names, their range names, the top-level range names, the pool
97
+ names and the notes; names must be distinct, and `id`, `scope` and `lens` are reserved.
98
+ - Names are not dealt. A brief that wants one draws it from a pool the pack supplies.
99
+
100
+ The dealt card for one unit:
101
+
102
+ ```json
103
+ { "id": "B007", "segment": "mid career", "income": 97250, "behaviour": "explorer", "behaviour_note": "", "age": 31, "objection": "my place is closer" }
104
+ ```
105
+
106
+ ## 3. Units file (fanout)
107
+
108
+ ```json
109
+ {
110
+ "schemaVersion": 1,
111
+ "units": [
112
+ { "id": "about", "scope": "site/about", "title": "About us", "tone": "warm" },
113
+ { "id": "pricing", "scope": ["site/pricing", "site/data/prices.json"], "title": "Pricing", "tone": "plain" }
114
+ ]
115
+ }
116
+ ```
117
+
118
+ - `id`: 1–64 lowercase letters, digits, `_` or `-` (a Nexus task id).
119
+ - `scope`: one path or a list of up to 20, portable and project-relative, never `.git` or
120
+ `.bmad-plus`; scopes are pairwise disjoint across units (no scope contains another).
121
+ - Other fields are strings, numbers or booleans and become card fields for the brief.
122
+
123
+ ## 4. Brief template
124
+
125
+ A Markdown file. Placeholders, and nothing else between double braces:
126
+
127
+ | Placeholder | Renders |
128
+ |---|---|
129
+ | `{{card.<field>}}` | the card field; a list renders comma-separated |
130
+ | `{{context.<name>}}` | the context file passed at init, verbatim and frozen in the run |
131
+ | `{{answer.path}}` | the absolute path of the answer file the sub-agent must write |
132
+ | `{{run.id}}`, `{{unit.id}}` | the run and unit ids |
133
+ | `{{disclosure}}` | the definition's disclosure sentence |
134
+
135
+ An unknown placeholder, a card field some card lacks or an undeclared context fails `init`.
136
+ Literal `{{` sequences are not allowed in a template. Every value is inserted verbatim: the
137
+ runtime filters no instruction text out of a card, a pool entry or a context, so the pack owns
138
+ what its pools and unit fields say and the moderator reads a context before passing it.
139
+
140
+ ```markdown
141
+ # You are unit {{unit.id}} of run {{run.id}}
142
+
143
+ Segment: {{card.segment}}, income {{card.income}}. Your first objection: {{card.objection}}.
144
+
145
+ ## The pitch
146
+
147
+ {{context.pitch}}
148
+
149
+ Write a JSON object to `{{answer.path}}` with `id` ("{{unit.id}}"), `buys`, `reason_code`
150
+ and `reason`.
151
+
152
+ {{disclosure}}
153
+ ```
154
+
155
+ ## 5. Answer
156
+
157
+ What the sub-agent writes to `{{answer.path}}`: one JSON object, `id` equal to the unit id,
158
+ every required field present, no field the definition does not declare.
159
+
160
+ ```json
161
+ { "id": "B007", "buys": false, "reason_code": "price", "reason": "5.40 is a lunch, not a drink.", "price_floor": 3.5 }
162
+ ```
163
+
164
+ `check` reports an answer as `missing` (no file) or `invalid` (unreadable JSON, wrong id, missing
165
+ required field, unknown field, type or bound breach) with the reasons. `save` applies the same
166
+ rules to the first balanced JSON object found in a printed reply.
167
+
168
+ ## 6. Tally
169
+
170
+ `tally.json` and `tally.md`, over valid answers only. Units without one are listed as skipped and
171
+ counted in the denominator line. Every section has an `all` entry and one entry per `groupBy`
172
+ value.
173
+
174
+ ```json
175
+ {
176
+ "schemaVersion": 1,
177
+ "run": "buyer-panel-20261016-k3f9",
178
+ "disclosure": "Simulated units are not people. ...",
179
+ "units": { "total": 20, "valid": 19, "skipped": [{ "unit": "B013", "reason": "no answer file" }] },
180
+ "rate": { "field": "buys", "all": { "yes": 11, "of": 19, "rate": 0.5789 }, "groups": { "segment": { "early career": { "yes": 6, "of": 9, "rate": 0.6667 } } } },
181
+ "counts": { "reason_code": { "all": { "price": 5, "habit": 7, "need": 4, "trust": 2, "other": 1 }, "groups": {} } },
182
+ "quotes": { "reason": { "keep": 2, "all": [{ "unit": "B001", "text": "On my way to work." }], "groups": {} } },
183
+ "numbers": { "price_floor": { "all": { "count": 14, "median": 4, "mean": 4.07, "min": 2.5, "max": 6 }, "groups": {} } }
184
+ }
185
+ ```
186
+
187
+ `tally.md` opens with the disclosure as a quote, then the denominator line, then one section per
188
+ rate, count, quote and number. Quotes are JSON-escaped verbatim, attributed to their unit id.
@@ -84,6 +84,8 @@ const PROJECT_INSTRUCTIONS = [
84
84
  '- Installed projects load skills from `.agents/skills/`; in this repository the sources live in `src/bmad-plus/skills/` and `src/bmad-plus/agents/`.',
85
85
  '- Each agent has a SKILL.md with capabilities, activation protocol, and role-switching rules.',
86
86
  '- Auto-activation triggers: `.agents/data/role-triggers.yaml` (source: `src/bmad-plus/data/role-triggers.yaml`).',
87
+ '- An agent whose roster line says `drives:` reads the `SKILL.md` of every skill it drives (`.agents/skills/<id>/SKILL.md`) before acting on that skill.',
88
+ "- The host runs a pack's declared intake (`bmad-plus intake`, skill `bmad-plus-intake`) before that pack's pipeline or swarm starts.",
87
89
  '',
88
90
  '## Project Structure',
89
91
  '',
@@ -141,8 +141,12 @@ function factsFromDerived(derived, packs = derived.packOrder) {
141
141
  categoryAgentCount: pack.categoryAgentCount,
142
142
  subAgentCount: pack.subAgentCount,
143
143
  workflowCount: pack.workflowCount,
144
+ pipelineCount: (pack.pipelines || []).length,
145
+ pipelines: pack.pipelines || [],
144
146
  frameworkCount: pack.frameworkCount,
147
+ usesSkills: pack.usesSkills || [],
145
148
  personas: pack.personas,
149
+ ...(pack.cohabitationWarning ? { cohabitationWarning: pack.cohabitationWarning } : {}),
146
150
  };
147
151
  });
148
152
  return {
@@ -174,6 +178,7 @@ function renderRoster(facts) {
174
178
  const identity = persona.alias
175
179
  ? '(' + persona.alias + ') — ' + persona.role
176
180
  : '(' + persona.role + ')';
181
+ const drives = [...new Set([...(pack.usesSkills || []), ...(persona.usesSkills || [])])];
177
182
  lines.push(
178
183
  '- **' +
179
184
  persona.name +
@@ -181,7 +186,8 @@ function renderRoster(facts) {
181
186
  identity +
182
187
  ' — ' +
183
188
  persona.description +
184
- (pack.required ? '' : ' (' + pack.name + ')')
189
+ (pack.required ? '' : ' (' + pack.name + ')') +
190
+ (drives.length ? ' — drives: ' + drives.join(', ') : '')
185
191
  );
186
192
  }
187
193
  }
@@ -214,16 +220,31 @@ function renderFactsSection(facts) {
214
220
  if (p.subAgentCount) extras.push(`${p.subAgentCount} sub-agents`);
215
221
  if (p.categoryAgentCount) extras.push(`${p.categoryAgentCount} specialized agents`);
216
222
  if (p.workflowCount) extras.push(`${p.workflowCount} workflows`);
223
+ if (p.pipelineCount)
224
+ extras.push(`${p.pipelineCount} pipeline${p.pipelineCount === 1 ? '' : 's'}`);
217
225
  if (p.frameworkCount) extras.push(`${p.frameworkCount} compliance frameworks`);
218
226
  const suffix = extras.length > 0 ? ` — ${extras.join(', ')}` : '';
219
227
  lines.push(
220
228
  ` - ${p.name}${p.required ? ' (required)' : ''}: ${p.agentCount} installer agent${p.agentCount === 1 ? '' : 's'} — ${p.desc}${suffix}`
221
229
  );
222
230
  }
231
+ for (const p of facts.packs) {
232
+ for (const pipeline of p.pipelines) {
233
+ const steps = pipeline.steps
234
+ .map((step, index) => `${index + 1}. ${step.workflow ?? step.agent} (${step.gate})`)
235
+ .join(' → ');
236
+ lines.push(`- Pipelines (${p.name}): ${pipeline.id} "${pipeline.title}": ${steps}`);
237
+ }
238
+ }
223
239
  if (facts.dataFlows.length)
224
240
  lines.push(
225
241
  `- Data handling: no telemetry; ${facts.dataFlows.length} flow${facts.dataFlows.length === 1 ? '' : 's'} can send data off the machine, each under its consent basis: ${facts.dataFlows.map((flow) => `${flow.id} (${flow.consent})`).join(', ')}. What, to whom and when: "Data handling" in the bmad-plus SECURITY.md.`
226
242
  );
243
+ // A pack that overlaps another one says so where every agent reads it, not only in
244
+ // the install prompt; the line exists only for packs that declare a warning.
245
+ for (const p of facts.packs) {
246
+ if (p.cohabitationWarning) lines.push(`- Cohabitation (${p.name}): ${p.cohabitationWarning}`);
247
+ }
227
248
  return lines;
228
249
  }
229
250
 
@@ -421,6 +442,8 @@ function renderUserSections(derived, { packs, userName = 'Developer', language =
421
442
  '- Load skills from `.agents/skills/`.',
422
443
  '- Each agent has a SKILL.md with capabilities, activation protocol, and role-switching rules.',
423
444
  '- Auto-activation triggers: `.agents/data/role-triggers.yaml`.',
445
+ '- An agent whose roster line says `drives:` reads the `SKILL.md` of every skill it drives (`.agents/skills/<id>/SKILL.md`) before acting on that skill.',
446
+ "- The host runs a pack's declared intake (`bmad-plus intake`, skill `bmad-plus-intake`) before that pack's pipeline or swarm starts.",
424
447
  '',
425
448
  '## Communication',
426
449
  '',
@@ -799,6 +822,7 @@ module.exports = {
799
822
  TOOL_NOTES,
800
823
  validateTargets,
801
824
  buildRegistryFacts,
825
+ factsFromDerived,
802
826
  groupAdaptersByFile,
803
827
  renderFactsSection,
804
828
  renderProjectInstructions,