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.
- package/CHANGELOG.md +39 -0
- package/README.md +24 -14
- package/SECURITY.md +63 -1
- package/osint-agent-package/skills/bmad-osint-investigate/osint/scripts/_http.py +68 -24
- package/package.json +4 -1
- package/readme-international/README.de.md +24 -14
- package/readme-international/README.es.md +24 -14
- package/readme-international/README.fr.md +24 -14
- package/src/bmad-plus/agents/agent-orchestrator/SKILL.md +5 -1
- package/src/bmad-plus/data/role-triggers.yaml +34 -0
- package/src/bmad-plus/module-help.csv +2 -0
- package/src/bmad-plus/module.yaml +21 -0
- package/src/bmad-plus/packs/pack-design/SKILL.md +72 -0
- package/src/bmad-plus/packs/pack-design/bmad-skill-manifest.yaml +13 -0
- package/src/bmad-plus/packs/pack-design/design-engine.md +126 -0
- package/src/bmad-plus/packs/pack-design/intake.yaml +105 -0
- package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review.md +3 -1
- package/src/bmad-plus/packs/pack-seo/SKILL.md +3 -1
- package/src/bmad-plus/packs/pack-seo/ref/cwv-thresholds.md +2 -2
- package/src/bmad-plus/packs/pack-seo/requirements.txt +1 -1
- package/src/bmad-plus/packs/pack-seo/scripts/seo_apis.py +72 -30
- package/src/bmad-plus/packs/pack-seo/scripts/seo_report.py +5 -6
- package/src/bmad-plus/packs/pack-seo/scripts/seo_screenshot.py +176 -14
- package/src/bmad-plus/packs/pack-shield/README.md +12 -0
- package/src/bmad-plus/packs/pack-shield/SKILL.md +7 -1
- package/src/bmad-plus/packs/pack-shield/review-rules/access-control.md +10 -0
- package/src/bmad-plus/packs/pack-shield/review-rules/ai-integrations.md +10 -0
- package/src/bmad-plus/packs/pack-shield/review-rules/change-and-supply-chain.md +10 -0
- package/src/bmad-plus/packs/pack-shield/review-rules/cryptography.md +10 -0
- package/src/bmad-plus/packs/pack-shield/review-rules/index.yaml +134 -0
- package/src/bmad-plus/packs/pack-shield/review-rules/logging.md +10 -0
- package/src/bmad-plus/packs/pack-shield/review-rules/personal-data.md +10 -0
- package/src/bmad-plus/packs/pack-shield/shared/ai-processing-register-template.yaml +53 -0
- package/src/bmad-plus/packs/pack-shield/shared/ai-processing-register.md +32 -0
- package/src/bmad-plus/packs/pack-shield/shared/assurance-case-template.yaml +87 -0
- package/src/bmad-plus/packs/pack-shield/shared/assurance-case.md +50 -0
- package/src/bmad-plus/packs/pack-shield/shield-orchestrator.md +24 -1
- package/src/bmad-plus/skills/bmad-plus-autopilot/SKILL.md +7 -0
- package/src/bmad-plus/skills/bmad-plus-intake/SKILL.md +87 -0
- package/src/bmad-plus/skills/bmad-plus-intake/ref/intake-format.md +112 -0
- package/src/bmad-plus/skills/bmad-plus-parallel/SKILL.md +18 -0
- package/src/bmad-plus/skills/bmad-plus-swarm/SKILL.md +115 -0
- package/src/bmad-plus/skills/bmad-plus-swarm/ref/definition.md +188 -0
- package/src/bmad-plus/skills/bmad-plus-uat/SKILL.md +1 -0
- package/src/bmad-plus/skills/bmad-plus-uat/template/page.html +5 -4
- package/tools/build/adapters.config.js +2 -0
- package/tools/build/generate-adapters.js +32 -1
- package/tools/build/generate.js +283 -7
- package/tools/build/intake-contract.js +522 -0
- package/tools/cli/bmad-plus-cli.js +4 -0
- package/tools/cli/commands/ai-register.js +63 -0
- package/tools/cli/commands/assurance.js +162 -0
- package/tools/cli/commands/install.js +28 -1
- package/tools/cli/commands/intake.js +124 -0
- package/tools/cli/commands/review.js +10 -3
- package/tools/cli/commands/swarm.js +119 -0
- package/tools/cli/commands/uninstall.js +17 -0
- package/tools/cli/i18n.js +20 -0
- package/tools/cli/lib/ai-register.js +393 -0
- package/tools/cli/lib/assurance.js +822 -0
- package/tools/cli/lib/control-refs.js +132 -0
- package/tools/cli/lib/installation-health.js +17 -0
- package/tools/cli/lib/intake.js +202 -0
- package/tools/cli/lib/pack-copy.js +95 -5
- package/tools/cli/lib/packs.js +156 -8
- package/tools/cli/lib/page-origins.js +582 -0
- package/tools/cli/lib/review-rules.js +92 -24
- package/tools/cli/lib/review.js +28 -1
- package/tools/cli/lib/swarm-deal.js +116 -0
- package/tools/cli/lib/swarm-defaults.js +6 -0
- package/tools/cli/lib/swarm-tally.js +195 -0
- package/tools/cli/lib/swarm-validate.js +589 -0
- package/tools/cli/lib/swarm.js +712 -0
- package/tools/cli/lib/uat.js +22 -5
- 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
|
-
|
|
15
|
-
|
|
16
|
-
--
|
|
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,
|