bmad-plus 0.21.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 (75) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +24 -14
  3. package/SECURITY.md +63 -1
  4. package/osint-agent-package/skills/bmad-osint-investigate/osint/scripts/_http.py +68 -24
  5. package/package.json +4 -1
  6. package/readme-international/README.de.md +24 -14
  7. package/readme-international/README.es.md +24 -14
  8. package/readme-international/README.fr.md +24 -14
  9. package/src/bmad-plus/agents/agent-orchestrator/SKILL.md +5 -1
  10. package/src/bmad-plus/data/role-triggers.yaml +34 -0
  11. package/src/bmad-plus/module-help.csv +2 -0
  12. package/src/bmad-plus/module.yaml +21 -0
  13. package/src/bmad-plus/packs/pack-design/SKILL.md +72 -0
  14. package/src/bmad-plus/packs/pack-design/bmad-skill-manifest.yaml +13 -0
  15. package/src/bmad-plus/packs/pack-design/design-engine.md +126 -0
  16. package/src/bmad-plus/packs/pack-design/intake.yaml +105 -0
  17. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review.md +3 -1
  18. package/src/bmad-plus/packs/pack-seo/SKILL.md +3 -1
  19. package/src/bmad-plus/packs/pack-seo/ref/cwv-thresholds.md +2 -2
  20. package/src/bmad-plus/packs/pack-seo/requirements.txt +1 -1
  21. package/src/bmad-plus/packs/pack-seo/scripts/seo_apis.py +72 -30
  22. package/src/bmad-plus/packs/pack-seo/scripts/seo_report.py +5 -6
  23. package/src/bmad-plus/packs/pack-seo/scripts/seo_screenshot.py +176 -14
  24. package/src/bmad-plus/packs/pack-shield/README.md +12 -0
  25. package/src/bmad-plus/packs/pack-shield/SKILL.md +7 -1
  26. package/src/bmad-plus/packs/pack-shield/review-rules/access-control.md +10 -0
  27. package/src/bmad-plus/packs/pack-shield/review-rules/ai-integrations.md +10 -0
  28. package/src/bmad-plus/packs/pack-shield/review-rules/change-and-supply-chain.md +10 -0
  29. package/src/bmad-plus/packs/pack-shield/review-rules/cryptography.md +10 -0
  30. package/src/bmad-plus/packs/pack-shield/review-rules/index.yaml +134 -0
  31. package/src/bmad-plus/packs/pack-shield/review-rules/logging.md +10 -0
  32. package/src/bmad-plus/packs/pack-shield/review-rules/personal-data.md +10 -0
  33. package/src/bmad-plus/packs/pack-shield/shared/ai-processing-register-template.yaml +53 -0
  34. package/src/bmad-plus/packs/pack-shield/shared/ai-processing-register.md +32 -0
  35. package/src/bmad-plus/packs/pack-shield/shared/assurance-case-template.yaml +87 -0
  36. package/src/bmad-plus/packs/pack-shield/shared/assurance-case.md +50 -0
  37. package/src/bmad-plus/packs/pack-shield/shield-orchestrator.md +24 -1
  38. package/src/bmad-plus/skills/bmad-plus-autopilot/SKILL.md +7 -0
  39. package/src/bmad-plus/skills/bmad-plus-intake/SKILL.md +87 -0
  40. package/src/bmad-plus/skills/bmad-plus-intake/ref/intake-format.md +112 -0
  41. package/src/bmad-plus/skills/bmad-plus-parallel/SKILL.md +18 -0
  42. package/src/bmad-plus/skills/bmad-plus-swarm/SKILL.md +115 -0
  43. package/src/bmad-plus/skills/bmad-plus-swarm/ref/definition.md +188 -0
  44. package/src/bmad-plus/skills/bmad-plus-uat/SKILL.md +1 -0
  45. package/src/bmad-plus/skills/bmad-plus-uat/template/page.html +5 -4
  46. package/tools/build/adapters.config.js +2 -0
  47. package/tools/build/generate-adapters.js +32 -1
  48. package/tools/build/generate.js +283 -7
  49. package/tools/build/intake-contract.js +522 -0
  50. package/tools/cli/bmad-plus-cli.js +4 -0
  51. package/tools/cli/commands/ai-register.js +63 -0
  52. package/tools/cli/commands/assurance.js +162 -0
  53. package/tools/cli/commands/install.js +28 -1
  54. package/tools/cli/commands/intake.js +124 -0
  55. package/tools/cli/commands/review.js +10 -3
  56. package/tools/cli/commands/swarm.js +119 -0
  57. package/tools/cli/commands/uninstall.js +17 -0
  58. package/tools/cli/i18n.js +20 -0
  59. package/tools/cli/lib/ai-register.js +393 -0
  60. package/tools/cli/lib/assurance.js +822 -0
  61. package/tools/cli/lib/control-refs.js +132 -0
  62. package/tools/cli/lib/installation-health.js +17 -0
  63. package/tools/cli/lib/intake.js +202 -0
  64. package/tools/cli/lib/pack-copy.js +95 -5
  65. package/tools/cli/lib/packs.js +156 -8
  66. package/tools/cli/lib/page-origins.js +582 -0
  67. package/tools/cli/lib/review-rules.js +92 -24
  68. package/tools/cli/lib/review.js +28 -1
  69. package/tools/cli/lib/swarm-deal.js +116 -0
  70. package/tools/cli/lib/swarm-defaults.js +6 -0
  71. package/tools/cli/lib/swarm-tally.js +195 -0
  72. package/tools/cli/lib/swarm-validate.js +589 -0
  73. package/tools/cli/lib/swarm.js +712 -0
  74. package/tools/cli/lib/uat.js +22 -5
  75. package/tools/cli/lib/update-transaction.js +8 -4
@@ -105,3 +105,21 @@ bmad-plus-parallel
105
105
  Or via the Orchestrator capabilities menu: `PL`
106
106
 
107
107
  This skill is automatically invoked by the Autopilot when `{parallel_execution}` is enabled and independent tasks are detected.
108
+
109
+ ## Swarms
110
+
111
+ Parallel stories share one goal and differ in content; a **swarm** repeats one kind of work
112
+ across many units. Four kinds, one runtime (`bmad-plus swarm`), one moderator skill
113
+ (`bmad-plus-swarm`):
114
+
115
+ | Kind | Units | Substrate |
116
+ |---|---|---|
117
+ | `panel` | dealt from a profile by exact quota, seeded | session sub-agents, one per brief, launched a wave at a time |
118
+ | `jury` | listed in the definition, one lens each | session sub-agents |
119
+ | `fanout` | supplied at init, one disjoint write scope each | session sub-agents, or a durable Nexus plan (`swarm plan` → `nexus create`) when the work edits the repository, needs an independent check per unit or must survive the session |
120
+
121
+ The budget is `swarm.budget` in `_bmad/config.yaml` (26 sub-agents per run by default, 10 per
122
+ wave); a larger run is refused with exit code 2 until a human says yes, and paid media generation
123
+ is asked for every time. The runtime deals, briefs, checks and tallies; the Parallel Supervisor
124
+ launches the sub-agents, never answers for a unit, and reports the disclosure sentence before any
125
+ figure. Protocol and contracts: `.agents/skills/bmad-plus-swarm/SKILL.md`.
@@ -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.
@@ -116,6 +116,7 @@ template lacks is a change to the template, made in BMAD+ with its tests.
116
116
  | `storage-explained` | where the answers live and what makes them disappear (site data cleared, private window closed), and that nobody receives them until the JSON is handed over |
117
117
  | `finished-is-not-accepted` · `unload-guard` | unanswered lines are said on the page before finishing (a second click finishes anyway); finishing keeps the date and says it is not an acceptance; leaving with an unsaved run is questioned |
118
118
  | `utf8-and-escaped-diacritics` · `export-is-the-run` | UTF-8 declared, diacritics handled as escapes, and the exported JSON is the run as answered |
119
+ | `no-third-party-requests` | opening the page asks nothing of any origin but its own: no remote font, stylesheet, script or image, so no third party learns who opened it |
119
120
 
120
121
  **Before a person opens the page**, in this order, and say in the delivery which ones ran:
121
122
 
@@ -4,16 +4,17 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>__TITLE__</title>
7
- <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Serif:wght@500;600&family=IBM+Plex+Mono:wght@400;500&display=swap">
8
7
  <style>
9
8
  :root {
10
9
  --bg: #f6f5f2; --card: #ffffff; --ink: #1b1d1f; --muted: #6b6f73; --line: #d9d7d1; --line-strong: #b9b6ae;
11
10
  --accent: #0f6f74; --accent-ink: #ffffff; --accent-soft: #e2f0f0;
12
11
  --ok: #1f7a3a; --ok-soft: #e4f2e8; --ko: #b3261e; --ko-soft: #f8e3e1; --warn: #b26a00; --warn-soft: #fbeeda;
13
12
  --mono-bg: #edece7; --focus: #0f6f74;
14
- --sans: "IBM Plex Sans", "Segoe UI", system-ui, sans-serif;
15
- --serif: "IBM Plex Serif", Georgia, "Times New Roman", serif;
16
- --mono: "IBM Plex Mono", Consolas, "Courier New", monospace;
13
+ /* Installed faces only: the page asks nothing of any origin but its own, and every
14
+ platform's own fonts cover the scripts of every language the page ships. */
15
+ --sans: system-ui, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif;
16
+ --serif: Charter, "Bitstream Charter", "Sitka Text", Cambria, "Noto Serif", "DejaVu Serif", Georgia, serif;
17
+ --mono: ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, "Liberation Mono", "DejaVu Sans Mono", monospace;
17
18
  }
18
19
  @media (prefers-color-scheme: dark) {
19
20
  :root:not([data-theme="light"]) {
@@ -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 {
@@ -155,6 +159,9 @@ function factsFromDerived(derived, packs = derived.packOrder) {
155
159
  ? derived.installerAgents
156
160
  : selected.reduce((total, pack) => total + pack.agentCount, 0),
157
161
  packs: selected,
162
+ dataFlows: (derived.dataHandling || []).filter(
163
+ (flow) => flow.component === 'cli' || packs.includes(flow.component)
164
+ ),
158
165
  };
159
166
  }
160
167
 
@@ -171,6 +178,7 @@ function renderRoster(facts) {
171
178
  const identity = persona.alias
172
179
  ? '(' + persona.alias + ') — ' + persona.role
173
180
  : '(' + persona.role + ')';
181
+ const drives = [...new Set([...(pack.usesSkills || []), ...(persona.usesSkills || [])])];
174
182
  lines.push(
175
183
  '- **' +
176
184
  persona.name +
@@ -178,7 +186,8 @@ function renderRoster(facts) {
178
186
  identity +
179
187
  ' — ' +
180
188
  persona.description +
181
- (pack.required ? '' : ' (' + pack.name + ')')
189
+ (pack.required ? '' : ' (' + pack.name + ')') +
190
+ (drives.length ? ' — drives: ' + drives.join(', ') : '')
182
191
  );
183
192
  }
184
193
  }
@@ -211,12 +220,31 @@ function renderFactsSection(facts) {
211
220
  if (p.subAgentCount) extras.push(`${p.subAgentCount} sub-agents`);
212
221
  if (p.categoryAgentCount) extras.push(`${p.categoryAgentCount} specialized agents`);
213
222
  if (p.workflowCount) extras.push(`${p.workflowCount} workflows`);
223
+ if (p.pipelineCount)
224
+ extras.push(`${p.pipelineCount} pipeline${p.pipelineCount === 1 ? '' : 's'}`);
214
225
  if (p.frameworkCount) extras.push(`${p.frameworkCount} compliance frameworks`);
215
226
  const suffix = extras.length > 0 ? ` — ${extras.join(', ')}` : '';
216
227
  lines.push(
217
228
  ` - ${p.name}${p.required ? ' (required)' : ''}: ${p.agentCount} installer agent${p.agentCount === 1 ? '' : 's'} — ${p.desc}${suffix}`
218
229
  );
219
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
+ }
239
+ if (facts.dataFlows.length)
240
+ lines.push(
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.`
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
+ }
220
248
  return lines;
221
249
  }
222
250
 
@@ -414,6 +442,8 @@ function renderUserSections(derived, { packs, userName = 'Developer', language =
414
442
  '- Load skills from `.agents/skills/`.',
415
443
  '- Each agent has a SKILL.md with capabilities, activation protocol, and role-switching rules.',
416
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.",
417
447
  '',
418
448
  '## Communication',
419
449
  '',
@@ -792,6 +822,7 @@ module.exports = {
792
822
  TOOL_NOTES,
793
823
  validateTargets,
794
824
  buildRegistryFacts,
825
+ factsFromDerived,
795
826
  groupAdaptersByFile,
796
827
  renderFactsSection,
797
828
  renderProjectInstructions,