nmem-cli 0.10.7__tar.gz → 0.10.8__tar.gz

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 (37) hide show
  1. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/Cargo.lock +13 -13
  2. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/Cargo.toml +1 -1
  3. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/PKG-INFO +1 -1
  4. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/session_import.rs +172 -4
  5. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/threads.rs +44 -3
  6. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/pyproject.toml +1 -1
  7. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/README.md +0 -0
  8. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/Cargo.toml +0 -0
  9. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/README.md +0 -0
  10. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/cli.rs +0 -0
  11. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/client.rs +0 -0
  12. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/agents.rs +0 -0
  13. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/communities.rs +0 -0
  14. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/data_transfer.rs +0 -0
  15. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/feed.rs +0 -0
  16. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/fs.rs +0 -0
  17. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/graph.rs +0 -0
  18. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/library.rs +0 -0
  19. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/license.rs +0 -0
  20. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/memories.rs +0 -0
  21. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/memory_relations.rs +0 -0
  22. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/mod.rs +0 -0
  23. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/models.rs +0 -0
  24. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/plugins.rs +0 -0
  25. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/provider.rs +0 -0
  26. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/rules.rs +0 -0
  27. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/service.rs +0 -0
  28. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/skills.rs +0 -0
  29. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/spaces.rs +0 -0
  30. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/stubs.rs +0 -0
  31. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/system.rs +0 -0
  32. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/wiki.rs +0 -0
  33. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/commands/working_memory.rs +0 -0
  34. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/config.rs +0 -0
  35. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/format.rs +0 -0
  36. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/main.rs +0 -0
  37. {nmem_cli-0.10.7 → nmem_cli-0.10.8}/crates/nmem-cli/src/style.rs +0 -0
@@ -5206,7 +5206,7 @@ dependencies = [
5206
5206
 
5207
5207
  [[package]]
5208
5208
  name = "nmem-cli"
5209
- version = "0.10.7"
5209
+ version = "0.10.8"
5210
5210
  dependencies = [
5211
5211
  "anstyle",
5212
5212
  "anyhow",
@@ -5223,7 +5223,7 @@ dependencies = [
5223
5223
 
5224
5224
  [[package]]
5225
5225
  name = "nmem-content"
5226
- version = "0.10.7"
5226
+ version = "0.10.8"
5227
5227
  dependencies = [
5228
5228
  "anyhow",
5229
5229
  "md5",
@@ -5235,7 +5235,7 @@ dependencies = [
5235
5235
 
5236
5236
  [[package]]
5237
5237
  name = "nmem-core"
5238
- version = "0.10.7"
5238
+ version = "0.10.8"
5239
5239
  dependencies = [
5240
5240
  "serde",
5241
5241
  "serde_json",
@@ -5245,7 +5245,7 @@ dependencies = [
5245
5245
 
5246
5246
  [[package]]
5247
5247
  name = "nmem-docs"
5248
- version = "0.10.7"
5248
+ version = "0.10.8"
5249
5249
  dependencies = [
5250
5250
  "criterion",
5251
5251
  "scraper",
@@ -5255,7 +5255,7 @@ dependencies = [
5255
5255
 
5256
5256
  [[package]]
5257
5257
  name = "nmem-embed"
5258
- version = "0.10.7"
5258
+ version = "0.10.8"
5259
5259
  dependencies = [
5260
5260
  "anyhow",
5261
5261
  "arrow-array",
@@ -5275,7 +5275,7 @@ dependencies = [
5275
5275
 
5276
5276
  [[package]]
5277
5277
  name = "nmem-feed"
5278
- version = "0.10.7"
5278
+ version = "0.10.8"
5279
5279
  dependencies = [
5280
5280
  "anyhow",
5281
5281
  "serde_json",
@@ -5284,7 +5284,7 @@ dependencies = [
5284
5284
 
5285
5285
  [[package]]
5286
5286
  name = "nmem-graph"
5287
- version = "0.10.7"
5287
+ version = "0.10.8"
5288
5288
  dependencies = [
5289
5289
  "anyhow",
5290
5290
  "caseless",
@@ -5303,7 +5303,7 @@ dependencies = [
5303
5303
 
5304
5304
  [[package]]
5305
5305
  name = "nmem-harness"
5306
- version = "0.10.7"
5306
+ version = "0.10.8"
5307
5307
  dependencies = [
5308
5308
  "anyhow",
5309
5309
  "futures",
@@ -5323,7 +5323,7 @@ dependencies = [
5323
5323
 
5324
5324
  [[package]]
5325
5325
  name = "nmem-model"
5326
- version = "0.10.7"
5326
+ version = "0.10.8"
5327
5327
  dependencies = [
5328
5328
  "anyhow",
5329
5329
  "encoding_rs",
@@ -5352,7 +5352,7 @@ dependencies = [
5352
5352
 
5353
5353
  [[package]]
5354
5354
  name = "nmem-search"
5355
- version = "0.10.7"
5355
+ version = "0.10.8"
5356
5356
  dependencies = [
5357
5357
  "anyhow",
5358
5358
  "arrow-array",
@@ -5374,7 +5374,7 @@ dependencies = [
5374
5374
 
5375
5375
  [[package]]
5376
5376
  name = "nmem-server"
5377
- version = "0.10.7"
5377
+ version = "0.10.8"
5378
5378
  dependencies = [
5379
5379
  "aes-gcm",
5380
5380
  "anyhow",
@@ -5430,7 +5430,7 @@ dependencies = [
5430
5430
 
5431
5431
  [[package]]
5432
5432
  name = "nmem-tui"
5433
- version = "0.10.7"
5433
+ version = "0.10.8"
5434
5434
  dependencies = [
5435
5435
  "anyhow",
5436
5436
  "arboard",
@@ -5444,7 +5444,7 @@ dependencies = [
5444
5444
 
5445
5445
  [[package]]
5446
5446
  name = "nmem-websearch"
5447
- version = "0.10.7"
5447
+ version = "0.10.8"
5448
5448
  dependencies = [
5449
5449
  "anyhow",
5450
5450
  "dom_smoothie",
@@ -4,7 +4,7 @@ members = ["crates/nmem-cli"]
4
4
 
5
5
  [workspace.package]
6
6
  edition = "2021"
7
- version = "0.10.7"
7
+ version = "0.10.8"
8
8
  license = "UNLICENSED"
9
9
  publish = false
10
10
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nmem-cli
3
- Version: 0.10.7
3
+ Version: 0.10.8
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -884,9 +884,62 @@ fn encode_claude_segment(segment: &str, dot_mode: &str) -> String {
884
884
  }
885
885
  }
886
886
 
887
+ /// True for a Windows drive-letter absolute path (`C:\...`, `d:/...`).
888
+ fn is_windows_drive_path(path: &str) -> bool {
889
+ let bytes = path.as_bytes();
890
+ bytes.len() >= 2 && bytes[0].is_ascii_alphabetic() && bytes[1] == b':'
891
+ }
892
+
893
+ /// Claude Code's Windows directory-naming rule (observed on Windows): every
894
+ /// special character in the absolute path is replaced with a single `-`, with
895
+ /// NO leading `-` (the path already starts with a drive letter). The drive
896
+ /// colon and the trailing separator each map to their own `-`, hence the
897
+ /// characteristic double dash after the drive letter:
898
+ /// `D:\` -> `D--` (`:` -> `-`, `\` -> `-`)
899
+ /// `D:\server-prod-env-setting` -> `D--server-prod-env-setting`
900
+ /// `D:\a\b` -> `D--a-b`
901
+ /// See `.context/attachments/.../claude-code-nmem-stop-hook-bug.md`.
902
+ fn encode_claude_windows_project_path(project_path: &str) -> String {
903
+ project_path
904
+ .chars()
905
+ .map(|c| match c {
906
+ '\\' | '/' | ':' | '.' | '_' => '-',
907
+ other => other,
908
+ })
909
+ .collect()
910
+ }
911
+
912
+ /// Historical single-dash Windows slug: collapse each RUN of special chars to
913
+ /// one `-` (so the drive `:` + `\` becomes a single dash). Used only as a
914
+ /// migration fallback when the correct double-dash dir is absent:
915
+ /// `D:\server-prod-env-setting` -> `D-server-prod-env-setting`
916
+ fn encode_claude_windows_project_path_single_dash(project_path: &str) -> String {
917
+ let mut out = String::with_capacity(project_path.len());
918
+ let mut prev_dash = false;
919
+ for c in project_path.chars() {
920
+ if matches!(c, '\\' | '/' | ':' | '.' | '_') {
921
+ if !prev_dash {
922
+ out.push('-');
923
+ prev_dash = true;
924
+ }
925
+ } else {
926
+ out.push(c);
927
+ prev_dash = false;
928
+ }
929
+ }
930
+ out
931
+ }
932
+
887
933
  fn encode_claude_project_path(project_path: &str, dot_mode: &str) -> String {
888
- // Windows drive form (C:\...) is not handled on this POSIX-focused port;
889
- // the desktop bundle covers Windows hosts.
934
+ // Windows drive paths (`C:\...`) follow Claude Code's own slug rule, which
935
+ // differs from POSIX: no leading `-`, and the drive `:` becomes its own `-`
936
+ // (producing the `D--...` double-dash form). The POSIX segment logic below
937
+ // can't reproduce that because it always prepends a leading `-` and never
938
+ // touches the drive colon. `dot_mode` is irrelevant for the drive form:
939
+ // Claude Code maps every special char (including `.`/`_`) to one `-`.
940
+ if is_windows_drive_path(project_path) {
941
+ return encode_claude_windows_project_path(project_path);
942
+ }
890
943
  let segments: Vec<String> = project_path
891
944
  .replace('\\', "/")
892
945
  .split('/')
@@ -993,11 +1046,19 @@ fn resolve_claude_project_dir(project_path: &str) -> Option<PathBuf> {
993
1046
  let variants = project_path_variants(project_path);
994
1047
  let mut candidate_dirs: Vec<PathBuf> = Vec::new();
995
1048
  for variant in &variants {
996
- for encoded in [
1049
+ let mut encodings = vec![
997
1050
  encode_claude_project_path(variant, "all_as_hyphen"),
998
1051
  encode_claude_project_path(variant, "hidden_only"),
999
1052
  encode_claude_project_path(variant, "preserve"),
1000
- ] {
1053
+ ];
1054
+ // Historical single-dash Windows slug (`D-server-...`): older components
1055
+ // collapsed the drive `:` + separator run into one `-`, so a user
1056
+ // mid-migration may still have transcripts under the single-dash dir.
1057
+ // The double-dash form is primary; this only adds a fallback candidate.
1058
+ if is_windows_drive_path(variant) {
1059
+ encodings.push(encode_claude_windows_project_path_single_dash(variant));
1060
+ }
1061
+ for encoded in encodings {
1001
1062
  let candidate = claude_projects_dir.join(&encoded);
1002
1063
  if !candidate_dirs.contains(&candidate) {
1003
1064
  candidate_dirs.push(candidate);
@@ -1632,9 +1693,46 @@ fn get_cursor_projects_dir() -> PathBuf {
1632
1693
 
1633
1694
  fn encode_cursor_project_path(project_path: &str) -> String {
1634
1695
  let normalized = normpath(&expanduser(project_path).to_string_lossy());
1696
+ // Windows drive paths (`C:\...`, `C:/...`) follow Cursor's own dir-naming
1697
+ // rule, which differs from POSIX: the drive `:` + separator collapses to a
1698
+ // double dash and remaining separators each become one `-`, with no leading
1699
+ // `-` (the encoded name starts with the drive letter). Mirrors the Python
1700
+ // `_encode_cursor_project_path` win32 branch (`":\\" -> "--"`, `"\\" -> "-"`):
1701
+ // C:\Users\me\proj -> C--Users-me-proj
1702
+ // C:/a/b -> C--a-b
1703
+ // `normpath` above only collapses `/`, so a Windows path is untouched by it;
1704
+ // detect the drive form here and slug both separators. Require a real
1705
+ // separator after the drive colon (mirrors the Python `":\\" in ...` guard),
1706
+ // so a bare `C:` never produces a stray trailing dash.
1707
+ if is_windows_drive_path(&normalized)
1708
+ && matches!(normalized.as_bytes().get(2), Some(b'\\') | Some(b'/'))
1709
+ {
1710
+ return encode_cursor_windows_project_path(&normalized);
1711
+ }
1635
1712
  normalized.trim_start_matches('/').replace('/', "-")
1636
1713
  }
1637
1714
 
1715
+ /// Cursor's Windows dir-naming rule: `<drive>:<sep><rest>` becomes
1716
+ /// `<drive>--<rest with each `\`/`/` mapped to `-`>`. Pure/cross-platform so it
1717
+ /// unit-tests on any host. Mirrors the Python win32 branch exactly.
1718
+ fn encode_cursor_windows_project_path(normalized: &str) -> String {
1719
+ // `is_windows_drive_path` guarantees bytes[0] is the drive letter and
1720
+ // bytes[1] == ':'. Everything after the colon is the rest (which begins with
1721
+ // a separator for an absolute drive path). Map the leading `:`+sep to `--`
1722
+ // and any further separator to a single `-`.
1723
+ let drive = &normalized[..1];
1724
+ let after_colon = &normalized[2..]; // skip "<drive>:"
1725
+ let rest: String = after_colon
1726
+ .chars()
1727
+ .map(|c| if c == '\\' || c == '/' { '-' } else { c })
1728
+ .collect();
1729
+ // `after_colon` starts with a separator for `C:\...`, which the map turns
1730
+ // into a leading `-`; combined with the `-` we prepend, that yields the
1731
+ // characteristic `--` after the drive letter. For the bare drive `C:` (no
1732
+ // trailing separator) there is no leading separator, so emit a single `-`.
1733
+ format!("{drive}-{rest}")
1734
+ }
1735
+
1638
1736
  fn cursor_project_name(project_encoded: Option<&str>) -> Option<String> {
1639
1737
  let enc = project_encoded?;
1640
1738
  let parts: Vec<&str> = enc.split('-').filter(|p| !p.is_empty()).collect();
@@ -4848,6 +4946,76 @@ mod tests {
4848
4946
  );
4849
4947
  }
4850
4948
 
4949
+ #[test]
4950
+ fn claude_encode_windows_drive_double_dash() {
4951
+ // Claude Code's real Windows dir rule: every special char -> one `-`,
4952
+ // no leading `-`. The drive `:` + separator produce the double dash.
4953
+ // dot_mode must be irrelevant for the drive form.
4954
+ for mode in ["all_as_hyphen", "hidden_only", "preserve"] {
4955
+ assert_eq!(encode_claude_project_path("D:\\", mode), "D--", "mode={mode}");
4956
+ assert_eq!(
4957
+ encode_claude_project_path("D:\\server-prod-env-setting", mode),
4958
+ "D--server-prod-env-setting",
4959
+ "mode={mode}"
4960
+ );
4961
+ assert_eq!(encode_claude_project_path("D:\\a\\b", mode), "D--a-b", "mode={mode}");
4962
+ // Forward-slash drive form is equivalent.
4963
+ assert_eq!(encode_claude_project_path("C:/a/b", mode), "C--a-b", "mode={mode}");
4964
+ }
4965
+ // POSIX path must keep the leading `-` and existing dot handling.
4966
+ assert_eq!(
4967
+ encode_claude_project_path("/Users/x/proj", "all_as_hyphen"),
4968
+ "-Users-x-proj"
4969
+ );
4970
+ }
4971
+
4972
+ #[test]
4973
+ fn claude_windows_single_dash_migration_variant() {
4974
+ assert_eq!(
4975
+ encode_claude_windows_project_path_single_dash("D:\\server-prod-env-setting"),
4976
+ "D-server-prod-env-setting"
4977
+ );
4978
+ assert_eq!(
4979
+ encode_claude_windows_project_path_single_dash("D:\\"),
4980
+ "D-"
4981
+ );
4982
+ assert_eq!(
4983
+ encode_claude_windows_project_path_single_dash("D:\\a\\b"),
4984
+ "D-a-b"
4985
+ );
4986
+ }
4987
+
4988
+ #[test]
4989
+ fn cursor_encode_windows_drive_double_dash() {
4990
+ // Cursor's real Windows dir rule (Python `_encode_cursor_project_path`
4991
+ // win32 branch): drive `:`+sep -> `--`, remaining sep -> `-`, no leading
4992
+ // `-`. Pure transform, so it must produce the right slug on any host.
4993
+ assert_eq!(
4994
+ encode_cursor_windows_project_path("C:\\Users\\me\\proj"),
4995
+ "C--Users-me-proj"
4996
+ );
4997
+ assert_eq!(encode_cursor_windows_project_path("D:\\a\\b"), "D--a-b");
4998
+ // Forward-slash drive form is equivalent (normpath leaves `\` alone but
4999
+ // a `/`-style Windows input can still reach here).
5000
+ assert_eq!(encode_cursor_windows_project_path("C:/a/b"), "C--a-b");
5001
+ // The public entrypoint routes drive paths through the Windows branch
5002
+ // and POSIX paths through the original slug (leading `-` stripped).
5003
+ assert_eq!(
5004
+ encode_cursor_project_path("C:\\Users\\me\\proj"),
5005
+ "C--Users-me-proj"
5006
+ );
5007
+ assert_eq!(encode_cursor_project_path("/Users/me/proj"), "Users-me-proj");
5008
+ }
5009
+
5010
+ #[test]
5011
+ fn detects_windows_drive_paths() {
5012
+ assert!(is_windows_drive_path("D:\\"));
5013
+ assert!(is_windows_drive_path("c:/x"));
5014
+ assert!(!is_windows_drive_path("/Users/x"));
5015
+ assert!(!is_windows_drive_path("relative/path"));
5016
+ assert!(!is_windows_drive_path("."));
5017
+ }
5018
+
4851
5019
  #[test]
4852
5020
  fn sanitize_lowercases_and_replaces() {
4853
5021
  assert_eq!(sanitize_session_id("Sess_ABC 123!"), "sess_abc-123-");
@@ -1048,7 +1048,35 @@ fn abs_lexical(project: &str) -> String {
1048
1048
  .unwrap_or_default()
1049
1049
  .join(expanded)
1050
1050
  };
1051
- abs.to_string_lossy().to_string()
1051
+ // `PathBuf::join(".")` does not normalize, so `abs_lexical(".")` on Windows
1052
+ // yields `D:\.` (a trailing `.` component). That trailing dot then slugs
1053
+ // wrong (an extra separator in the Claude Code dir name). Strip a trailing
1054
+ // `.` component lexically, honoring both separators. `current_dir()` is
1055
+ // already canonical, so this only removes the artifact introduced by joining
1056
+ // the literal default ".".
1057
+ let s = abs.to_string_lossy().to_string();
1058
+ strip_trailing_dot_component(&s)
1059
+ }
1060
+
1061
+ /// Remove a trailing `\.` or `/.` (a no-op `.` path component) without touching
1062
+ /// the rest of the string. `D:\.` -> `D:\`, `/a/b/.` -> `/a/b`, `D:\` -> `D:\`.
1063
+ fn strip_trailing_dot_component(path: &str) -> String {
1064
+ if let Some(stripped) = path.strip_suffix('.') {
1065
+ // Only strip if the `.` is its own component (preceded by a separator),
1066
+ // never a real name like `foo.` or the parent of `..`.
1067
+ if stripped.ends_with(['/', '\\']) {
1068
+ let trimmed = stripped.trim_end_matches(['/', '\\']);
1069
+ // Keep the root separator: `/.` -> `/`, `D:\.` -> `D:\`.
1070
+ if trimmed.is_empty() {
1071
+ return "/".to_string();
1072
+ }
1073
+ if trimmed.ends_with(':') {
1074
+ return format!("{trimmed}\\");
1075
+ }
1076
+ return trimmed.to_string();
1077
+ }
1078
+ }
1079
+ path.to_string()
1052
1080
  }
1053
1081
 
1054
1082
  /// `os.path.basename(path.rstrip('/'))`.
@@ -2034,8 +2062,8 @@ fn urlencode(s: &str) -> String {
2034
2062
  #[cfg(test)]
2035
2063
  mod tests {
2036
2064
  use super::{
2037
- is_simple_message_list, sync_persistence_source, thread_authorship_metadata, urlencode,
2038
- with_stable_external_ids,
2065
+ is_simple_message_list, strip_trailing_dot_component, sync_persistence_source,
2066
+ thread_authorship_metadata, urlencode, with_stable_external_ids,
2039
2067
  };
2040
2068
  use serde_json::json;
2041
2069
 
@@ -2045,6 +2073,19 @@ mod tests {
2045
2073
  assert_eq!(urlencode("codex-019e95ac"), "codex-019e95ac");
2046
2074
  }
2047
2075
 
2076
+ #[test]
2077
+ fn strip_trailing_dot_component_removes_join_artifact() {
2078
+ // `abs_lexical(".")` -> `cwd + "\."` on Windows; the trailing `.` must go.
2079
+ assert_eq!(strip_trailing_dot_component("D:\\."), "D:\\");
2080
+ assert_eq!(strip_trailing_dot_component("D:\\proj\\."), "D:\\proj");
2081
+ assert_eq!(strip_trailing_dot_component("/a/b/."), "/a/b");
2082
+ assert_eq!(strip_trailing_dot_component("/."), "/");
2083
+ // Never touch real names ending in a dot or `..`.
2084
+ assert_eq!(strip_trailing_dot_component("D:\\foo."), "D:\\foo.");
2085
+ assert_eq!(strip_trailing_dot_component("D:\\proj"), "D:\\proj");
2086
+ assert_eq!(strip_trailing_dot_component("/a/b"), "/a/b");
2087
+ }
2088
+
2048
2089
  #[test]
2049
2090
  fn authorship_explicit_wins_and_source_app_is_provenance() {
2050
2091
  // Use a source_app that is also a valid identity-looking string to prove
@@ -13,7 +13,7 @@ build-backend = "maturin"
13
13
 
14
14
  [project]
15
15
  name = "nmem-cli"
16
- version = "0.10.7"
16
+ version = "0.10.8"
17
17
  description = "CLI and TUI for Nowledge Mem - AI memory management"
18
18
  authors = [
19
19
  { name = "Nowledge Labs" },
File without changes