@stofone/dsh-agent-roster 0.9.1 → 0.10.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 (168) hide show
  1. package/CHANGELOG.md +71 -1
  2. package/README.md +78 -4
  3. package/README.zh.md +44 -1
  4. package/bin/cli.mjs +228 -2
  5. package/client.js +49 -24
  6. package/docs/roster-v2.md +220 -0
  7. package/examples/dist/core-review-1.0.0.json +160 -0
  8. package/examples/dist/research-0.1.0.json +95 -0
  9. package/examples/roles/core-review-correctness/AGENT.md +20 -0
  10. package/examples/roles/core-review-lead/AGENT.md +23 -0
  11. package/examples/roles/core-review-lead-captain/AGENT.md +13 -0
  12. package/examples/roles/core-review-maintainer/AGENT.md +20 -0
  13. package/examples/roles/research-scout/AGENT.md +16 -0
  14. package/examples/roles/research-verifier/AGENT.md +16 -0
  15. package/examples/rosters/core-review/roster.yaml +39 -0
  16. package/examples/rosters/research/roster.yaml +20 -0
  17. package/examples-orchestration/dist/core-review-1.0.0.json +185 -0
  18. package/examples-orchestration/dist/core-review-1.0.0.tasks.json +76 -0
  19. package/examples-orchestration/roles/fixer/AGENT.md +18 -0
  20. package/examples-orchestration/roles/lead/AGENT.md +18 -0
  21. package/examples-orchestration/roles/reviewer/AGENT.md +23 -0
  22. package/examples-orchestration/roles/reviewer/parts/checklist.md +6 -0
  23. package/examples-orchestration/rosters/core-review/overrides/fixer.md +1 -0
  24. package/examples-orchestration/rosters/core-review/roster.yaml +43 -0
  25. package/index.js +8 -1
  26. package/lib/compile2.js +522 -0
  27. package/lib/format-v2.js +196 -0
  28. package/lib/frontmatter.js +113 -0
  29. package/lib/migrate.js +326 -0
  30. package/lib/plan.js +167 -0
  31. package/lib/registry.js +65 -11
  32. package/lib/role.js +498 -0
  33. package/lib/roster2.js +591 -0
  34. package/lib/run.js +188 -0
  35. package/lib/write.js +14 -8
  36. package/orgs/agent-roster-collective/dist/api-design-1.0.0.json +130 -0
  37. package/orgs/agent-roster-collective/dist/content-ops-1.0.0.json +130 -0
  38. package/orgs/agent-roster-collective/dist/contract-review-1.0.0.json +130 -0
  39. package/orgs/agent-roster-collective/dist/data-quality-1.0.0.json +130 -0
  40. package/orgs/agent-roster-collective/dist/db-migration-1.0.0.json +130 -0
  41. package/orgs/agent-roster-collective/dist/docs-writer-1.0.0.json +130 -0
  42. package/orgs/agent-roster-collective/dist/finance-recon-1.0.0.json +130 -0
  43. package/orgs/agent-roster-collective/dist/frontend-ui-1.0.0.json +130 -0
  44. package/orgs/agent-roster-collective/dist/i18n-l10n-1.0.0.json +130 -0
  45. package/orgs/agent-roster-collective/dist/incident-response-1.0.0.json +130 -0
  46. package/orgs/agent-roster-collective/dist/literature-review-1.0.0.json +130 -0
  47. package/orgs/agent-roster-collective/dist/market-research-1.0.0.json +130 -0
  48. package/orgs/agent-roster-collective/dist/ml-eval-1.0.0.json +130 -0
  49. package/orgs/agent-roster-collective/dist/performance-1.0.0.json +130 -0
  50. package/orgs/agent-roster-collective/dist/product-discovery-1.0.0.json +130 -0
  51. package/orgs/agent-roster-collective/dist/qa-regression-1.0.0.json +130 -0
  52. package/orgs/agent-roster-collective/dist/recruiting-1.0.0.json +130 -0
  53. package/orgs/agent-roster-collective/dist/release-manager-1.0.0.json +130 -0
  54. package/orgs/agent-roster-collective/dist/security-audit-1.0.0.json +132 -0
  55. package/orgs/agent-roster-collective/dist/sre-oncall-1.0.0.json +130 -0
  56. package/orgs/agent-roster-collective/dist/support-triage-1.0.0.json +130 -0
  57. package/orgs/agent-roster-collective/roles/api-design-failure-mode/AGENT.md +19 -0
  58. package/orgs/agent-roster-collective/roles/api-design-lead/AGENT.md +19 -0
  59. package/orgs/agent-roster-collective/roles/api-design-schema/AGENT.md +20 -0
  60. package/orgs/agent-roster-collective/roles/content-ops-distributor/AGENT.md +19 -0
  61. package/orgs/agent-roster-collective/roles/content-ops-editor/AGENT.md +19 -0
  62. package/orgs/agent-roster-collective/roles/content-ops-lead/AGENT.md +19 -0
  63. package/orgs/agent-roster-collective/roles/contract-review-clause-reader/AGENT.md +19 -0
  64. package/orgs/agent-roster-collective/roles/contract-review-lead/AGENT.md +19 -0
  65. package/orgs/agent-roster-collective/roles/contract-review-risk-note/AGENT.md +19 -0
  66. package/orgs/agent-roster-collective/roles/data-quality-lead/AGENT.md +19 -0
  67. package/orgs/agent-roster-collective/roles/data-quality-profiler/AGENT.md +19 -0
  68. package/orgs/agent-roster-collective/roles/data-quality-rule-author/AGENT.md +19 -0
  69. package/orgs/agent-roster-collective/roles/db-migration-lead/AGENT.md +19 -0
  70. package/orgs/agent-roster-collective/roles/db-migration-migrator/AGENT.md +19 -0
  71. package/orgs/agent-roster-collective/roles/db-migration-verifier/AGENT.md +19 -0
  72. package/orgs/agent-roster-collective/roles/docs-writer-example-runner/AGENT.md +19 -0
  73. package/orgs/agent-roster-collective/roles/docs-writer-lead/AGENT.md +19 -0
  74. package/orgs/agent-roster-collective/roles/docs-writer-writer/AGENT.md +19 -0
  75. package/orgs/agent-roster-collective/roles/finance-recon-exception/AGENT.md +19 -0
  76. package/orgs/agent-roster-collective/roles/finance-recon-lead/AGENT.md +19 -0
  77. package/orgs/agent-roster-collective/roles/finance-recon-matcher/AGENT.md +19 -0
  78. package/orgs/agent-roster-collective/roles/frontend-ui-a11y-reviewer/AGENT.md +19 -0
  79. package/orgs/agent-roster-collective/roles/frontend-ui-implementer/AGENT.md +19 -0
  80. package/orgs/agent-roster-collective/roles/frontend-ui-lead/AGENT.md +19 -0
  81. package/orgs/agent-roster-collective/roles/i18n-l10n-lead/AGENT.md +19 -0
  82. package/orgs/agent-roster-collective/roles/i18n-l10n-reviewer/AGENT.md +19 -0
  83. package/orgs/agent-roster-collective/roles/i18n-l10n-translator/AGENT.md +19 -0
  84. package/orgs/agent-roster-collective/roles/incident-response-commander/AGENT.md +19 -0
  85. package/orgs/agent-roster-collective/roles/incident-response-scribe/AGENT.md +19 -0
  86. package/orgs/agent-roster-collective/roles/incident-response-triage/AGENT.md +19 -0
  87. package/orgs/agent-roster-collective/roles/literature-review-critic/AGENT.md +19 -0
  88. package/orgs/agent-roster-collective/roles/literature-review-extractor/AGENT.md +19 -0
  89. package/orgs/agent-roster-collective/roles/literature-review-lead/AGENT.md +19 -0
  90. package/orgs/agent-roster-collective/roles/market-research-lead/AGENT.md +19 -0
  91. package/orgs/agent-roster-collective/roles/market-research-researcher/AGENT.md +19 -0
  92. package/orgs/agent-roster-collective/roles/market-research-synthesizer/AGENT.md +19 -0
  93. package/orgs/agent-roster-collective/roles/ml-eval-dataset/AGENT.md +19 -0
  94. package/orgs/agent-roster-collective/roles/ml-eval-lead/AGENT.md +19 -0
  95. package/orgs/agent-roster-collective/roles/ml-eval-metric/AGENT.md +19 -0
  96. package/orgs/agent-roster-collective/roles/performance-lead/AGENT.md +19 -0
  97. package/orgs/agent-roster-collective/roles/performance-load-tester/AGENT.md +19 -0
  98. package/orgs/agent-roster-collective/roles/performance-profiler/AGENT.md +19 -0
  99. package/orgs/agent-roster-collective/roles/product-discovery-interviewer/AGENT.md +19 -0
  100. package/orgs/agent-roster-collective/roles/product-discovery-lead/AGENT.md +19 -0
  101. package/orgs/agent-roster-collective/roles/product-discovery-skeptic/AGENT.md +19 -0
  102. package/orgs/agent-roster-collective/roles/qa-regression-case-writer/AGENT.md +19 -0
  103. package/orgs/agent-roster-collective/roles/qa-regression-flake-hunter/AGENT.md +19 -0
  104. package/orgs/agent-roster-collective/roles/qa-regression-lead/AGENT.md +19 -0
  105. package/orgs/agent-roster-collective/roles/recruiting-lead/AGENT.md +19 -0
  106. package/orgs/agent-roster-collective/roles/recruiting-scheduler/AGENT.md +19 -0
  107. package/orgs/agent-roster-collective/roles/recruiting-screener/AGENT.md +19 -0
  108. package/orgs/agent-roster-collective/roles/release-manager-changelog/AGENT.md +19 -0
  109. package/orgs/agent-roster-collective/roles/release-manager-lead/AGENT.md +19 -0
  110. package/orgs/agent-roster-collective/roles/release-manager-rollback/AGENT.md +19 -0
  111. package/orgs/agent-roster-collective/roles/security-audit-code-auditor/AGENT.md +19 -0
  112. package/orgs/agent-roster-collective/roles/security-audit-lead/AGENT.md +19 -0
  113. package/orgs/agent-roster-collective/roles/security-audit-threat-modeler/AGENT.md +19 -0
  114. package/orgs/agent-roster-collective/roles/sre-oncall-capacity/AGENT.md +19 -0
  115. package/orgs/agent-roster-collective/roles/sre-oncall-lead/AGENT.md +19 -0
  116. package/orgs/agent-roster-collective/roles/sre-oncall-runbook/AGENT.md +19 -0
  117. package/orgs/agent-roster-collective/roles/support-triage-classifier/AGENT.md +19 -0
  118. package/orgs/agent-roster-collective/roles/support-triage-lead/AGENT.md +19 -0
  119. package/orgs/agent-roster-collective/roles/support-triage-responder/AGENT.md +19 -0
  120. package/orgs/agent-roster-collective/rosters/api-design/roster.yaml +26 -0
  121. package/orgs/agent-roster-collective/rosters/content-ops/roster.yaml +25 -0
  122. package/orgs/agent-roster-collective/rosters/contract-review/roster.yaml +25 -0
  123. package/orgs/agent-roster-collective/rosters/data-quality/roster.yaml +26 -0
  124. package/orgs/agent-roster-collective/rosters/db-migration/roster.yaml +26 -0
  125. package/orgs/agent-roster-collective/rosters/docs-writer/roster.yaml +25 -0
  126. package/orgs/agent-roster-collective/rosters/finance-recon/roster.yaml +25 -0
  127. package/orgs/agent-roster-collective/rosters/frontend-ui/roster.yaml +26 -0
  128. package/orgs/agent-roster-collective/rosters/i18n-l10n/roster.yaml +26 -0
  129. package/orgs/agent-roster-collective/rosters/incident-response/roster.yaml +26 -0
  130. package/orgs/agent-roster-collective/rosters/literature-review/roster.yaml +25 -0
  131. package/orgs/agent-roster-collective/rosters/market-research/roster.yaml +25 -0
  132. package/orgs/agent-roster-collective/rosters/ml-eval/roster.yaml +25 -0
  133. package/orgs/agent-roster-collective/rosters/performance/roster.yaml +25 -0
  134. package/orgs/agent-roster-collective/rosters/product-discovery/roster.yaml +25 -0
  135. package/orgs/agent-roster-collective/rosters/qa-regression/roster.yaml +26 -0
  136. package/orgs/agent-roster-collective/rosters/recruiting/roster.yaml +25 -0
  137. package/orgs/agent-roster-collective/rosters/release-manager/roster.yaml +25 -0
  138. package/orgs/agent-roster-collective/rosters/security-audit/roster.yaml +30 -0
  139. package/orgs/agent-roster-collective/rosters/sre-oncall/roster.yaml +26 -0
  140. package/orgs/agent-roster-collective/rosters/support-triage/roster.yaml +25 -0
  141. package/package.json +3 -1
  142. package/schema/compiled.v1.schema.json +391 -0
  143. package/schema/role.v1.schema.json +240 -0
  144. package/schema/roster.v2.schema.json +437 -0
  145. package/examples/core-review/roster.yaml +0 -82
  146. package/examples/research/roster.yaml +0 -42
  147. package/orgs/agent-roster-collective/api-design/roster.yaml +0 -67
  148. package/orgs/agent-roster-collective/content-ops/roster.yaml +0 -55
  149. package/orgs/agent-roster-collective/contract-review/roster.yaml +0 -58
  150. package/orgs/agent-roster-collective/data-quality/roster.yaml +0 -64
  151. package/orgs/agent-roster-collective/db-migration/roster.yaml +0 -64
  152. package/orgs/agent-roster-collective/docs-writer/roster.yaml +0 -59
  153. package/orgs/agent-roster-collective/finance-recon/roster.yaml +0 -63
  154. package/orgs/agent-roster-collective/frontend-ui/roster.yaml +0 -60
  155. package/orgs/agent-roster-collective/i18n-l10n/roster.yaml +0 -60
  156. package/orgs/agent-roster-collective/incident-response/roster.yaml +0 -65
  157. package/orgs/agent-roster-collective/literature-review/roster.yaml +0 -61
  158. package/orgs/agent-roster-collective/market-research/roster.yaml +0 -60
  159. package/orgs/agent-roster-collective/ml-eval/roster.yaml +0 -63
  160. package/orgs/agent-roster-collective/performance/roster.yaml +0 -63
  161. package/orgs/agent-roster-collective/product-discovery/roster.yaml +0 -55
  162. package/orgs/agent-roster-collective/qa-regression/roster.yaml +0 -64
  163. package/orgs/agent-roster-collective/recruiting/roster.yaml +0 -58
  164. package/orgs/agent-roster-collective/release-manager/roster.yaml +0 -59
  165. package/orgs/agent-roster-collective/security-audit/roster.yaml +0 -65
  166. package/orgs/agent-roster-collective/sre-oncall/roster.yaml +0 -60
  167. package/orgs/agent-roster-collective/support-triage/roster.yaml +0 -58
  168. /package/orgs/agent-roster-collective/{security-audit → rosters/security-audit}/capabilities.yaml +0 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,75 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.10.0] - 2026-11-03
8
+
9
+ ### Roles, rosters and compiled artifacts (v2)
10
+
11
+ - **A roster is no longer one document doing four jobs.** Personas move to `roles/<dir>/AGENT.md`
12
+ (frontmatter for parameters, markdown body for the persona, optional `parts/`), assemblies stay in
13
+ `rosters/<id>/roster.yaml` (members are `name@x.y.z` references, the captain gets its own section),
14
+ and everything consumers read is a self-contained `dist/<id>-<ver>.json`.
15
+ - **Assembly is part of the format**: `executionPrompt = join("\n\n", [body, ...parts, ...append])`,
16
+ implemented once in `lib/frontmatter.js#assemblePrompt`. The compiled artifact records character
17
+ count, block list and SHA-256 per prompt.
18
+ - **The discriminator decides the version** — `dsh-agent-roster/role@1`, `/roster@2`, `/compiled@1`.
19
+ v1 is frozen; two formats never share a directory.
20
+ - **Identity comes from the document**, so the "directory name must equal `id`" rule is replaced by a
21
+ stronger one: role names are unique within a library, and duplicate `name` is an error.
22
+ - **`orchestration` is compiled as a declaration.** Stages and gates are validated (a gate's `after`
23
+ must name real stages, `by` must name a real member), the artifact marks
24
+ `status: "declaration"`, and every non-executed field is listed in `declarations[]`.
25
+ - **New CLI**: `dsh-agent-roster compile --roster <dir> [--roles <dir>] [--write] [--json]`.
26
+ - **Schema alignment tests**: `test/v2-schema.test.js` pins field sets, enums, limits and regexes to
27
+ `lib/format-v2.js`, and `test/examples-orchestration.test.js` compiles `examples-orchestration/` and fails if `dist/` drifts.
28
+ - Design contract: [`docs/roster-v2.md`](./docs/roster-v2.md).
29
+ - **Gates must be runnable.** Every gate carries at least one `verify` entry (`command`, `expectExit`);
30
+ a gate without one is rejected as `gate-unverifiable`, because a gate whose only criterion is a
31
+ sentence is judged by the person signing it.
32
+ - **A construction plan, not an execution.** `compile` now also writes
33
+ `dist/<id>-<ver>.tasks.json` (`plan@1`): stages and gates flattened into cards with dependencies,
34
+ `executor: "captain"`, and `loop.onGateFailure` naming where a failed gate goes back to and how many
35
+ rounds it may take. The plugin never calls `agent_teams_create_task` — the plan is a drawing.
36
+
37
+ ### Migrating what already existed
38
+
39
+ - **`migrate --dir <library> [--write]`** rewrites a v1 library in place: each member's
40
+ `systemPrompt` becomes the body of `roles/<roster>-<member>/AGENT.md`, the member turns into a
41
+ reference, the captain becomes a role file of its own, `capabilities.yaml` moves next to the
42
+ assembly, every roster is compiled into `dist/`, and the old flat directory is removed.
43
+ Without `--write` it only reports what it would do — it is the one command that deletes things.
44
+ - **Role names carry the roster id prefix.** v2 requires role names to be unique per library; a
45
+ library whose 21 rosters all call their captain `lead` cannot have 21 files named `lead`. When the
46
+ captain collides with a member it gets a `-captain` suffix instead of being merged — they are two
47
+ personas and merging them would silently drop one.
48
+ - **This repository's own archives were migrated**: `examples/` (2 rosters, 5 members, one captain)
49
+ and `orgs/agent-roster-collective/` (21 rosters, 63 members) now live as `roles/ + rosters/ + dist/`.
50
+ - `maintainers` accepts handles (`STofone`) rather than the lowercase id pattern — migration must not
51
+ rename a person to satisfy a validator.
52
+ - **Both layouts are readable**: the scanner, the document route and the writer accept
53
+ `<root>/<id>/roster.*` and `<root>/rosters/<id>/roster.*`, so a v1 library keeps working while it is
54
+ being migrated, and the panel shows v2 rosters through the same `rosterViewOf` shape.
55
+
56
+ ### An execution record feeds the next compile
57
+
58
+ - **`run@1`** (`lib/run.js`): rounds → tasks → findings/commands, with unknown fields rejected.
59
+ - **`compile --run <file>`** merges the previous round's failures into the retry card's description
60
+ (and stamps `round` on the plan). A failure that only lives on a red card never reaches the next
61
+ compile — which is how a gate becomes decoration.
62
+ - The record is written by the captain session, not by the plugin: task ids belong to the team.
63
+
64
+ ## [0.9.2] - 2026-11-03
65
+
66
+ ### The member list is a table
67
+
68
+ - **Four columns now line up across rows.** Each member row used to compute its own grid, so the
69
+ name / description / tool policy / model columns drifted from row to row and the list read as
70
+ separate blocks. `.rstr-members` owns the column widths and every row uses `subgrid`.
71
+ - **One line per member at narrow widths**: name and role on the left, the policy pinned right,
72
+ description and model beneath — four lines instead of five, and nothing overlaps.
73
+ - Member notes (aliases, declared skills, inherited fields) span the full row instead of pushing
74
+ the columns around.
75
+
7
76
  ## [0.9.1] - 2026-11-03
8
77
 
9
78
  ### Expansion stops clipping itself
@@ -376,7 +445,8 @@
376
445
  - 工具返回值含 `undefined` 键,运行时按 `value is not lossless JSON` 整次拒绝;缺失值统一写 `null`,
377
446
  且所有返回值先过 `lib/json.js#lossless`。
378
447
 
379
- [Unreleased]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v0.9.1...main
448
+ [Unreleased]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v0.9.2...main
449
+ [0.9.2]: https://github.com/dsh-agent-roster/dsh-agent-roster/releases/tag/v0.9.2
380
450
  [0.9.1]: https://github.com/dsh-agent-roster/dsh-agent-roster/releases/tag/v0.9.1
381
451
  [0.9.0]: https://github.com/dsh-agent-roster/dsh-agent-roster/releases/tag/v0.9.0
382
452
  [0.8.3]: https://github.com/dsh-agent-roster/dsh-agent-roster/releases/tag/v0.8.3
package/README.md CHANGED
@@ -84,7 +84,7 @@ captain: # optional: what the captain (the session that crea
84
84
 
85
85
  JSON works too (`roster.json`); YAML and JSON are probed in the order `roster.yaml`, `roster.yml`, `roster.json`. Exactly one file per directory — a second one is reported, never silently picked.
86
86
 
87
- This package ships two example libraries in [`examples/`](./examples). `rosterDirs` is empty by
87
+ This package ships two example libraries: [`examples/`](./examples) (v2, migrated from the v1 archives this repo grew up with) and [`examples-orchestration/`](./examples-orchestration) (v2, hand-written to show gates and a construction plan). `rosterDirs` is empty by
88
88
  default, meaning the global library above, which is empty in a fresh install — so the panel shows
89
89
  "0 rosters" until you create one in the UI. To look at the examples instead, point the CLI at them:
90
90
 
@@ -221,6 +221,79 @@ This file creates nothing. It does not write `team.json`, does not create member
221
221
  node bin/cli.mjs validate --dir orgs/agent-roster-collective
222
222
  ```
223
223
 
224
+ ## Roles, rosters and compiled artifacts (v2)
225
+
226
+ v1 keeps everything in one `roster.yaml`: the persona sits in a YAML block scalar, the assembly protocol lives in plugin code, the captain is written twice, and identity comes from the directory name. v2 splits that into three layers, one authority each:
227
+
228
+ ```text
229
+ roles/<any-dir>/AGENT.md role: frontmatter is parameters, body is the persona, parts/ optional
230
+ rosters/<id>/roster.yaml assembly: members are references, the captain has its own section
231
+ dist/<id>-<ver>.json compiled: self-contained, hashable, the only thing consumers read
232
+ ```
233
+
234
+ The discriminator decides the version — `dsh-agent-roster/role@1`, `dsh-agent-roster/roster@2`, `dsh-agent-roster/compiled@1`. Two formats never share a directory: v1 is frozen, and `roster migrate` (not yet built) rewrites it in place.
235
+
236
+ Assembly is written into the format rather than hidden in code:
237
+
238
+ ```text
239
+ executionPrompt = join("\n\n", [body, ...parts, ...member.append])
240
+ ```
241
+
242
+ `assemblePrompt()` in `lib/frontmatter.js` is the only implementation, and the compiled artifact records the character count, the block list and a SHA-256 for every prompt, so "which text did this member actually get" is answerable without re-reading the sources.
243
+
244
+ A gate must be runnable, not self-certified: every gate carries at least one `verify` entry (`command` + `expectExit`), and a gate without one is rejected at validation time (`gate-unverifiable`). `requires` stays, but only as prose for the human reading the verdict.
245
+
246
+ `compile` also emits the construction plan — `dist/<id>-<ver>.tasks.json`:
247
+
248
+ ```json
249
+ { "kind": "dsh-agent-roster/plan@1", "executor": "captain",
250
+ "tasks": [ { "id": "build", "kind": "stage", "member": "fixer", "dependencies": [] },
251
+ { "id": "audit", "kind": "stage", "member": "reviewer", "dependencies": ["build"] },
252
+ { "id": "tests-pass", "kind": "gate", "member": "reviewer", "dependencies": ["audit"] } ],
253
+ "loop": { "onGateFailure": { "retry": "build", "maxRounds": 1 } } }
254
+ ```
255
+
256
+ `executor: "captain"` is the whole point: the plugin never calls `agent_teams_create_task`, because writing team state belongs to the captain session. The plan is a drawing the captain builds from, not the plugin building for it.
257
+
258
+ The example library in [`examples-orchestration/`](./examples-orchestration/) compiles end to end:
259
+
260
+ ```bash
261
+ node bin/cli.mjs compile --roster examples-orchestration/rosters/core-review --write
262
+ node bin/cli.mjs compile --roster examples-orchestration/rosters/core-review --json
263
+ ```
264
+
265
+ `orchestration` is part of the format but is compiled with `status: "declaration"`: stages and gates are validated (a gate's `after` must name real stages, `by` must name a real member), yet nothing executes them. Declaring a field is not the same as proving it runs, so the artifact says which is which in `declarations[]`. The full contract — field ownership, assembly semantics, identity and uniqueness, migration rules — is in [`docs/roster-v2.md`](./docs/roster-v2.md); the schemas are [`schema/role.v1.schema.json`](./schema/role.v1.schema.json), [`schema/roster.v2.schema.json`](./schema/roster.v2.schema.json) and [`schema/compiled.v1.schema.json`](./schema/compiled.v1.schema.json).
266
+
267
+ ### From v1 to v2 in one command
268
+
269
+ ```bash
270
+ node bin/cli.mjs migrate --dir examples # 只出计划:要写哪些文件、能不能编译
271
+ node bin/cli.mjs migrate --dir examples --write # 原地重写:roles/ + rosters/ + dist/,删掉旧目录
272
+ ```
273
+
274
+ Migration keeps personas verbatim: a member's `systemPrompt` becomes the body of
275
+ `roles/<roster>-<member>/AGENT.md`, and the member turns into a reference. The captain stops being a
276
+ loose field and becomes a role file of its own — `<roster>-<captain>-captain` when its name collides
277
+ with a member, because those are two different personas and silently merging them would lose one.
278
+
279
+ Role names carry the roster id prefix because v2 requires role names to be **unique per library**,
280
+ and a library whose 21 rosters all call their captain `lead` cannot have 21 files with the same name.
281
+
282
+ ### Feeding a failed round back into the plan
283
+
284
+ `compile --run <file>` reads an execution record (`run@1`) and appends the previous round's failures
285
+ to the retry card's description:
286
+
287
+ ```bash
288
+ node bin/cli.mjs compile --roster examples-orchestration/rosters/core-review \
289
+ --run examples-orchestration/dist/core-review-1.0.0.run.json
290
+ ```
291
+
292
+ The record is written by whoever executes the plan (the captain session), not by the plugin: a task
293
+ id belongs to the team, and the plugin never writes team state. What it does own is the merge — a
294
+ failure that only exists on a red card never reaches the next compile, which is exactly how a gate
295
+ turns into decoration.
296
+
224
297
  ## Tools
225
298
 
226
299
  | Tool | Purpose |
@@ -378,9 +451,10 @@ lib/ format, parse, validate, registry, bridge, verify, render, lib
378
451
  client.js client plugin: sidebar entry, main panel (rosters/teams/health), settings section
379
452
  locale/ panel + listing dictionaries (en/zh)
380
453
  schema/ roster / capabilities / organization / registry v1 schemas (editor completion; kept aligned by tests)
381
- bin/cli.mjs validate/list/version for CI
454
+ bin/cli.mjs validate/list/compile/migrate/registry for CI
382
455
  scripts/ host-smoke.mjs, client-smoke.mjs, check-locale.mjs (dev only, not shipped)
383
- examples/ two example libraries
456
+ examples/ a v2 library migrated from the v1 archives
457
+ examples-orchestration/ a v2 library with orchestration, gates and a construction plan
384
458
  orgs/ a worked example library with its own organization.yaml (21 rosters)
385
459
  test/ unit tests
386
460
  ```
@@ -389,7 +463,7 @@ test/ unit tests
389
463
 
390
464
  Verified on this machine:
391
465
 
392
- - `npm test` — 240/240 passing (format, parse, validate, registry, paths, bridge, verify buckets, digests and provenance, roster diff, capability requirements and the capability gate, library organization, registry schema, lossless JSON, write plan/apply, all routes, panel snapshot and hints, dictionary consistency, tool contracts, client loader contract, library snapshot, config view, package manifest, declared fields and captain block, schema/constant alignment, remote registry and one-click install).
466
+ - `npm test` — 286/286 passing (format, parse, validate, registry, paths, bridge, verify buckets, digests and provenance, roster diff, capability requirements and the capability gate, library organization, registry schema, lossless JSON, write plan/apply, all routes, panel snapshot and hints, dictionary consistency, tool contracts, client loader contract, library snapshot, config view, package manifest, declared fields and captain block, schema/constant alignment, the v2 role/roster/compiled layers and their schema alignment, v1→v2 migration, run records feeding the plan, remote registry and one-click install).
393
467
  - `npm run smoke` — `apply()` registers all six tools through a real `defineTool` from a DSH bundle; `roster_list` / `roster_show` / `roster_create` / `roster_verify` / `roster_diff` execute, every output survives `lossless` checks, `path` overrides are honored, effect disposal runs cleanly. The same command then loads `client.js` against a stubbed module table and asserts the three slot registrations, style injection and effect disposal.
394
468
  - CLI: `validate --dir examples` returns exit 0 over both example libraries.
395
469
  - Installed into a real profile with the plugin manager (`plugin_manager install_bundle` against this directory → `link:` dependency + bundle entry); the profile re-reads the linked files on restart.
package/README.zh.md CHANGED
@@ -217,6 +217,49 @@ rosters:
217
217
  node bin/cli.mjs validate --dir orgs/agent-roster-collective
218
218
  ```
219
219
 
220
+ ## 角色 / 组装 / 编译产物(v2)
221
+
222
+ v1 把一切都塞进一份 `roster.yaml`:人设住在 YAML 块标量里,拼装协议藏在插件代码里,队长写两遍,身份靠目录名。v2 把它拆成三层,每层只有一个权威:
223
+
224
+ ```text
225
+ roles/<任意目录>/AGENT.md 角色:frontmatter 是参数,正文是人设,parts/ 可选
226
+ rosters/<id>/roster.yaml 组装:成员是引用,队长独立成段
227
+ dist/<id>-<ver>.json 编译产物:自包含、可哈希,消费者只看它
228
+ ```
229
+
230
+ 判别键决定版本——`dsh-agent-roster/role@1`、`dsh-agent-roster/roster@2`、`dsh-agent-roster/compiled@1`。同一目录不允许两种格式共存:v1 冻结,`roster migrate`(尚未实现)负责原地重写。
231
+
232
+ 拼装写进格式,而不是藏在代码里:
233
+
234
+ ```text
235
+ executionPrompt = join("\n\n", [body, ...parts, ...member.append])
236
+ ```
237
+
238
+ `lib/frontmatter.js` 的 `assemblePrompt()` 是唯一实现;编译产物为每份人设记录字符数、块清单与 SHA-256,所以「这名成员到底拿到了哪段文字」不必回头读源文件就能回答。
239
+
240
+ 门必须能被跑,而不是自己给自己盖章:每个门至少带一条 `verify`(`command` + `expectExit`),没有判据的门在校验阶段就被拒(`gate-unverifiable`)。`requires` 保留,但只是给读结论的人看的说明。
241
+
242
+ `compile` 还会产出施工图 `dist/<id>-<ver>.tasks.json`:
243
+
244
+ ```json
245
+ { "kind": "dsh-agent-roster/plan@1", "executor": "captain",
246
+ "tasks": [ { "id": "build", "kind": "stage", "member": "fixer", "dependencies": [] },
247
+ { "id": "audit", "kind": "stage", "member": "reviewer", "dependencies": ["build"] },
248
+ { "id": "tests-pass", "kind": "gate", "member": "reviewer", "dependencies": ["audit"] } ],
249
+ "loop": { "onGateFailure": { "retry": "build", "maxRounds": 1 } } }
250
+ ```
251
+
252
+ `executor: "captain"` 就是关键:插件从不调用 `agent_teams_create_task`,因为写团队状态的能力属于队长会话。施工图是给队长照着建的图,不是插件替队长建。
253
+
254
+ [`examples-orchestration/`](./examples-orchestration/) 里的示例库可端到端编译:
255
+
256
+ ```bash
257
+ node bin/cli.mjs compile --roster examples-orchestration/rosters/core-review --write
258
+ node bin/cli.mjs compile --roster examples-orchestration/rosters/core-review --json
259
+ ```
260
+
261
+ `orchestration` 是格式的一部分,但编译时标 `status: "declaration"`:阶段与门会被校验(门的 `after` 必须指向真实阶段,`by` 必须指向真实成员),但没有任何东西执行它。能声明不等于已被证明会运行,所以产物在 `declarations[]` 里逐条说明谁属于哪一类。完整契约——字段归宿、拼装语义、身份与唯一性、迁移规则——见 [`docs/roster-v2.md`](./docs/roster-v2.md);schema 见 [`schema/role.v1.schema.json`](./schema/role.v1.schema.json)、[`schema/roster.v2.schema.json`](./schema/roster.v2.schema.json)、[`schema/compiled.v1.schema.json`](./schema/compiled.v1.schema.json)。
262
+
220
263
  ## 工具
221
264
 
222
265
  | 工具 | 作用 |
@@ -374,7 +417,7 @@ test/ 单测
374
417
 
375
418
  本机已验证:
376
419
 
377
- - `npm test` —— 240/240 通过(格式、解析、校验、扫描、路径、桥接、核对四桶、摘要与溯源、档案差异、能力要求与能力闸门、档案库组织、索引 schema、无损 JSON、写入计划与执行、六条路由、快照与提示、两本词典一致、工具契约、客户端加载器契约、包清单、配置视图、样式 token 约束、声明层字段与队长段、schema 与常量对齐)。
420
+ - `npm test` —— 286/286 通过(格式、解析、校验、扫描、路径、桥接、核对四桶、摘要与溯源、档案差异、能力要求与能力闸门、档案库组织、索引 schema、无损 JSON、写入计划与执行、六条路由、快照与提示、两本词典一致、工具契约、客户端加载器契约、包清单、配置视图、样式 token 约束、声明层字段与队长段、schema 与常量对齐、v2 角色/组装/编译三层及其 schema 对齐、v1→v2 迁移、执行回执并入施工图)。
378
421
  - `npm run smoke` —— 用真实 DSH bundle 的 `defineTool` 注册六个工具,逐个调用:输出全部通过无损校验、`path` 覆盖生效、释放干净;同一条命令随后用桩模块表加载 `client.js`,断言三处槽位注册、样式只注入一次、effect 可回收。
379
422
  - CLI:`validate --dir examples` 对两座示例库退出码 0。
380
423
  - 用插件管理器真的装进了本机 profile(`plugin_manager install_bundle` 指向本仓库 → `link:` 依赖 + bundle 条目),重启后按链接读取本仓库文件。
package/bin/cli.mjs CHANGED
@@ -11,8 +11,10 @@
11
11
  * dsh-agent-roster registry validate --file index/registry.json [--json]
12
12
  * dsh-agent-roster registry fetch --file index/registry.json [--local-only]
13
13
  * dsh-agent-roster registry sync --file index/registry.json [--write]
14
+ * dsh-agent-roster compile --roster rosters/money-lab [--roles roles] [--run run.json] [--write] [--json]
15
+ * dsh-agent-roster migrate --dir examples [--write] [--json]
14
16
  */
15
- import { readFileSync, writeFileSync } from "node:fs";
17
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
16
18
  import nodePath from "node:path";
17
19
 
18
20
  import { resolveDirSpec } from "../lib/home.js";
@@ -20,6 +22,10 @@ import { scanRosterLibrary } from "../lib/registry.js";
20
22
  import { renderRosterList } from "../lib/render.js";
21
23
  import { REGISTRY_KIND } from "../lib/format.js";
22
24
  import { fetchCollection, readRegistryFile } from "../lib/remote.js";
25
+ import { compileRosterAt } from "../lib/compile2.js";
26
+ import { migrateLibrary } from "../lib/migrate.js";
27
+ import { compilePlan } from "../lib/plan.js";
28
+ import { mergeRunIntoPlan, validateRun } from "../lib/run.js";
23
29
 
24
30
  const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
25
31
 
@@ -29,6 +35,8 @@ const USAGE = `用法:
29
35
  dsh-agent-roster registry validate --file <路径> 校验公共索引(registry)
30
36
  dsh-agent-roster registry fetch --file <路径> 逐条确认 collection 真的能取到
31
37
  dsh-agent-roster registry sync --file <路径> 按仓库实际内容重算 roster 列表
38
+ dsh-agent-roster compile --roster <目录> 把 v2 组装编译成自包含产物(不写盘)
39
+ dsh-agent-roster migrate --dir <目录> 把 v1 档案原地迁成 v2(roles/ + rosters/ + dist/)
32
40
  dsh-agent-roster --version 输出版本号
33
41
 
34
42
  选项:
@@ -37,7 +45,10 @@ const USAGE = `用法:
37
45
  --file <path> registry 文件(registry 子命令用)
38
46
  --local-only fetch 只验本仓库登记的 collection(CI 里用,不依赖外网)
39
47
  --write sync 把重算结果写回文件
40
- --json 以 JSON 输出(validate 与 list 都支持)
48
+ --roster <path> v2 组装目录(compile 用)
49
+ --run <path> 执行回执(compile 用;把上一轮失败并入重试卡)
50
+ --roles <path> 角色库目录(compile 用;缺省按 rosters/<id> 推同级的 roles/)
51
+ --json 以 JSON 输出(validate / list / compile 都支持)
41
52
  --help 显示本帮助
42
53
  `;
43
54
 
@@ -53,6 +64,9 @@ function parseArgs(argv) {
53
64
  file: null,
54
65
  localOnly: false,
55
66
  write: false,
67
+ roster: null,
68
+ roles: null,
69
+ run: null,
56
70
  };
57
71
  for (let index = 0; index < argv.length; index += 1) {
58
72
  const arg = argv[index];
@@ -71,6 +85,21 @@ function parseArgs(argv) {
71
85
  if (value === undefined) throw new UsageError("--file 需要一个路径");
72
86
  options.file = value;
73
87
  index += 1;
88
+ } else if (arg === "--roster") {
89
+ const value = argv[index + 1];
90
+ if (value === undefined) throw new UsageError("--roster 需要一个路径");
91
+ options.roster = value;
92
+ index += 1;
93
+ } else if (arg === "--run") {
94
+ const value = argv[index + 1];
95
+ if (value === undefined) throw new UsageError("--run 需要一个路径");
96
+ options.run = value;
97
+ index += 1;
98
+ } else if (arg === "--roles") {
99
+ const value = argv[index + 1];
100
+ if (value === undefined) throw new UsageError("--roles 需要一个路径");
101
+ options.roles = value;
102
+ index += 1;
74
103
  } else if (arg === "--known-policy") {
75
104
  const value = argv[index + 1];
76
105
  if (value === undefined) throw new UsageError("--known-policy 需要一个 id");
@@ -112,6 +141,8 @@ async function main(argv) {
112
141
  process.stdout.write(`${pkg.name} ${pkg.version}\n`);
113
142
  return 0;
114
143
  }
144
+ if (options.command === "compile") return runCompile(options);
145
+ if (options.command === "migrate") return runMigrate(options);
115
146
  if (options.command === "registry") {
116
147
  if (options.file === null) {
117
148
  process.stderr.write(`registry 子命令需要 --file\n\n${USAGE}`);
@@ -201,6 +232,136 @@ async function main(argv) {
201
232
  * - `fetch` 确认索引里写的每份档案真的存在(本仓库的走本地文件,其余走网络);
202
233
  * - `sync` 用仓库实际内容重算 `rosters` 与 `generatedAt`,把人肉维护改成机器盖章。
203
234
  */
235
+ /**
236
+ * 编译一份 v2 组装。
237
+ *
238
+ * 三条约定:
239
+ * 1. 默认**不写盘**——编译是纯函数,`--write` 才落 `dist/`;
240
+ * 2. 有 error 就是退出码 1,警告不影响;
241
+ * 3. 产物是消费者唯一入口:桥接层与面板不需要再解析源文件。
242
+ */
243
+ async function runCompile(options) {
244
+ if (options.roster === null) {
245
+ process.stderr.write(`compile 需要 --roster\n\n${USAGE}`);
246
+ return 2;
247
+ }
248
+ const rosterDir = nodePath.resolve(process.cwd(), options.roster);
249
+ const parent = nodePath.dirname(rosterDir);
250
+ const root = options.roles === null && nodePath.basename(parent) === "rosters" ? nodePath.dirname(parent) : null;
251
+ const rolesDir =
252
+ options.roles === null
253
+ ? root === null
254
+ ? nodePath.join(parent, "roles")
255
+ : nodePath.join(root, "roles")
256
+ : nodePath.resolve(process.cwd(), options.roles);
257
+
258
+ const result = compileRosterAt(rosterDir, {
259
+ rolesDir,
260
+ knownToolPolicies: options.knownPolicies.length > 0 ? options.knownPolicies : null,
261
+ source: { repo: ownRepo(), ref: "main" },
262
+ });
263
+
264
+ const diagnostics = [...(result.diagnostics ?? [])];
265
+ const planResult = result.compiled === null ? { plan: null, diagnostics: [] } : compilePlan(result.compiled);
266
+ let plan = planResult.plan;
267
+ diagnostics.push(...planResult.diagnostics);
268
+ let feedback = [];
269
+ if (options.run !== null) {
270
+ const runPath = nodePath.resolve(process.cwd(), options.run);
271
+ let runValue = null;
272
+ try {
273
+ runValue = JSON.parse(readFileSync(runPath, "utf8"));
274
+ } catch (error) {
275
+ diagnostics.push({ severity: "error", code: "run-unreadable", path: "", file: options.run, reason: `读不到执行回执:${error?.message ?? error}` });
276
+ }
277
+ if (runValue !== null) {
278
+ const validation = validateRun(runValue, { file: options.run });
279
+ diagnostics.push(...validation.diagnostics);
280
+ if (validation.ok) {
281
+ const merged = mergeRunIntoPlan(plan, validation.run);
282
+ plan = merged.plan;
283
+ feedback = merged.feedback;
284
+ }
285
+ }
286
+ }
287
+ const errors = diagnostics.filter((item) => item.severity === "error");
288
+
289
+ if (options.json) {
290
+ process.stdout.write(
291
+ `${JSON.stringify(
292
+ {
293
+ plugin: pkg.name,
294
+ version: pkg.version,
295
+ rolesDir,
296
+ rosterDir,
297
+ ok: result.ok,
298
+ compiled: result.compiled,
299
+ plan: plan === null ? null : planResult.plan,
300
+ diagnostics,
301
+ },
302
+ null,
303
+ 2,
304
+ )}\n`,
305
+ );
306
+ return errors.length > 0 ? 1 : 0;
307
+ }
308
+
309
+ if (!result.ok || result.compiled === null) {
310
+ process.stderr.write(`编译失败:${nodePath.basename(rosterDir)}\n`);
311
+ for (const item of diagnostics) process.stderr.write(`${formatDiagnostic(item)}\n`);
312
+ return 1;
313
+ }
314
+
315
+ const compiled = result.compiled;
316
+ const chars = compiled.members.reduce((sum, item) => sum + item.promptStats.chars, 0);
317
+ process.stdout.write(
318
+ `产物 ${compiled.source.roster}@${compiled.source.version} 成员 ${compiled.members.length} ` +
319
+ `人设合计 ${chars} 字符 声明 ${compiled.declarations.length} 项 targets ${compiled.targets.join(",") || "-"}\n`,
320
+ );
321
+ if (compiled.captain !== null) {
322
+ process.stdout.write(` 队长 ${compiled.captain.role ?? "-"} persona ${compiled.captain.personaChars} 字符\n`);
323
+ }
324
+ if (compiled.orchestration !== null) {
325
+ process.stdout.write(
326
+ ` 编排 ${compiled.orchestration.shape ?? "-"} stages ${compiled.orchestration.stages.length} ` +
327
+ `gates ${compiled.orchestration.gates.length} status ${compiled.orchestration.status}\n`,
328
+ );
329
+ }
330
+ if (plan !== null) {
331
+ const gates = plan.tasks.filter((task) => task.kind === "gate").length;
332
+ const loop = plan.loop.onGateFailure;
333
+ process.stdout.write(
334
+ ` 施工图 ${plan.tasks.length} 张卡 门 ${gates} executor ${plan.executor} ` +
335
+ `重试回到 ${loop.retry ?? "-"}(最多 ${loop.maxRounds} 轮)\n`,
336
+ );
337
+ }
338
+ if (feedback.length > 0) {
339
+ process.stdout.write(` 回执 上一轮失败 ${feedback.length} 张卡,已并入重试卡\n`);
340
+ }
341
+ for (const member of compiled.members) {
342
+ process.stdout.write(
343
+ ` ${member.name.padEnd(24)} ${member.role.padEnd(24)} ${String(member.promptStats.chars).padStart(5)} 字符 ` +
344
+ `${member.promptStats.sha256.slice(0, 12)}\n`,
345
+ );
346
+ }
347
+ for (const item of diagnostics) process.stdout.write(`${formatDiagnostic(item)}\n`);
348
+
349
+ if (options.write) {
350
+ const outDir = nodePath.join(root ?? parent, "dist");
351
+ mkdirSync(outDir, { recursive: true });
352
+ const outFile = nodePath.join(outDir, `${compiled.source.roster}-${compiled.source.version}.json`);
353
+ writeFileSync(outFile, `${JSON.stringify(compiled, null, 2)}\n`);
354
+ process.stdout.write(` 已写出 ${nodePath.relative(process.cwd(), outFile) || outFile}\n`);
355
+ if (plan !== null) {
356
+ const planFile = nodePath.join(outDir, `${compiled.source.roster}-${compiled.source.version}.tasks.json`);
357
+ writeFileSync(planFile, `${JSON.stringify(plan, null, 2)}\n`);
358
+ process.stdout.write(` 已写出 ${nodePath.relative(process.cwd(), planFile) || planFile}\n`);
359
+ }
360
+ }
361
+
362
+ return 0;
363
+ }
364
+
204
365
  async function runRegistry(options) {
205
366
  const file = nodePath.resolve(process.cwd(), options.file);
206
367
  const output = (text) => process.stdout.write(`${text}\n`);
@@ -333,3 +494,68 @@ function isOwnCollection(collection, own) {
333
494
  }
334
495
 
335
496
  process.exitCode = await main(process.argv.slice(2));
497
+
498
+ /**
499
+ * `migrate`:把一座 v1 档案库原地迁到 v2 布局。
500
+ *
501
+ * 默认只出计划(要写哪些文件、编译能不能过),`--write` 才真的落盘;这是唯一一个会删旧目录
502
+ * 的命令,所以默认不动手。
503
+ */
504
+ function runMigrate(options) {
505
+ const dirs = options.dirs.length > 0 ? options.dirs : [""];
506
+ const knownToolPolicies = options.knownPolicies.length > 0 ? options.knownPolicies : null;
507
+ let rc = 0;
508
+ const reports = [];
509
+ for (const dir of dirs) {
510
+ const rootDir = resolveDirSpec(dir, process.cwd());
511
+ const report = migrateLibrary(rootDir, { write: options.write, knownToolPolicies, source: { repo: ownRepo(), ref: "main" } });
512
+ reports.push({ dir, rootDir, report });
513
+ if (report.diagnostics.some((item) => item.severity === "error")) rc = 1;
514
+ if (options.json) continue;
515
+ process.stdout.write(`${options.write ? "迁移" : "迁移计划(未写盘)"} ${rootDir}\n`);
516
+ if (report.entries.length === 0) process.stdout.write(" 没有找到 v1 档案(每个子目录里的 roster.yaml)\n");
517
+ for (const entry of report.entries) {
518
+ const extra = [entry.hasCaptain ? "含队长" : null, entry.capabilities ? "能力要求" : null].filter(Boolean).join(" · ");
519
+ process.stdout.write(` ${entry.id}@${entry.version} 成员 ${entry.memberCount} 文件 ${entry.files.length}${extra ? ` ${extra}` : ""}\n`);
520
+ if (!options.write) {
521
+ for (const file of entry.files) process.stdout.write(` + ${file.path}\n`);
522
+ }
523
+ }
524
+ if (options.write) {
525
+ process.stdout.write(` 写入 ${report.written.length} 个文件,删除 ${report.removed.length} 个旧目录,编译 ${report.compiled.length} 份\n`);
526
+ }
527
+ for (const item of report.diagnostics) process.stdout.write(` ${formatDiagnostic(item)}\n`);
528
+ }
529
+ if (options.json) {
530
+ process.stdout.write(
531
+ `${JSON.stringify(
532
+ {
533
+ plugin: pkg.name,
534
+ version: pkg.version,
535
+ write: options.write,
536
+ reports: reports.map((item) => ({
537
+ dir: item.dir,
538
+ rootDir: item.rootDir,
539
+ write: item.report.write,
540
+ entries: item.report.entries.map((entry) => ({
541
+ id: entry.id,
542
+ version: entry.version,
543
+ from: entry.from,
544
+ files: entry.files.map((file) => file.path),
545
+ memberCount: entry.memberCount,
546
+ hasCaptain: entry.hasCaptain,
547
+ capabilities: entry.capabilities,
548
+ })),
549
+ written: item.report.written,
550
+ removed: item.report.removed,
551
+ compiled: item.report.compiled,
552
+ diagnostics: item.report.diagnostics,
553
+ })),
554
+ },
555
+ null,
556
+ 2,
557
+ )}\n`,
558
+ );
559
+ }
560
+ return rc;
561
+ }
package/client.js CHANGED
@@ -468,6 +468,20 @@ window.__ModuleLoader__.load({
468
468
  /* ------------------------------------------------------------------ */
469
469
 
470
470
  function MemberBlock({ member }) {
471
+ // 四列由 .rstr-members 的 subgrid 统一给宽度:姓名 / 说明 / 策略 / 模型。
472
+ // 说明与策略即使为空也占位,否则后面的列会整体左移,表格就对不齐了。
473
+ const notes = [];
474
+ if (Array.isArray(member.aliases) && member.aliases.length > 0) {
475
+ notes.push(`${t("member.aliases")}: ${member.aliases.join(", ")}`);
476
+ }
477
+ if (Array.isArray(member.skills) && member.skills.length > 0) {
478
+ notes.push(`${t("member.skills")}: ${member.skills.join(", ")}(${t("member.skillsHint")})`);
479
+ }
480
+ if (Array.isArray(member.inherited) && member.inherited.length > 0) {
481
+ notes.push(`inherited: ${member.inherited.join(", ")}`);
482
+ }
483
+ if (member.notes) notes.push(member.notes);
484
+
471
485
  return h(
472
486
  "div",
473
487
  { className: "rstr-member" },
@@ -477,33 +491,24 @@ window.__ModuleLoader__.load({
477
491
  h("strong", { className: "rstr-member-name" }, member.name),
478
492
  member.role ? h(Chip, null, member.role) : null,
479
493
  ),
480
- member.description ? h("p", { className: "rstr-member-desc" }, member.description) : null,
481
- Array.isArray(member.aliases) && member.aliases.length > 0
482
- ? h("p", { className: "rstr-muted rstr-small" }, `${t("member.aliases")}: ${member.aliases.join(", ")}`)
483
- : null,
494
+ h("p", { className: "rstr-member-desc" }, member.description ?? ""),
495
+ h(
496
+ "span",
497
+ { className: "rstr-member-policy" },
498
+ member.toolPolicy ? h(Chip, { tone: "policy" }, member.toolPolicy) : null,
499
+ ),
484
500
  h(
485
501
  "div",
486
502
  { className: "rstr-member-meta" },
487
- member.toolPolicy ? h(Chip, { tone: "policy" }, member.toolPolicy) : null,
488
- member.model ? h("span", null, `${t("member.model")}: ${member.model}`) : null,
489
- member.reasoningEffort ? h("span", null, `${t("member.effort")}: ${member.reasoningEffort}`) : null,
503
+ member.model ? h("span", null, member.model) : null,
504
+ member.reasoningEffort ? h("span", null, member.reasoningEffort) : null,
490
505
  member.fallback ? h("span", null, `${t("member.fallback")}: ${member.fallback}`) : null,
491
506
  member.toolScope?.allow?.length
492
507
  ? h("span", null, `${t("member.tools")}: ${member.toolScope.allow.join(", ")}`)
493
508
  : null,
494
509
  h("span", { className: "rstr-muted" }, t("team.promptChars", { count: member.systemPromptChars })),
495
510
  ),
496
- Array.isArray(member.skills) && member.skills.length > 0
497
- ? h(
498
- "p",
499
- { className: "rstr-muted rstr-small" },
500
- `${t("member.skills")}: ${member.skills.join(", ")}(${t("member.skillsHint")})`,
501
- )
502
- : null,
503
- Array.isArray(member.inherited) && member.inherited.length > 0
504
- ? h("p", { className: "rstr-muted rstr-small" }, `inherited: ${member.inherited.join(", ")}`)
505
- : null,
506
- member.notes ? h("p", { className: "rstr-muted rstr-small" }, member.notes) : null,
511
+ notes.map((text, index) => h("p", { key: `note-${index}`, className: "rstr-member-note" }, text)),
507
512
  );
508
513
  }
509
514
 
@@ -2360,21 +2365,41 @@ window.__ModuleLoader__.load({
2360
2365
 
2361
2366
  /* ---- 成员块 ----------------------------------------------------------- */
2362
2367
 
2363
- .rstr-members { display: flex; flex-direction: column; gap: 0; border-top: 1px dashed var(--dsw-alias-border-l1); }
2368
+ /* 成员是一张表:四列由容器统一给宽度(subgrid),否则每一行各算各的网格,
2369
+ 姓名 / 说明 / 策略 / 模型四列对不齐,读起来就是散的。 */
2370
+ .rstr-members {
2371
+ display: grid;
2372
+ grid-template-columns: minmax(120px, 172px) minmax(0, 1fr) 104px 244px;
2373
+ column-gap: 14px;
2374
+ border-top: 1px dashed var(--dsw-alias-border-l1);
2375
+ }
2364
2376
  .rstr-member {
2365
2377
  display: grid;
2366
- grid-template-columns: minmax(120px, 170px) minmax(0, 1fr) auto;
2367
- gap: 6px 16px;
2378
+ grid-column: 1 / -1;
2379
+ grid-template-columns: subgrid;
2380
+ column-gap: 14px;
2381
+ row-gap: 2px;
2368
2382
  align-items: baseline;
2369
- padding: 9px 4px;
2383
+ padding: 10px 4px;
2370
2384
  border-bottom: 1px solid var(--dsw-alias-border-l1);
2371
2385
  min-width: 0;
2372
2386
  }
2373
2387
  .rstr-member:last-child { border-bottom: none; }
2374
2388
  .rstr-member-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; min-width: 0; }
2375
- .rstr-member-name { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary); }
2389
+ .rstr-member-name { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary); overflow: hidden; text-overflow: ellipsis; }
2376
2390
  .rstr-member-desc { margin: 0; font-size: 12px; color: var(--dsw-alias-label-secondary); min-width: 0; }
2377
- .rstr-member-meta { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; font-size: 11px; color: var(--dsw-alias-label-secondary); justify-self: end; font-variant-numeric: tabular-nums; }
2391
+ .rstr-member-policy { justify-self: start; min-width: 0; }
2392
+ .rstr-member-meta { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; font-size: 11px; color: var(--dsw-alias-label-secondary); justify-self: end; font-variant-numeric: tabular-nums; }
2393
+ .rstr-member-note { grid-column: 1 / -1; margin: 0; font-size: 11px; color: var(--dsw-alias-label-secondary); }
2394
+ @media (max-width: 900px) {
2395
+ /* 窄面板:姓名一行、策略贴右;说明与模型各占一整行。 */
2396
+ .rstr-members { grid-template-columns: minmax(0, 1fr) auto; }
2397
+ .rstr-member-head { grid-column: 1; grid-row: 1; }
2398
+ .rstr-member-policy { grid-column: 2; grid-row: 1; justify-self: end; }
2399
+ .rstr-member-desc { grid-column: 1 / -1; grid-row: 2; margin-top: 2px; }
2400
+ .rstr-member-meta { grid-column: 1 / -1; grid-row: 3; justify-self: start; }
2401
+ .rstr-member-note { grid-row: auto; }
2402
+ }
2378
2403
 
2379
2404
  /* ---- 队长块 ----------------------------------------------------------- */
2380
2405