@team-agent/installer 0.5.84 → 0.5.86

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 (64) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/mod.rs +0 -1
  4. package/crates/team-agent/src/cli/profile.rs +4 -12
  5. package/crates/team-agent/src/cli/tests/verb_profile.rs +66 -0
  6. package/crates/team-agent/src/compiler/tests.rs +13 -24
  7. package/crates/team-agent/src/compiler.rs +1 -50
  8. package/crates/team-agent/src/leader/start.rs +0 -2
  9. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +0 -1
  10. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +0 -1
  11. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +0 -6
  12. package/crates/team-agent/src/lifecycle/launch/identity.rs +0 -1
  13. package/crates/team-agent/src/lifecycle/launch/layout.rs +0 -1
  14. package/crates/team-agent/src/lifecycle/launch/leader_context.rs +0 -1
  15. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +1 -89
  16. package/crates/team-agent/src/lifecycle/launch/ownership.rs +0 -1
  17. package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +0 -3
  18. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +14 -24
  19. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +0 -1
  20. package/crates/team-agent/src/lifecycle/launch/readiness.rs +0 -1
  21. package/crates/team-agent/src/lifecycle/launch/spawn.rs +4 -8
  22. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +0 -1
  23. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +0 -1
  24. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +0 -1
  25. package/crates/team-agent/src/lifecycle/launch.rs +0 -9
  26. package/crates/team-agent/src/lifecycle/profile_launch.rs +1 -1
  27. package/crates/team-agent/src/lifecycle/restart/common.rs +6 -9
  28. package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +1 -3
  29. package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +42 -46
  30. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +209 -52
  31. package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +32 -77
  32. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -13
  33. package/crates/team-agent/src/lifecycle/tests/pi_catalog_red.rs +0 -1
  34. package/crates/team-agent/src/lifecycle/tests/pi_command_red.rs +0 -2
  35. package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +14 -85
  36. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +1 -1
  37. package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +0 -5
  38. package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +0 -1
  39. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +4 -4
  40. package/crates/team-agent/src/lifecycle/tests.rs +0 -1
  41. package/crates/team-agent/src/lifecycle/types.rs +0 -10
  42. package/crates/team-agent/src/lifecycle/worker_command_context.rs +4 -248
  43. package/crates/team-agent/src/messaging/results/collection_tests.rs +719 -0
  44. package/crates/team-agent/src/messaging/results.rs +104 -27
  45. package/crates/team-agent/src/model/enums.rs +0 -35
  46. package/crates/team-agent/src/model/mod.rs +0 -1
  47. package/crates/team-agent/src/model/spec.rs +23 -84
  48. package/crates/team-agent/src/provider/adapter.rs +48 -50
  49. package/crates/team-agent/src/provider/adapters/claude.rs +5 -30
  50. package/crates/team-agent/src/provider/adapters/codex.rs +5 -25
  51. package/crates/team-agent/src/provider/adapters/copilot.rs +9 -42
  52. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +4 -8
  53. package/crates/team-agent/src/provider/adapters/grok.rs +5 -58
  54. package/crates/team-agent/src/provider/adapters/mod.rs +5 -7
  55. package/crates/team-agent/src/provider/adapters/pi.rs +1 -56
  56. package/crates/team-agent/src/provider/bypass_flags.rs +1 -2
  57. package/crates/team-agent/src/provider/session/capture.rs +4 -4
  58. package/crates/team-agent/src/provider/tests/adapter.rs +7 -24
  59. package/crates/team-agent/src/provider/types.rs +1 -1
  60. package/crates/team-agent/src/state/persist.rs +15 -1
  61. package/package.json +4 -4
  62. package/schemas/team.schema.json +4 -9
  63. package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +0 -784
  64. package/crates/team-agent/src/model/permissions.rs +0 -721
@@ -31,7 +31,6 @@ use std::process::Command;
31
31
  use crate::lifecycle::*;
32
32
  use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
33
33
  use crate::model::ids::AgentId;
34
- use crate::model::permissions::{self, AgentPermissionInput};
35
34
  use crate::model::yaml::{self, Value};
36
35
  use crate::state::persist::load_runtime_state;
37
36
  use crate::transport::{PaneField, PaneId, SessionName, Target, Transport, WindowName};
@@ -4434,7 +4433,7 @@ mod fresh_quick_start_leader_binding_tests {
4434
4433
 
4435
4434
 
4436
4435
  #[test]
4437
- fn invalid_pi_tool_category_is_rejected_before_runtime_persistence() {
4436
+ fn pi_tool_category_is_transparently_accepted_by_compiler() {
4438
4437
  let root = std::env::temp_dir().join(format!(
4439
4438
  "ta-quick-start-pi-tools-{}-{}",
4440
4439
  std::process::id(),
@@ -4453,29 +4452,20 @@ mod fresh_quick_start_leader_binding_tests {
4453
4452
  )
4454
4453
  .unwrap();
4455
4454
 
4456
- let transport = crate::transport::test_support::OfflineTransport::new();
4457
- let mut discover = |_requested: &str| -> Result<Vec<String>, ()> { Err(()) };
4458
- let error = quick_start_with_transport_in_workspace_with_display_pi_preflight(
4459
- &root,
4460
- &team,
4461
- None,
4462
- true,
4463
- None,
4464
- &transport,
4465
- false,
4466
- &mut discover,
4467
- )
4468
- .expect_err("invalid Pi category must fail before quick-start persistence");
4469
- let error = error.to_string();
4470
- assert!(error.contains("Pi does not support Team Agent tool category \"provider_builtin\""));
4471
- assert!(error.contains("remove it from the role's tools"));
4472
- assert!(
4473
- !crate::state::persist::runtime_state_path(&root).exists(),
4474
- "quick-start validation failure must not persist runtime state"
4475
- );
4455
+ let spec = crate::compiler::compile_team(&team)
4456
+ .expect("tools metadata must be transparently accepted by the compiler");
4457
+ let worker = spec
4458
+ .get("agents")
4459
+ .and_then(Value::as_list)
4460
+ .and_then(|agents| {
4461
+ agents.iter().find(|agent| {
4462
+ agent.get("id").and_then(Value::as_str) == Some("worker")
4463
+ })
4464
+ })
4465
+ .expect("compiled spec must retain the worker role");
4476
4466
  assert!(
4477
- !root.join(".team/runtime").exists(),
4478
- "quick-start validation failure must not create runtime artifacts"
4467
+ worker.get("tools").is_none(),
4468
+ "tools metadata must not acquire runtime semantics"
4479
4469
  );
4480
4470
  let _ = std::fs::remove_dir_all(root);
4481
4471
  }
@@ -28,7 +28,6 @@ use std::process::Command;
28
28
  use crate::lifecycle::*;
29
29
  use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
30
30
  use crate::model::ids::AgentId;
31
- use crate::model::permissions::{self, AgentPermissionInput};
32
31
  use crate::model::yaml::{self, Value};
33
32
  use crate::state::persist::load_runtime_state;
34
33
  use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
@@ -30,7 +30,6 @@ use std::process::Command;
30
30
  use crate::lifecycle::*;
31
31
  use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
32
32
  use crate::model::ids::AgentId;
33
- use crate::model::permissions::{self, AgentPermissionInput};
34
33
  use crate::model::yaml::{self, Value};
35
34
  use crate::state::persist::load_runtime_state;
36
35
  use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
@@ -32,7 +32,6 @@ use crate::lifecycle::profile_launch::parse_provider;
32
32
  use crate::lifecycle::*;
33
33
  use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
34
34
  use crate::model::ids::AgentId;
35
- use crate::model::permissions::{self, AgentPermissionInput};
36
35
  use crate::model::yaml::{self, Value};
37
36
  use crate::state::persist::load_runtime_state;
38
37
  use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
@@ -123,11 +122,6 @@ pub(super) fn spawn_agents(
123
122
  )?;
124
123
  let system_prompt =
125
124
  crate::lifecycle::worker_command_context::compile_worker_system_prompt(&command_agent)?;
126
- let tools = crate::lifecycle::worker_command_context::resolved_tool_strings_for_command(
127
- &command_agent,
128
- provider,
129
- )?;
130
- let resolved_tool_refs: Vec<&str> = tools.iter().map(String::as_str).collect();
131
125
  // 0.5.66 bypass 单源:per-agent safety 只服务 env 注入与审计,不参与 argv 决策。
132
126
  let safety = effective_runtime_config_for_worker_spawn(agent, provider)?;
133
127
  let mcp_team_id = runtime_team_key_for_spec(spec_path, spec, session_name);
@@ -168,7 +162,10 @@ pub(super) fn spawn_agents(
168
162
  mcp_config: Some(&mcp_config),
169
163
  system_prompt: Some(system_prompt.as_str()),
170
164
  model: command_model,
171
- tools: &resolved_tool_refs,
165
+ dangerously_skip_permissions: matches!(
166
+ agent.get("dangerously_skip_permissions"),
167
+ Some(Value::Bool(true))
168
+ ),
172
169
  profile_launch: Some(&profile_launch),
173
170
  // Layer 1 self-healing (architect probe 2026-06-22): expose
174
171
  // agent_id as a display-name hint so Claude / Copilot
@@ -186,7 +183,6 @@ pub(super) fn spawn_agents(
186
183
  model: command_model,
187
184
  effort: agent_effort,
188
185
  system_prompt: &system_prompt,
189
- tool_categories: &resolved_tool_refs,
190
186
  team_mcp_tools: &["send_message", "report_result"],
191
187
  mcp_config: &mcp_config,
192
188
  session_scope: crate::lifecycle::launch::pi_mcp::PiSessionScope::Isolated,
@@ -62,7 +62,6 @@ use std::process::Command;
62
62
  use crate::lifecycle::*;
63
63
  use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
64
64
  use crate::model::ids::AgentId;
65
- use crate::model::permissions::{self, AgentPermissionInput};
66
65
  use crate::model::yaml::{self, Value};
67
66
  use crate::state::persist::load_runtime_state;
68
67
  use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
@@ -37,7 +37,6 @@ use crate::lifecycle::profile_launch::parse_provider;
37
37
  use crate::lifecycle::*;
38
38
  use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
39
39
  use crate::model::ids::AgentId;
40
- use crate::model::permissions::{self, AgentPermissionInput};
41
40
  use crate::model::yaml::{self, Value};
42
41
  use crate::state::persist::load_runtime_state;
43
42
  use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
@@ -30,7 +30,6 @@ use std::process::Command;
30
30
  use crate::lifecycle::*;
31
31
  use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
32
32
  use crate::model::ids::AgentId;
33
- use crate::model::permissions::{self, AgentPermissionInput};
34
33
  use crate::model::yaml::{self, Value};
35
34
  use crate::state::persist::load_runtime_state;
36
35
  use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
@@ -17,7 +17,6 @@ use std::process::Command;
17
17
 
18
18
  use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
19
19
  use crate::model::ids::AgentId;
20
- use crate::model::permissions::{self, AgentPermissionInput};
21
20
  use crate::model::yaml::{self, Value};
22
21
  use crate::state::persist::load_runtime_state;
23
22
  use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
@@ -133,13 +132,6 @@ pub fn launch_with_transport_in_workspace(
133
132
  session_name.as_str()
134
133
  )));
135
134
  }
136
- let permissions = spec_agents(&spec)
137
- .into_iter()
138
- .map(|agent| PermissionSummary {
139
- agent_id: agent,
140
- raw: serde_json::json!({"source": "compiled_spec"}),
141
- })
142
- .collect::<Vec<_>>();
143
135
  let routes = spec_routes(&spec);
144
136
  let started = if dry_run {
145
137
  Vec::new()
@@ -208,7 +200,6 @@ pub fn launch_with_transport_in_workspace(
208
200
  dry_run,
209
201
  tmux_endpoint: transport.tmux_endpoint(),
210
202
  routes,
211
- permissions,
212
203
  leader_receiver_attached: false,
213
204
  leader_bind_stage: None,
214
205
  leader_bind_reason: None,
@@ -333,7 +333,7 @@ fn push_unique_path(paths: &mut Vec<PathBuf>, path: PathBuf) {
333
333
  }
334
334
  }
335
335
 
336
- fn parse_env_text(text: &str) -> BTreeMap<String, String> {
336
+ pub(crate) fn parse_env_text(text: &str) -> BTreeMap<String, String> {
337
337
  let mut values = BTreeMap::new();
338
338
  for raw in text.lines() {
339
339
  let mut line = raw.trim();
@@ -301,11 +301,6 @@ pub(super) fn spawn_agent_window(
301
301
  )?;
302
302
  let system_prompt =
303
303
  crate::lifecycle::worker_command_context::compile_worker_system_prompt(&command_agent)?;
304
- let tools = crate::lifecycle::worker_command_context::resolved_tool_strings_for_command(
305
- &command_agent,
306
- provider,
307
- )?;
308
- let resolved_tool_refs: Vec<&str> = tools.iter().map(String::as_str).collect();
309
304
  // owner_team_id resolution priority (Issue 2 fix):
310
305
  // 1. caller's explicit override (restart paths pass `selected.team_key`)
311
306
  // 2. agent row's persisted `owner_team_id` (set by prior launch/restart)
@@ -343,10 +338,11 @@ pub(super) fn spawn_agent_window(
343
338
  let mcp_config_path = if provider == crate::provider::Provider::Pi {
344
339
  None
345
340
  } else {
346
- Some(crate::lifecycle::launch::write_worker_mcp_config(
341
+ Some(crate::lifecycle::launch::write_worker_mcp_config_for_provider(
347
342
  workspace,
348
343
  agent_id.as_str(),
349
344
  &mcp_config,
345
+ Some(provider),
350
346
  )?)
351
347
  };
352
348
  let profile_launch =
@@ -373,7 +369,10 @@ pub(super) fn spawn_agent_window(
373
369
  mcp_config: Some(&mcp_config),
374
370
  system_prompt: Some(system_prompt.as_str()),
375
371
  model: command_model,
376
- tools: &resolved_tool_refs,
372
+ dangerously_skip_permissions: agent
373
+ .get("dangerously_skip_permissions")
374
+ .and_then(serde_json::Value::as_bool)
375
+ .unwrap_or(false),
377
376
  profile_launch: Some(&profile_launch),
378
377
  agent_id_hint: Some(agent_id.as_str()),
379
378
  effort: restart_effort,
@@ -398,7 +397,6 @@ pub(super) fn spawn_agent_window(
398
397
  model: command_model,
399
398
  effort: restart_effort,
400
399
  system_prompt: &system_prompt,
401
- tool_categories: &resolved_tool_refs,
402
400
  team_mcp_tools: &["send_message", "report_result"],
403
401
  mcp_config: &mcp_config,
404
402
  session_scope: crate::lifecycle::launch::pi_mcp::PiSessionScope::Isolated,
@@ -743,7 +741,6 @@ fn merge_command_context_fields(
743
741
  };
744
742
  for field in [
745
743
  "role",
746
- "tools",
747
744
  "system_prompt",
748
745
  "output_contract",
749
746
  "provider",
@@ -97,15 +97,13 @@ fn managed_profile_command_plan_omits_mcp_config_even_when_mcp_config_is_availab
97
97
  }
98
98
  }),
99
99
  };
100
- let tools = ["mcp_team"];
101
-
102
100
  let plan = get_adapter(Provider::Claude)
103
101
  .build_command_plan(ProviderCommandContext {
104
102
  auth_mode: AuthMode::CompatibleApi,
105
103
  mcp_config: Some(&config),
106
104
  system_prompt: Some("worker prompt requiring native MCP"),
107
105
  model: Some("profile-effective-haiku"),
108
- tools: &tools,
106
+ dangerously_skip_permissions: false,
109
107
  profile_launch: Some(&profile),
110
108
  agent_id_hint: None,
111
109
  effort: None,
@@ -1,9 +1,9 @@
1
1
  //! ---
2
- //! purpose: 回归——clone-agent 分身 tools 必须与源席集合相等,禁止静默夹成 leader 三件套
2
+ //! purpose: 回归——clone-agent 分身保留源席 tools,编译 spec 不投影 tools
3
3
  //! contract:
4
4
  //! provides:
5
5
  //! - name: clone_agent_preserves_source_tools
6
- //! what: 真跑 clone-agent 后,dynamic-role-files / runtime spec 与源席 tools 集合相等
6
+ //! what: 真跑 clone-agent 后,dynamic-role-files 保留源席 tools,runtime spec 不投影 tools
7
7
  //! requires:
8
8
  //! - name: source-six-set-vs-leader-three-set
9
9
  //! what: 同一 team 里 leader 三件套 + 源席六件套的共享冲突面
@@ -275,6 +275,10 @@ fn role_tools(path: &Path) -> BTreeSet<String> {
275
275
  .collect()
276
276
  }
277
277
 
278
+ fn set_of(items: &[&str]) -> BTreeSet<String> {
279
+ items.iter().map(|s| (*s).to_string()).collect()
280
+ }
281
+
278
282
  fn load_runtime_spec(workspace: &Path) -> Value {
279
283
  let runtime = workspace.join(".team").join("runtime");
280
284
  let spec = std::fs::read_dir(&runtime)
@@ -287,17 +291,7 @@ fn load_runtime_spec(workspace: &Path) -> Value {
287
291
  .expect("parse spec")
288
292
  }
289
293
 
290
- fn yaml_tool_set(node: &Value) -> BTreeSet<String> {
291
- node.get("tools")
292
- .and_then(Value::as_list)
293
- .unwrap_or_else(|| panic!("tools list missing"))
294
- .iter()
295
- .filter_map(Value::as_str)
296
- .map(str::to_string)
297
- .collect()
298
- }
299
-
300
- fn spec_tools(workspace: &Path, agent: &str) -> BTreeSet<String> {
294
+ fn spec_projects_tools(workspace: &Path, agent: &str) -> bool {
301
295
  let parsed = load_runtime_spec(workspace);
302
296
  let agents = parsed
303
297
  .get("agents")
@@ -305,22 +299,18 @@ fn spec_tools(workspace: &Path, agent: &str) -> BTreeSet<String> {
305
299
  .expect("spec agents");
306
300
  for row in agents {
307
301
  if row.get("id").and_then(Value::as_str) == Some(agent) {
308
- return yaml_tool_set(row);
302
+ return row.get("tools").is_some();
309
303
  }
310
304
  }
311
305
  panic!("agent {agent} missing from runtime spec");
312
306
  }
313
307
 
314
- fn leader_spec_tools(workspace: &Path) -> BTreeSet<String> {
315
- yaml_tool_set(
316
- load_runtime_spec(workspace)
317
- .get("leader")
318
- .expect("spec leader"),
319
- )
320
- }
321
-
322
- fn set_of(items: &[&str]) -> BTreeSet<String> {
323
- items.iter().map(|s| (*s).to_string()).collect()
308
+ fn leader_spec_projects_tools(workspace: &Path) -> bool {
309
+ load_runtime_spec(workspace)
310
+ .get("leader")
311
+ .expect("spec leader")
312
+ .get("tools")
313
+ .is_some()
324
314
  }
325
315
 
326
316
  fn clone_ok(case: &Case, source: &str, dest: &str, phase: &str) {
@@ -347,7 +337,7 @@ fn clone_ok(case: &Case, source: &str, dest: &str, phase: &str) {
347
337
  );
348
338
  }
349
339
 
350
- /// clone 后分身 role + spec tools 必须等于源席;三件套源不得被扩成六件。
340
+ /// clone 后分身 role 保留源席 tools;编译 spec 不投影 tools。
351
341
  #[test]
352
342
  fn clone_agent_preserves_source_tools() {
353
343
  let case = Case::start();
@@ -380,21 +370,18 @@ fn clone_agent_preserves_source_tools() {
380
370
  String::from_utf8_lossy(&qs.stdout)
381
371
  );
382
372
 
383
- let source_spec = spec_tools(&case.workspace, SOURCE);
384
- assert_eq!(
385
- source_spec,
386
- set_of(SIX),
387
- "fixture source must be the six-set"
373
+ assert!(
374
+ !spec_projects_tools(&case.workspace, SOURCE),
375
+ "compiled source spec must not project tools metadata"
388
376
  );
389
- let leader_spec = leader_spec_tools(&case.workspace);
390
- assert_eq!(
391
- leader_spec,
392
- set_of(THREE),
393
- "leader must stay the three-set ceiling so the conflict surface exists; leader={leader_spec:?}"
377
+ assert!(
378
+ !leader_spec_projects_tools(&case.workspace),
379
+ "compiled leader spec must not project tools metadata"
394
380
  );
395
381
 
396
382
  clone_ok(&case, SOURCE, CLONE, "first_clone");
397
383
 
384
+ let source_role = role_tools(&case.workspace.join("agents").join(format!("{SOURCE}.md")));
398
385
  let clone_role = role_tools(
399
386
  &case
400
387
  .workspace
@@ -402,23 +389,32 @@ fn clone_agent_preserves_source_tools() {
402
389
  .join("dynamic-role-files")
403
390
  .join(format!("{CLONE}.md")),
404
391
  );
405
- let clone_spec = spec_tools(&case.workspace, CLONE);
406
392
  assert_eq!(
407
- clone_role, source_spec,
408
- "clone role tools must equal source; source={source_spec:?} clone={clone_role:?}"
393
+ clone_role, source_role,
394
+ "clone role tools must equal source; source={source_role:?} clone={clone_role:?}"
409
395
  );
410
- assert_eq!(
411
- clone_spec, source_spec,
412
- "clone spec tools must equal source; source={source_spec:?} spec={clone_spec:?}"
396
+ assert!(
397
+ !spec_projects_tools(&case.workspace, CLONE),
398
+ "compiled clone spec must not project tools metadata"
413
399
  );
414
400
 
415
- let narrow_src = spec_tools(&case.workspace, NARROW);
416
- assert_eq!(narrow_src, set_of(THREE), "narrow source fixture");
401
+ let narrow_source_role = role_tools(&case.workspace.join("agents").join(format!("{NARROW}.md")));
402
+ assert_eq!(narrow_source_role, set_of(THREE), "narrow source fixture");
417
403
  clone_ok(&case, NARROW, NARROW_CLONE, "second_clone");
418
- let narrow_clone = spec_tools(&case.workspace, NARROW_CLONE);
404
+ let narrow_clone_role = role_tools(
405
+ &case
406
+ .workspace
407
+ .join(".team")
408
+ .join("dynamic-role-files")
409
+ .join(format!("{NARROW_CLONE}.md")),
410
+ );
419
411
  assert_eq!(
420
- narrow_clone, narrow_src,
421
- "narrow clone must keep the three-set (preserve, do not widen); got={narrow_clone:?}"
412
+ narrow_clone_role, narrow_source_role,
413
+ "narrow clone role must keep the three-set (preserve, do not widen); got={narrow_clone_role:?}"
414
+ );
415
+ assert!(
416
+ !spec_projects_tools(&case.workspace, NARROW_CLONE),
417
+ "compiled narrow clone spec must not project tools metadata"
422
418
  );
423
419
 
424
420
  case.shutdown();