@team-agent/installer 0.5.106 → 0.5.107
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/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/tests/base.rs +112 -225
- package/crates/team-agent/src/compiler/tests.rs +46 -59
- package/crates/team-agent/src/leader/tests/byte_findings.rs +1 -14
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +57 -173
- package/crates/team-agent/src/lifecycle/launch/pi_mcp_extension.js +226 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +0 -37
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +0 -9
- package/crates/team-agent/src/lifecycle/tests/fixtures/pi_native_mcp_host.mjs +71 -0
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +0 -66
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +0 -392
- package/crates/team-agent/src/lifecycle/tests/lifecycle_lock.rs +0 -5
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +2 -361
- package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +1 -16
- package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +20 -112
- package/crates/team-agent/src/lifecycle/tests/pi_fork_session_inheritance_red.rs +1 -12
- package/crates/team-agent/src/lifecycle/tests/pi_native_mcp_capability_red.rs +546 -0
- package/crates/team-agent/src/lifecycle/tests.rs +1 -0
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +11 -54
- package/crates/team-agent/src/messaging/tests/wave2.rs +2 -302
- package/crates/team-agent/src/model/name_similarity.rs +52 -67
- package/crates/team-agent/src/model/task_graph.rs +37 -124
- package/crates/team-agent/src/model/yaml/tests.rs +46 -165
- package/crates/team-agent/src/provider/tests/adapter.rs +0 -20
- package/crates/team-agent/src/provider/tests/classify.rs +106 -198
- package/crates/team-agent/src/tmux_backend/tests.rs +178 -501
- package/crates/team-agent/src/transport/tests/wire.rs +0 -55
- package/package.json +4 -4
package/Cargo.lock
CHANGED
package/Cargo.toml
CHANGED
|
@@ -13,33 +13,19 @@ use serde_json::json;
|
|
|
13
13
|
// =========================================================================
|
|
14
14
|
|
|
15
15
|
#[test]
|
|
16
|
-
fn
|
|
17
|
-
// golden:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
vec!["-x".to_string(), "-y".to_string()]
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
#[test]
|
|
34
|
-
fn provider_args_empty_is_empty() {
|
|
35
|
-
// golden: _provider_args([]) == []
|
|
36
|
-
assert_eq!(provider_args(&[]), Vec::<String>::new());
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
#[test]
|
|
40
|
-
fn provider_args_lone_dashdash_yields_empty() {
|
|
41
|
-
// golden: _provider_args(["--"]) == [] (values[1:] of single-elem list)
|
|
42
|
-
assert_eq!(provider_args(&["--".into()]), Vec::<String>::new());
|
|
16
|
+
fn provider_args_cases() {
|
|
17
|
+
// (old case-id, input, Python golden); values[1:] of a lone "--" is empty.
|
|
18
|
+
const CASES: &[(&str, &[&str], &[&str])] = &[
|
|
19
|
+
("provider_args_strips_leading_dashdash", &["--", "-x"], &["-x"]),
|
|
20
|
+
("provider_args_keeps_when_no_leading_dashdash", &["-x", "-y"], &["-x", "-y"]),
|
|
21
|
+
("provider_args_empty_is_empty", &[], &[]),
|
|
22
|
+
("provider_args_lone_dashdash_yields_empty", &["--"], &[]),
|
|
23
|
+
];
|
|
24
|
+
for (id, input, expected) in CASES {
|
|
25
|
+
let argv: Vec<String> = input.iter().map(|arg| (*arg).to_string()).collect();
|
|
26
|
+
let expected: Vec<String> = expected.iter().map(|arg| (*arg).to_string()).collect();
|
|
27
|
+
assert_eq!(provider_args(&argv), expected, "{id}");
|
|
28
|
+
}
|
|
43
29
|
}
|
|
44
30
|
|
|
45
31
|
// =========================================================================
|
|
@@ -47,118 +33,72 @@ fn provider_args_lone_dashdash_yields_empty() {
|
|
|
47
33
|
// =========================================================================
|
|
48
34
|
|
|
49
35
|
#[test]
|
|
50
|
-
fn
|
|
51
|
-
//
|
|
52
|
-
let
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
"--"
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
got.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
#[test]
|
|
97
|
-
fn
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
fn leader_launcher_args_attach_session_equals() {
|
|
117
|
-
// golden: ["--attach-session=mysess"] -> attach_session="mysess"
|
|
118
|
-
let got = leader_launcher_args(&["--attach-session=mysess".into()]).unwrap();
|
|
119
|
-
assert_eq!(got.attach_session, Some("mysess".to_string()));
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
#[test]
|
|
123
|
-
fn leader_launcher_args_dashdash_passthrough_strips_separator() {
|
|
124
|
-
// ["--attach","--","-x","--provider-confirm"] ->
|
|
125
|
-
// provider_args=["-x","--provider-confirm"], attach_existing=True, confirm_attach=False
|
|
126
|
-
// Known Team Agent launcher flags after `--` are rejected by a separate guard.
|
|
127
|
-
let got = leader_launcher_args(&[
|
|
128
|
-
"--attach".into(),
|
|
129
|
-
"--".into(),
|
|
130
|
-
"-x".into(),
|
|
131
|
-
"--provider-confirm".into(),
|
|
132
|
-
])
|
|
133
|
-
.unwrap();
|
|
134
|
-
assert!(got.attach_existing);
|
|
135
|
-
assert!(!got.confirm_attach);
|
|
136
|
-
assert_eq!(
|
|
137
|
-
got.provider_args,
|
|
138
|
-
vec!["-x".to_string(), "--provider-confirm".to_string()]
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
#[test]
|
|
143
|
-
fn leader_launcher_args_unknown_tokens_collect_as_provider_args() {
|
|
144
|
-
// golden: ["foo","--attach","bar"] -> provider_args=["foo","bar"], attach_existing=True
|
|
145
|
-
let got = leader_launcher_args(&["foo".into(), "--attach".into(), "bar".into()]).unwrap();
|
|
146
|
-
assert_eq!(
|
|
147
|
-
got.provider_args,
|
|
148
|
-
vec!["foo".to_string(), "bar".to_string()]
|
|
149
|
-
);
|
|
150
|
-
assert!(got.attach_existing);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
#[test]
|
|
154
|
-
fn leader_launcher_args_attach_session_missing_value_errors() {
|
|
155
|
-
// golden: ["--attach-session"] raises RuntimeError("--attach-session requires a tmux session name")
|
|
156
|
-
let err = leader_launcher_args(&["--attach-session".into()]).unwrap_err();
|
|
157
|
-
let msg = err.to_string();
|
|
158
|
-
assert!(
|
|
159
|
-
msg.contains("--attach-session requires a tmux session name"),
|
|
160
|
-
"expected exact missing-value message, got: {msg}"
|
|
161
|
-
);
|
|
36
|
+
fn leader_launcher_args_success_cases() {
|
|
37
|
+
// (old case-id, argv, complete expected structure), including default false flags.
|
|
38
|
+
let cases: &[(&str, &[&str], LeaderLauncherArgs)] = &[
|
|
39
|
+
("leader_launcher_args_empty_all_default", &[], LeaderLauncherArgs::default()),
|
|
40
|
+
("leader_launcher_args_attach_and_confirm", &["--attach", "--confirm"], LeaderLauncherArgs {
|
|
41
|
+
attach_existing: true,
|
|
42
|
+
confirm_attach: true,
|
|
43
|
+
..Default::default()
|
|
44
|
+
}),
|
|
45
|
+
// --attach-existing is an alias of --attach.
|
|
46
|
+
("leader_launcher_args_attach_existing_alias", &["--attach-existing"], LeaderLauncherArgs {
|
|
47
|
+
attach_existing: true,
|
|
48
|
+
..Default::default()
|
|
49
|
+
}),
|
|
50
|
+
("leader_launcher_args_external_leader_opt_out", &["--external-leader", "--", "--model", "opus"], LeaderLauncherArgs {
|
|
51
|
+
provider_args: vec!["--model".to_string(), "opus".to_string()],
|
|
52
|
+
external_leader: true,
|
|
53
|
+
..Default::default()
|
|
54
|
+
}),
|
|
55
|
+
("leader_launcher_args_attach_session_spaced", &["--attach-session", "mysess"], LeaderLauncherArgs {
|
|
56
|
+
attach_session: Some("mysess".to_string()),
|
|
57
|
+
..Default::default()
|
|
58
|
+
}),
|
|
59
|
+
("leader_launcher_args_attach_session_equals", &["--attach-session=mysess"], LeaderLauncherArgs {
|
|
60
|
+
attach_session: Some("mysess".to_string()),
|
|
61
|
+
..Default::default()
|
|
62
|
+
}),
|
|
63
|
+
// Known Team Agent launcher flags after `--` are rejected by the error table.
|
|
64
|
+
("leader_launcher_args_dashdash_passthrough_strips_separator", &["--attach", "--", "-x", "--provider-confirm"], LeaderLauncherArgs {
|
|
65
|
+
provider_args: vec!["-x".to_string(), "--provider-confirm".to_string()],
|
|
66
|
+
attach_existing: true,
|
|
67
|
+
..Default::default()
|
|
68
|
+
}),
|
|
69
|
+
("leader_launcher_args_unknown_tokens_collect_as_provider_args", &["foo", "--attach", "bar"], LeaderLauncherArgs {
|
|
70
|
+
provider_args: vec!["foo".to_string(), "bar".to_string()],
|
|
71
|
+
attach_existing: true,
|
|
72
|
+
..Default::default()
|
|
73
|
+
}),
|
|
74
|
+
];
|
|
75
|
+
for (id, input, expected) in cases {
|
|
76
|
+
let argv: Vec<String> = input.iter().map(|arg| (*arg).to_string()).collect();
|
|
77
|
+
let got = leader_launcher_args(&argv).unwrap_or_else(|err| panic!("{id}: {err}"));
|
|
78
|
+
assert_eq!(got, *expected, "{id}");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#[test]
|
|
83
|
+
fn leader_launcher_args_error_cases() {
|
|
84
|
+
// Preserve the original error-substring contract, rather than requiring exact equality.
|
|
85
|
+
const CASES: &[(&str, &[&str], &str)] = &[
|
|
86
|
+
(
|
|
87
|
+
"leader_launcher_args_external_leader_after_dashdash_errors",
|
|
88
|
+
&["--", "--external-leader"],
|
|
89
|
+
"Team Agent launcher flag --external-leader must appear before --",
|
|
90
|
+
),
|
|
91
|
+
(
|
|
92
|
+
"leader_launcher_args_attach_session_missing_value_errors",
|
|
93
|
+
&["--attach-session"],
|
|
94
|
+
"--attach-session requires a tmux session name",
|
|
95
|
+
),
|
|
96
|
+
];
|
|
97
|
+
for (id, input, expected) in CASES {
|
|
98
|
+
let argv: Vec<String> = input.iter().map(|arg| (*arg).to_string()).collect();
|
|
99
|
+
let err = leader_launcher_args(&argv).expect_err(id);
|
|
100
|
+
assert!(err.to_string().contains(*expected), "{id}: unexpected error: {err}");
|
|
101
|
+
}
|
|
162
102
|
}
|
|
163
103
|
|
|
164
104
|
// =========================================================================
|
|
@@ -166,37 +106,22 @@ fn leader_launcher_args_attach_session_missing_value_errors() {
|
|
|
166
106
|
// =========================================================================
|
|
167
107
|
|
|
168
108
|
#[test]
|
|
169
|
-
fn
|
|
170
|
-
//
|
|
171
|
-
let
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
#[test]
|
|
188
|
-
fn send_target_broadcast_star() {
|
|
189
|
-
// skeleton contract: bare "*" target -> Broadcast (send.py interprets "*" as全队广播)
|
|
190
|
-
assert_eq!(send_target(None, Some("*")), MessageTarget::Broadcast);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
#[test]
|
|
194
|
-
fn send_target_empty_targets_falls_through_to_target() {
|
|
195
|
-
// golden: targets="" is falsy in Python -> returns args.target ("fallback")
|
|
196
|
-
assert_eq!(
|
|
197
|
-
send_target(Some(""), Some("fallback")),
|
|
198
|
-
MessageTarget::Single("fallback".to_string())
|
|
199
|
-
);
|
|
109
|
+
fn send_target_cases() {
|
|
110
|
+
// (old case-id, targets, target, complete expected enum).
|
|
111
|
+
let cases: &[(&str, Option<&str>, Option<&str>, MessageTarget)] = &[
|
|
112
|
+
(
|
|
113
|
+
"send_target_fanout_strips_and_filters_empty", Some("a, b ,,c"), None,
|
|
114
|
+
MessageTarget::Fanout(vec!["a".to_string(), "b".to_string(), "c".to_string()]),
|
|
115
|
+
),
|
|
116
|
+
("send_target_single_target", None, Some("agent_x"), MessageTarget::Single("agent_x".to_string())),
|
|
117
|
+
// A bare "*" means broadcast to the whole team.
|
|
118
|
+
("send_target_broadcast_star", None, Some("*"), MessageTarget::Broadcast),
|
|
119
|
+
// targets="" is falsy in Python, so it falls through to args.target.
|
|
120
|
+
("send_target_empty_targets_falls_through_to_target", Some(""), Some("fallback"), MessageTarget::Single("fallback".to_string())),
|
|
121
|
+
];
|
|
122
|
+
for (id, targets, target, expected) in cases {
|
|
123
|
+
assert_eq!(send_target(*targets, *target), *expected, "{id}");
|
|
124
|
+
}
|
|
200
125
|
}
|
|
201
126
|
|
|
202
127
|
// =========================================================================
|
|
@@ -205,61 +130,23 @@ fn send_target_empty_targets_falls_through_to_target() {
|
|
|
205
130
|
// =========================================================================
|
|
206
131
|
|
|
207
132
|
#[test]
|
|
208
|
-
fn
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
SummaryBucket::Stopped
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
#[test]
|
|
230
|
-
fn classify_busy() {
|
|
231
|
-
// raw==busy OR hstatus in {running,working} -> Busy
|
|
232
|
-
assert_eq!(classify_agent_bucket("busy", ""), SummaryBucket::Busy);
|
|
233
|
-
assert_eq!(classify_agent_bucket("", "running"), SummaryBucket::Busy);
|
|
234
|
-
assert_eq!(classify_agent_bucket("", "working"), SummaryBucket::Busy);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
#[test]
|
|
238
|
-
fn classify_hstatus_idle_beats_raw_running() {
|
|
239
|
-
// golden: raw=running, h=idle -> idle (hstatus==idle branch precedes raw==running branch)
|
|
240
|
-
assert_eq!(
|
|
241
|
-
classify_agent_bucket("running", "idle"),
|
|
242
|
-
SummaryBucket::Idle
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
#[test]
|
|
247
|
-
fn classify_pure_running() {
|
|
248
|
-
// raw==running, no overriding hstatus -> Running
|
|
249
|
-
assert_eq!(classify_agent_bucket("running", ""), SummaryBucket::Running);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
#[test]
|
|
253
|
-
fn classify_blocked_and_unmatched_are_unknown_never_idle() {
|
|
254
|
-
// bug-071/077/085: blocked/stuck/missing AND any unmatched value -> Unknown, NOT idle.
|
|
255
|
-
assert_eq!(classify_agent_bucket("blocked", ""), SummaryBucket::Unknown);
|
|
256
|
-
assert_eq!(classify_agent_bucket("stuck", ""), SummaryBucket::Unknown);
|
|
257
|
-
assert_eq!(classify_agent_bucket("", "missing"), SummaryBucket::Unknown);
|
|
258
|
-
assert_eq!(
|
|
259
|
-
classify_agent_bucket("weird_value", ""),
|
|
260
|
-
SummaryBucket::Unknown
|
|
261
|
-
);
|
|
262
|
-
assert_eq!(classify_agent_bucket("", ""), SummaryBucket::Unknown);
|
|
133
|
+
fn classify_agent_bucket_cases() {
|
|
134
|
+
// (old case-id, all original (raw, health) inputs, expected bucket).
|
|
135
|
+
const CASES: &[(&str, &[(&str, &str)], SummaryBucket)] = &[
|
|
136
|
+
("classify_failed_takes_priority", &[("failed", ""), ("error", ""), ("running", "error")], SummaryBucket::Failed),
|
|
137
|
+
("classify_stopped", &[("stopped", ""), ("done", ""), ("running", "done")], SummaryBucket::Stopped),
|
|
138
|
+
("classify_busy", &[("busy", ""), ("", "running"), ("", "working")], SummaryBucket::Busy),
|
|
139
|
+
// hstatus==idle precedes the raw==running branch.
|
|
140
|
+
("classify_hstatus_idle_beats_raw_running", &[("running", "idle")], SummaryBucket::Idle),
|
|
141
|
+
("classify_pure_running", &[("running", "")], SummaryBucket::Running),
|
|
142
|
+
// bug-071/077/085: unmatched and blocked/stuck/missing are Unknown, NEVER idle.
|
|
143
|
+
("classify_blocked_and_unmatched_are_unknown_never_idle", &[("blocked", ""), ("stuck", ""), ("", "missing"), ("weird_value", ""), ("", "")], SummaryBucket::Unknown),
|
|
144
|
+
];
|
|
145
|
+
for (id, inputs, expected) in CASES {
|
|
146
|
+
for &(raw, health) in inputs.iter() {
|
|
147
|
+
assert_eq!(classify_agent_bucket(raw, health), *expected, "{id}: raw={raw:?}, health={health:?}");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
263
150
|
}
|
|
264
151
|
|
|
265
152
|
#[test]
|
|
@@ -119,65 +119,52 @@ Implement bounded tasks and report result_envelope_v1.
|
|
|
119
119
|
// ───────────────────────────── read_front_matter ─────────────────────────────
|
|
120
120
|
|
|
121
121
|
#[test]
|
|
122
|
-
fn
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
("
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
let p = write_tmp("unterminated.md", text);
|
|
169
|
-
let (meta, body) = read_front_matter(&p).expect("unterminated front matter is plain text");
|
|
170
|
-
assert_eq!(meta, Value::Map(Vec::new()));
|
|
171
|
-
assert_eq!(body, text);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
#[test]
|
|
175
|
-
fn front_matter_non_object_falls_back_to_plain_text() {
|
|
176
|
-
let text = "---\n- a\n- b\n---\nbody\n";
|
|
177
|
-
let p = write_tmp("list.md", text);
|
|
178
|
-
let (meta, body) = read_front_matter(&p).expect("non-object front matter is plain text");
|
|
179
|
-
assert_eq!(meta, Value::Map(Vec::new()));
|
|
180
|
-
assert_eq!(body, text);
|
|
122
|
+
fn front_matter_cases() {
|
|
123
|
+
struct Case {
|
|
124
|
+
id: &'static str,
|
|
125
|
+
file: &'static str,
|
|
126
|
+
text: &'static str,
|
|
127
|
+
meta: &'static [(&'static str, &'static str)],
|
|
128
|
+
body: &'static str,
|
|
129
|
+
}
|
|
130
|
+
const CASES: &[Case] = &[
|
|
131
|
+
// No leading "---\n" → ({}, text) verbatim (compiler.py:175-176).
|
|
132
|
+
Case {
|
|
133
|
+
id: "front_matter_no_marker_returns_empty_meta_and_full_text", file: "no_marker.md",
|
|
134
|
+
text: "hello\nworld\n", meta: &[], body: "hello\nworld\n",
|
|
135
|
+
},
|
|
136
|
+
Case {
|
|
137
|
+
id: "front_matter_basic_splits_meta_and_lstrips_body", file: "basic.md",
|
|
138
|
+
text: "---\nname: x\nrole: R\n---\n\nbody line\n", meta: &[("name", "x"), ("role", "R")], body: "body line\n",
|
|
139
|
+
},
|
|
140
|
+
// Closing marker at the blank line → raw "" → {}; body "body\n".
|
|
141
|
+
Case {
|
|
142
|
+
id: "front_matter_empty_block_is_empty_map", file: "empty.md",
|
|
143
|
+
text: "---\n\n---\nbody\n", meta: &[], body: "body\n",
|
|
144
|
+
},
|
|
145
|
+
// body.lstrip("\n") removes leading NEWLINES but keeps the 2-space indent.
|
|
146
|
+
Case {
|
|
147
|
+
id: "front_matter_body_lstrip_strips_only_newlines", file: "lstrip.md",
|
|
148
|
+
text: "---\nname: x\n---\n\n\n indented body\n", meta: &[("name", "x")], body: " indented body\n",
|
|
149
|
+
},
|
|
150
|
+
Case {
|
|
151
|
+
id: "front_matter_unterminated_falls_back_to_plain_text", file: "unterminated.md",
|
|
152
|
+
text: "---\nname: x\n", meta: &[], body: "---\nname: x\n",
|
|
153
|
+
},
|
|
154
|
+
Case {
|
|
155
|
+
id: "front_matter_non_object_falls_back_to_plain_text", file: "list.md",
|
|
156
|
+
text: "---\n- a\n- b\n---\nbody\n", meta: &[], body: "---\n- a\n- b\n---\nbody\n",
|
|
157
|
+
},
|
|
158
|
+
];
|
|
159
|
+
for case in CASES {
|
|
160
|
+
let p = write_tmp(case.file, case.text);
|
|
161
|
+
let (meta, body) = read_front_matter(&p).unwrap_or_else(|err| panic!("{}: {err}", case.id));
|
|
162
|
+
let expected_meta = Value::Map(case.meta.iter().map(|(key, value)| {
|
|
163
|
+
((*key).to_string(), Value::Str((*value).to_string()))
|
|
164
|
+
}).collect());
|
|
165
|
+
assert_eq!(meta, expected_meta, "{}: metadata", case.id);
|
|
166
|
+
assert_eq!(body, case.body, "{}: body", case.id);
|
|
167
|
+
}
|
|
181
168
|
}
|
|
182
169
|
|
|
183
170
|
// ───────────────────────────── compile_team: full dict parity ─────────────────────────────
|
|
@@ -54,8 +54,7 @@ fn d1_claim_team_owner_includes_os_user() {
|
|
|
54
54
|
// fingerprint/requested_provider/warning. Golden _receiver_from_claim_target (__init__.py:861-877).
|
|
55
55
|
// Rust LeaderReceiver serializes all 17 (no skip_serializing_if) -> 3 always-null extras leak. RED.
|
|
56
56
|
// (The POPULATED tmux values session_name/window_*/pane_* come from the caller-target scan — a
|
|
57
|
-
// deferred real-tmux seam
|
|
58
|
-
// locked here are unchanged by that scan.)
|
|
57
|
+
// deferred real-tmux seam; the KEY-SET + ORDER locked here are unchanged by that scan.)
|
|
59
58
|
#[test]
|
|
60
59
|
#[serial_test::serial(env)]
|
|
61
60
|
fn d2_claim_leader_receiver_is_fifteen_golden_keys_in_order_no_extras() {
|
|
@@ -98,18 +97,6 @@ fn d2_claim_leader_receiver_is_fifteen_golden_keys_in_order_no_extras() {
|
|
|
98
97
|
], "golden _receiver_from_claim_target 15-key set + ORDER (__init__.py:861-877 + BUG-4 socket-qualified receiver)");
|
|
99
98
|
}
|
|
100
99
|
|
|
101
|
-
// D2 seam — the caller-target SCAN that fills session_name/window_index/window_name/pane_index/
|
|
102
|
-
// pane_tty/pane_current_command from core_list_targets (golden _receiver_from_claim_target reads
|
|
103
|
-
// target[...]). Rust make_receiver leaves them None (no scan). Real-tmux: needs core_list_targets.
|
|
104
|
-
#[test]
|
|
105
|
-
#[ignore = "real-tmux seam: leader_receiver session_name/window_*/pane_* are populated from the \
|
|
106
|
-
caller target via core_list_targets (golden _receiver_from_claim_target); Rust has no \
|
|
107
|
-
scan (values null). Porter wires the target scan; this asserts the populated values."]
|
|
108
|
-
fn d2_receiver_populated_from_caller_target_seam() {
|
|
109
|
-
// Golden contract: new_receiver.session_name == caller_target.session_name, etc. Needs a live
|
|
110
|
-
// tmux target list (core_list_targets) — out of in-process scope.
|
|
111
|
-
}
|
|
112
|
-
|
|
113
100
|
// D3 [BLOCK] — bound_pane is RECEIVER-first (golden :624 receiver.pane_id OR owner.pane_id). PROBE-B
|
|
114
101
|
// (receiver=%9, owner=%1, caller=%9): golden ok=True already_bound. Rust derives OWNER-first
|
|
115
102
|
// (owner %1 != caller %9) -> owner live + !confirm -> refused force_confirm_required. RED.
|