cursorfleet 0.1.0__py3-none-any.whl

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 (101) hide show
  1. cursorfleet/__init__.py +9 -0
  2. cursorfleet/_templates/cursor/agents/architect.md +18 -0
  3. cursorfleet/_templates/cursor/agents/implementer.md +20 -0
  4. cursorfleet/_templates/cursor/agents/patcher.md +17 -0
  5. cursorfleet/_templates/cursor/agents/qa.md +21 -0
  6. cursorfleet/_templates/cursor/agents/reviewer.md +20 -0
  7. cursorfleet/_templates/cursor/agents/scout.md +17 -0
  8. cursorfleet/_templates/cursor/agents/tester.md +20 -0
  9. cursorfleet/_templates/cursor/agents-md/root.md +7 -0
  10. cursorfleet/_templates/cursor/agents-md/work.md +7 -0
  11. cursorfleet/_templates/cursor/config/config.toml +47 -0
  12. cursorfleet/_templates/cursor/hooks/entry.json +4 -0
  13. cursorfleet/_templates/cursor/rules/cursorfleet-core.mdc +10 -0
  14. cursorfleet/_templates/cursor/rules/cursorfleet-handoff.mdc +15 -0
  15. cursorfleet/_templates/cursor/skills/cursorfleet-artifacts/SKILL.md +57 -0
  16. cursorfleet/_templates/cursor/skills/cursorfleet-coordinator/SKILL.md +35 -0
  17. cursorfleet/adapters/__init__.py +0 -0
  18. cursorfleet/adapters/cursor/__init__.py +0 -0
  19. cursorfleet/adapters/cursor/blocks.py +104 -0
  20. cursorfleet/adapters/cursor/diagnostics.py +547 -0
  21. cursorfleet/adapters/cursor/fsutil.py +156 -0
  22. cursorfleet/adapters/cursor/hook_main.py +428 -0
  23. cursorfleet/adapters/cursor/hook_normalize.py +527 -0
  24. cursorfleet/adapters/cursor/hook_policy.py +76 -0
  25. cursorfleet/adapters/cursor/hook_sanitize.py +474 -0
  26. cursorfleet/adapters/cursor/hooksjson.py +305 -0
  27. cursorfleet/adapters/cursor/installer.py +626 -0
  28. cursorfleet/adapters/cursor/kit.py +210 -0
  29. cursorfleet/adapters/cursor/kit_probe.py +54 -0
  30. cursorfleet/adapters/cursor/lock.py +145 -0
  31. cursorfleet/adapters/cursor/templates.py +60 -0
  32. cursorfleet/adapters/cursor/validation.py +365 -0
  33. cursorfleet/adapters/cursor/workspace.py +80 -0
  34. cursorfleet/cli/__init__.py +0 -0
  35. cursorfleet/cli/commands/__init__.py +1 -0
  36. cursorfleet/cli/commands/_common.py +82 -0
  37. cursorfleet/cli/commands/_resolution.py +56 -0
  38. cursorfleet/cli/commands/doctor.py +81 -0
  39. cursorfleet/cli/commands/events.py +197 -0
  40. cursorfleet/cli/commands/index.py +55 -0
  41. cursorfleet/cli/commands/init.py +71 -0
  42. cursorfleet/cli/commands/migrate.py +43 -0
  43. cursorfleet/cli/commands/replay.py +76 -0
  44. cursorfleet/cli/commands/run.py +97 -0
  45. cursorfleet/cli/commands/status.py +210 -0
  46. cursorfleet/cli/commands/tui.py +47 -0
  47. cursorfleet/cli/commands/uninstall.py +73 -0
  48. cursorfleet/cli/commands/validate.py +69 -0
  49. cursorfleet/cli/main.py +81 -0
  50. cursorfleet/config/__init__.py +44 -0
  51. cursorfleet/config/io.py +27 -0
  52. cursorfleet/config/models.py +154 -0
  53. cursorfleet/config/roster.py +245 -0
  54. cursorfleet/events/__init__.py +39 -0
  55. cursorfleet/events/forbidden.py +45 -0
  56. cursorfleet/events/ids.py +109 -0
  57. cursorfleet/events/kinds.py +132 -0
  58. cursorfleet/events/models.py +213 -0
  59. cursorfleet/events/pathcheck.py +45 -0
  60. cursorfleet/git/__init__.py +0 -0
  61. cursorfleet/git/collector.py +226 -0
  62. cursorfleet/git/runner.py +135 -0
  63. cursorfleet/git/worktrees.py +71 -0
  64. cursorfleet/policy/__init__.py +0 -0
  65. cursorfleet/py.typed +0 -0
  66. cursorfleet/safeexe.py +70 -0
  67. cursorfleet/schema_gen.py +73 -0
  68. cursorfleet/state/__init__.py +0 -0
  69. cursorfleet/state/artifact_scan.py +309 -0
  70. cursorfleet/state/context.py +38 -0
  71. cursorfleet/state/event_store.py +227 -0
  72. cursorfleet/state/indexer.py +443 -0
  73. cursorfleet/state/lock.py +74 -0
  74. cursorfleet/state/models.py +268 -0
  75. cursorfleet/state/reducer.py +588 -0
  76. cursorfleet/state/retention.py +218 -0
  77. cursorfleet/state/run_builder.py +136 -0
  78. cursorfleet/state/run_model.py +43 -0
  79. cursorfleet/state/run_store.py +95 -0
  80. cursorfleet/state/runtime.py +645 -0
  81. cursorfleet/state/spool.py +186 -0
  82. cursorfleet/state/spool_read.py +242 -0
  83. cursorfleet/state/status_doc.py +293 -0
  84. cursorfleet/tui/__init__.py +0 -0
  85. cursorfleet/tui/app.py +581 -0
  86. cursorfleet/tui/data.py +545 -0
  87. cursorfleet/tui/filters.py +82 -0
  88. cursorfleet/tui/gates.py +255 -0
  89. cursorfleet/tui/help.py +101 -0
  90. cursorfleet/tui/layout.py +26 -0
  91. cursorfleet/tui/theme.py +176 -0
  92. cursorfleet/tui/views.py +1077 -0
  93. cursorfleet/tui/watch.py +33 -0
  94. cursorfleet/workflow/__init__.py +0 -0
  95. cursorfleet/workflow/artifacts.py +148 -0
  96. cursorfleet/workflow/frontmatter.py +232 -0
  97. cursorfleet-0.1.0.dist-info/METADATA +161 -0
  98. cursorfleet-0.1.0.dist-info/RECORD +101 -0
  99. cursorfleet-0.1.0.dist-info/WHEEL +4 -0
  100. cursorfleet-0.1.0.dist-info/entry_points.txt +3 -0
  101. cursorfleet-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,645 @@
1
+ """Runtime directory resolution and private-file helpers. Stdlib only (hot-path safe).
2
+
3
+ Layout (ADR 0002)::
4
+
5
+ <git-common-dir>/cursorfleet/
6
+ LAYOUT, hmac.key, spool/<session>/<writer>.jsonl, index/state.sqlite,
7
+ locks/indexer.lock, quarantine/
8
+
9
+ PROVISIONAL (ADR 0001 Q4): that the common dir resolves identically from every
10
+ Cursor surface (IDE, CLI, Agents Window worktrees) is unverified.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import os
16
+ import stat
17
+
18
+ LAYOUT_VERSION = "1"
19
+ RUNTIME_DIRNAME = "cursorfleet"
20
+ _MAX_WALK = 64
21
+ _MAX_SMALL_READ = 4096
22
+
23
+ DIR_MODE = 0o700
24
+ FILE_MODE = 0o600
25
+
26
+
27
+ class GitLocation:
28
+ """Where a path sits in git, found by reading ``.git`` files (no subprocess).
29
+
30
+ A plain class on purpose: ``dataclasses`` costs ~12 ms of import time on the hook hot path.
31
+ """
32
+
33
+ __slots__ = ("common_dir", "git_dir", "top_level")
34
+
35
+ def __init__(self, top_level: str, git_dir: str, common_dir: str) -> None:
36
+ self.top_level = top_level # realpath of the directory containing the ``.git`` entry
37
+ self.git_dir = git_dir # realpath of this worktree's git dir (== common for main)
38
+ self.common_dir = common_dir # realpath of the shared git dir
39
+
40
+ def __repr__(self) -> str:
41
+ return f"GitLocation({self.top_level!r}, {self.git_dir!r}, {self.common_dir!r})"
42
+
43
+ def __eq__(self, other: object) -> bool:
44
+ return isinstance(other, GitLocation) and (
45
+ (self.top_level, self.git_dir, self.common_dir)
46
+ == (other.top_level, other.git_dir, other.common_dir)
47
+ )
48
+
49
+ def __hash__(self) -> int:
50
+ return hash((self.top_level, self.git_dir, self.common_dir))
51
+
52
+
53
+ class RuntimePaths:
54
+ """Absolute paths (``str``: no pathlib import on the hot path) in the runtime dir."""
55
+
56
+ __slots__ = ("root",)
57
+
58
+ def __init__(self, root: str) -> None:
59
+ self.root = root
60
+
61
+ @property
62
+ def spool(self) -> str:
63
+ return os.path.join(self.root, "spool")
64
+
65
+ @property
66
+ def index_dir(self) -> str:
67
+ return os.path.join(self.root, "index")
68
+
69
+ @property
70
+ def db(self) -> str:
71
+ return os.path.join(self.index_dir, "state.sqlite")
72
+
73
+ @property
74
+ def locks(self) -> str:
75
+ return os.path.join(self.root, "locks")
76
+
77
+ @property
78
+ def indexer_lock(self) -> str:
79
+ return os.path.join(self.locks, "indexer.lock")
80
+
81
+ @property
82
+ def quarantine(self) -> str:
83
+ return os.path.join(self.root, "quarantine")
84
+
85
+ @property
86
+ def hmac_key(self) -> str:
87
+ return os.path.join(self.root, "hmac.key")
88
+
89
+ @property
90
+ def layout(self) -> str:
91
+ return os.path.join(self.root, "LAYOUT")
92
+
93
+
94
+ def runtime_paths(common_dir: str | os.PathLike[str]) -> RuntimePaths:
95
+ return RuntimePaths(os.path.join(os.fspath(common_dir), RUNTIME_DIRNAME))
96
+
97
+
98
+ def safe_realpath(path: str) -> str:
99
+ """``os.path.realpath`` that still works when ``getcwd`` is unusable.
100
+
101
+ Windows ``ntpath.realpath`` can call ``getcwd`` even for a drive-absolute path.
102
+ If that raises and ``path`` is already absolute, fall back to ``normpath``
103
+ (no further symlink resolution). Never used for relative paths: those stay
104
+ rejected so a broken cwd cannot silently mean "somewhere else".
105
+ """
106
+ try:
107
+ return os.path.realpath(path)
108
+ except OSError:
109
+ if os.path.isabs(path):
110
+ return os.path.normpath(path)
111
+ raise
112
+
113
+
114
+ def _read_small(path: str) -> str:
115
+ with open(path, "rb") as handle:
116
+ return handle.read(_MAX_SMALL_READ).decode("utf-8", "replace")
117
+
118
+
119
+ def _first_line(text: str) -> str:
120
+ lines = text.strip().splitlines()
121
+ return lines[0].strip() if lines else ""
122
+
123
+
124
+ def _resolve_gitfile(entry: str) -> tuple[str, str] | None:
125
+ """Return ``(git_dir, common_dir)`` for a ``.git`` file (linked worktree/submodule)."""
126
+ line = _first_line(_read_small(entry))
127
+ if not line.startswith("gitdir:"):
128
+ return None
129
+ gitdir = line[len("gitdir:") :].strip()
130
+ if not gitdir:
131
+ return None
132
+ if not os.path.isabs(gitdir):
133
+ gitdir = os.path.join(os.path.dirname(entry), gitdir)
134
+ gitdir = safe_realpath(gitdir)
135
+ common = gitdir
136
+ commondir_file = os.path.join(gitdir, "commondir")
137
+ if os.path.isfile(commondir_file):
138
+ value = _first_line(_read_small(commondir_file))
139
+ if value:
140
+ common = safe_realpath(value if os.path.isabs(value) else os.path.join(gitdir, value))
141
+ return gitdir, common
142
+
143
+
144
+ def find_git_location(start: str) -> GitLocation | None: # noqa: PLR0911
145
+ """Walk up from ``start`` looking for ``.git``; resolve it without spawning git.
146
+
147
+ Equivalent to ``git rev-parse --show-toplevel / --git-dir / --git-common-dir`` for
148
+ ordinary checkouts, linked worktrees and submodules (contract-tested against git).
149
+ Returns ``None`` outside a git repo. Never raises.
150
+ """
151
+ if not start or not os.path.isabs(start):
152
+ return None # relative/empty starts would silently mean "the cwd"
153
+ try:
154
+ current = safe_realpath(start)
155
+ for _ in range(_MAX_WALK):
156
+ entry = os.path.join(current, ".git")
157
+ if os.path.lexists(entry):
158
+ if os.path.isdir(entry):
159
+ real = safe_realpath(entry)
160
+ return GitLocation(current, real, real)
161
+ resolved = _resolve_gitfile(entry)
162
+ if resolved is None:
163
+ return None
164
+ return GitLocation(current, resolved[0], resolved[1])
165
+ parent = os.path.dirname(current)
166
+ if parent == current:
167
+ return None
168
+ current = parent
169
+ except (OSError, ValueError):
170
+ return None
171
+ return None
172
+
173
+
174
+ _FILE_ATTRIBUTE_REPARSE_POINT = 0x400
175
+ INSTALL_MARKER_DIR = ".cursorfleet"
176
+ INSTALL_MARKER_NAME = "config.toml"
177
+
178
+
179
+ def _is_link_like(info: os.stat_result) -> bool:
180
+ """True for a symlink or a Windows reparse point (junction), matching ``untrusted_reason``."""
181
+ return bool(
182
+ stat.S_ISLNK(info.st_mode)
183
+ or getattr(info, "st_file_attributes", 0) & _FILE_ATTRIBUTE_REPARSE_POINT
184
+ )
185
+
186
+
187
+ def has_install_marker(top_level: str) -> bool:
188
+ """True when ``<top_level>/.cursorfleet/config.toml`` is a regular file (ADR 0002).
189
+
190
+ The marker must exist before any runtime directory or event file is created.
191
+ A symlink or junction on ``.cursorfleet`` or ``config.toml`` (including a
192
+ link-to-elsewhere) is refused, consistent with runtime path safety.
193
+ Never creates files. Never raises.
194
+ """
195
+ if not top_level:
196
+ return False
197
+ try:
198
+ directory = os.path.join(top_level, INSTALL_MARKER_DIR)
199
+ marker = os.path.join(directory, INSTALL_MARKER_NAME)
200
+ dir_info = os.lstat(directory)
201
+ file_info = os.lstat(marker)
202
+ real = safe_realpath(marker)
203
+ real_info = os.lstat(real)
204
+ root_real = safe_realpath(top_level)
205
+ prefix = root_real if root_real.endswith(os.sep) else root_real + os.sep
206
+ return (
207
+ not _is_link_like(dir_info)
208
+ and stat.S_ISDIR(dir_info.st_mode)
209
+ and not _is_link_like(file_info)
210
+ and stat.S_ISREG(file_info.st_mode)
211
+ and not _is_link_like(real_info)
212
+ and stat.S_ISREG(real_info.st_mode)
213
+ and os.path.normcase(real).startswith(os.path.normcase(prefix))
214
+ )
215
+ except OSError:
216
+ return False
217
+
218
+
219
+ def resolve_inherited_location(start: str) -> GitLocation | None:
220
+ """Nearest Git root from ``start`` if it already has the install marker.
221
+
222
+ Stops at the first ``.git`` directory or gitfile. Does not walk past an inner
223
+ repository boundary when that root has no marker. Creates nothing. Never raises.
224
+ """
225
+ location = find_git_location(start)
226
+ if location is None or not has_install_marker(location.top_level):
227
+ return None
228
+ return location
229
+
230
+
231
+ BOUNDARY_REPOSITORY_ROOT = "repository-root"
232
+ BOUNDARY_ORDINARY_DESCENDANT = "ordinary-descendant"
233
+ BOUNDARY_LINKED_WORKTREE = "linked-worktree"
234
+ BOUNDARY_NESTED_REPOSITORY = "nested-repository"
235
+ BOUNDARY_SUBMODULE = "submodule"
236
+ BOUNDARY_NON_GIT = "non-git"
237
+ BOUNDARY_EXTERNAL_SYMLINK = "external-symlink"
238
+ BOUNDARY_AMBIGUOUS_MULTI_ROOT = "ambiguous-multi-root"
239
+ _MAX_ROOTS = 16
240
+
241
+
242
+ def posix_abs_path(path: str | None) -> str | None:
243
+ """Absolute path with POSIX separators for JSON and matching text output."""
244
+ if path is None:
245
+ return None
246
+ return os.path.normpath(path).replace("\\", "/")
247
+
248
+
249
+ def _same_path(left: str, right: str) -> bool:
250
+ return os.path.normcase(os.path.normpath(left)) == os.path.normcase(os.path.normpath(right))
251
+
252
+
253
+ def is_ambiguous_workspace_roots(raw: object) -> bool:
254
+ """True when ``workspace_roots`` names two or more distinct realpath roots."""
255
+ if not isinstance(raw, list):
256
+ return False
257
+ seen: set[str] = set()
258
+ for item in raw[:_MAX_ROOTS]:
259
+ if not isinstance(item, str) or not item:
260
+ continue
261
+ try:
262
+ real = os.path.normcase(safe_realpath(item))
263
+ except (OSError, ValueError):
264
+ return True
265
+ seen.add(real)
266
+ if len(seen) > 1:
267
+ return True
268
+ return False
269
+
270
+
271
+ def is_external_symlink_anchor(raw: str) -> bool:
272
+ """True when a symlink component of ``raw`` escapes the Git root that contains the link."""
273
+ try:
274
+ current = raw if os.path.isabs(raw) else os.path.join(os.getcwd(), raw)
275
+ current = os.path.abspath(current)
276
+ for _ in range(_MAX_ROOTS * 4):
277
+ if os.path.islink(current):
278
+ parent = os.path.dirname(current)
279
+ parent_loc = find_git_location(parent)
280
+ if parent_loc is not None:
281
+ real = safe_realpath(current)
282
+ top = parent_loc.top_level
283
+ prefix = top if top.endswith(os.sep) else top + os.sep
284
+ real_n, top_n, prefix_n = (
285
+ os.path.normcase(real),
286
+ os.path.normcase(top),
287
+ os.path.normcase(prefix),
288
+ )
289
+ if real_n != top_n and not real_n.startswith(prefix_n):
290
+ return True
291
+ parent = os.path.dirname(current)
292
+ if parent == current:
293
+ break
294
+ current = parent
295
+ except OSError:
296
+ return True
297
+ return False
298
+
299
+
300
+ def _child_git_roots(start: str) -> list[str]:
301
+ """Immediate child directories that have a ``.git`` entry (multi-root parent folder)."""
302
+ found: list[str] = []
303
+ try:
304
+ with os.scandir(start) as entries:
305
+ for entry in entries:
306
+ if not entry.is_dir(follow_symlinks=False):
307
+ continue
308
+ if os.path.lexists(os.path.join(entry.path, ".git")):
309
+ found.append(entry.path)
310
+ if len(found) > 1:
311
+ return found
312
+ except OSError:
313
+ return []
314
+ return found
315
+
316
+
317
+ def _gitfile_boundary(location: GitLocation) -> str:
318
+ entry = os.path.join(location.top_level, ".git")
319
+ try:
320
+ is_file = os.path.isfile(entry) and not os.path.isdir(entry)
321
+ except OSError:
322
+ is_file = False
323
+ if not is_file:
324
+ return BOUNDARY_REPOSITORY_ROOT
325
+ parts = location.git_dir.replace("\\", "/").split("/")
326
+ if "worktrees" in parts:
327
+ return BOUNDARY_LINKED_WORKTREE
328
+ if "modules" in parts:
329
+ return BOUNDARY_SUBMODULE
330
+ if not _same_path(location.git_dir, location.common_dir):
331
+ return BOUNDARY_LINKED_WORKTREE
332
+ return BOUNDARY_SUBMODULE
333
+
334
+
335
+ def _enclosing_git(top_level: str) -> GitLocation | None:
336
+ parent = os.path.dirname(top_level)
337
+ if parent == top_level:
338
+ return None
339
+ outer = find_git_location(parent)
340
+ if outer is None or _same_path(outer.top_level, top_level):
341
+ return None
342
+ return outer
343
+
344
+
345
+ class Resolution:
346
+ """Read-only repository resolution for ``doctor`` and ``validate`` (ADR 0009).
347
+
348
+ A plain class: this module stays stdlib-only for the hook import path.
349
+ """
350
+
351
+ __slots__ = (
352
+ "boundary",
353
+ "common_dir",
354
+ "input_path",
355
+ "marker_path",
356
+ "marker_present",
357
+ "marker_valid",
358
+ "reason",
359
+ "resolved_path",
360
+ "status",
361
+ "top_level",
362
+ )
363
+
364
+ def __init__( # noqa: PLR0913, PLR0917
365
+ self,
366
+ input_path: str,
367
+ resolved_path: str,
368
+ top_level: str | None,
369
+ common_dir: str | None,
370
+ marker_path: str | None,
371
+ marker_present: bool,
372
+ marker_valid: bool,
373
+ boundary: str,
374
+ status: str,
375
+ reason: str,
376
+ ) -> None:
377
+ self.input_path = input_path
378
+ self.resolved_path = resolved_path
379
+ self.top_level = top_level
380
+ self.common_dir = common_dir
381
+ self.marker_path = marker_path
382
+ self.marker_present = marker_present
383
+ self.marker_valid = marker_valid
384
+ self.boundary = boundary
385
+ self.status = status
386
+ self.reason = reason
387
+
388
+ def as_dict(self) -> dict[str, object]:
389
+ """JSON-ready facts. Text output must render this same mapping."""
390
+ return {
391
+ "input_path": posix_abs_path(self.input_path),
392
+ "resolved_path": posix_abs_path(self.resolved_path),
393
+ "repository_root": posix_abs_path(self.top_level),
394
+ "common_dir": posix_abs_path(self.common_dir),
395
+ "marker_path": posix_abs_path(self.marker_path),
396
+ "marker_present": self.marker_present,
397
+ "marker_valid": self.marker_valid,
398
+ "boundary": self.boundary,
399
+ "status": self.status,
400
+ "reason": self.reason,
401
+ }
402
+
403
+
404
+ def _marker_fields(top_level: str | None) -> tuple[str | None, bool, bool]:
405
+ if not top_level:
406
+ return None, False, False
407
+ marker = os.path.join(top_level, INSTALL_MARKER_DIR, INSTALL_MARKER_NAME)
408
+ try:
409
+ present = os.path.lexists(marker)
410
+ except OSError:
411
+ present = False
412
+ return marker, present, has_install_marker(top_level)
413
+
414
+
415
+ def _fail( # noqa: PLR0913, PLR0917
416
+ input_path: str,
417
+ resolved_path: str,
418
+ boundary: str,
419
+ reason: str,
420
+ top_level: str | None = None,
421
+ common_dir: str | None = None,
422
+ ) -> Resolution:
423
+ marker_path, present, valid = _marker_fields(top_level)
424
+ return Resolution(
425
+ input_path,
426
+ resolved_path,
427
+ top_level,
428
+ common_dir,
429
+ marker_path,
430
+ present,
431
+ valid,
432
+ boundary,
433
+ "fail",
434
+ reason,
435
+ )
436
+
437
+
438
+ def _ok(
439
+ input_path: str,
440
+ resolved_path: str,
441
+ location: GitLocation,
442
+ boundary: str,
443
+ reason: str,
444
+ ) -> Resolution:
445
+ marker_path, present, valid = _marker_fields(location.top_level)
446
+ return Resolution(
447
+ input_path,
448
+ resolved_path,
449
+ location.top_level,
450
+ location.common_dir,
451
+ marker_path,
452
+ present,
453
+ valid,
454
+ boundary,
455
+ "ok",
456
+ reason,
457
+ )
458
+
459
+
460
+ def inspect_repository( # noqa: PLR0911
461
+ start: str, workspace_roots: object = None
462
+ ) -> Resolution:
463
+ """Classify ``start`` using the Task 16 file-walk. Creates nothing. Never raises."""
464
+ input_path = start
465
+ try:
466
+ abs_input = start if os.path.isabs(start) else os.path.abspath(start)
467
+ except OSError:
468
+ abs_input = start
469
+ try:
470
+ resolved = safe_realpath(abs_input)
471
+ except (OSError, ValueError):
472
+ resolved = abs_input
473
+ if is_ambiguous_workspace_roots(workspace_roots):
474
+ return _fail(
475
+ input_path,
476
+ resolved,
477
+ BOUNDARY_AMBIGUOUS_MULTI_ROOT,
478
+ "ambiguous multi-root workspace",
479
+ )
480
+ if is_external_symlink_anchor(abs_input):
481
+ return _fail(
482
+ input_path,
483
+ resolved,
484
+ BOUNDARY_EXTERNAL_SYMLINK,
485
+ "path is an external symlink that leaves the containing repository",
486
+ )
487
+ location = find_git_location(resolved if os.path.isabs(resolved) else abs_input)
488
+ if location is None:
489
+ if len(_child_git_roots(resolved)) > 1:
490
+ return _fail(
491
+ input_path,
492
+ resolved,
493
+ BOUNDARY_AMBIGUOUS_MULTI_ROOT,
494
+ "ambiguous multi-root workspace",
495
+ )
496
+ return _fail(
497
+ input_path,
498
+ resolved,
499
+ BOUNDARY_NON_GIT,
500
+ "not inside a Git working tree",
501
+ )
502
+ kind = _gitfile_boundary(location)
503
+ nested = _enclosing_git(location.top_level) is not None
504
+ at_root = _same_path(resolved, location.top_level)
505
+ marker_valid = has_install_marker(location.top_level)
506
+ if kind == BOUNDARY_LINKED_WORKTREE:
507
+ if at_root:
508
+ return _ok(
509
+ input_path,
510
+ resolved,
511
+ location,
512
+ BOUNDARY_LINKED_WORKTREE,
513
+ "linked worktree root; runtime stays at the git common directory",
514
+ )
515
+ return _ok(
516
+ input_path,
517
+ resolved,
518
+ location,
519
+ BOUNDARY_ORDINARY_DESCENDANT,
520
+ "ordinary descendant of a linked worktree",
521
+ )
522
+ if kind == BOUNDARY_SUBMODULE:
523
+ return _ok(
524
+ input_path,
525
+ resolved,
526
+ location,
527
+ BOUNDARY_SUBMODULE,
528
+ "submodule or gitfile boundary; outer repository is not used",
529
+ )
530
+ if nested:
531
+ reason = (
532
+ "uninitialized inner repository; will not fall back to an outer repository"
533
+ if not marker_valid
534
+ else "nested repository; inner root is the boundary"
535
+ )
536
+ return _ok(
537
+ input_path,
538
+ resolved,
539
+ location,
540
+ BOUNDARY_NESTED_REPOSITORY if at_root else BOUNDARY_ORDINARY_DESCENDANT,
541
+ reason,
542
+ )
543
+ if at_root:
544
+ return _ok(
545
+ input_path,
546
+ resolved,
547
+ location,
548
+ BOUNDARY_REPOSITORY_ROOT,
549
+ (
550
+ "detected repository root"
551
+ if marker_valid
552
+ else "detected repository root; CursorFleet marker is absent"
553
+ ),
554
+ )
555
+ return _ok(
556
+ input_path,
557
+ resolved,
558
+ location,
559
+ BOUNDARY_ORDINARY_DESCENDANT,
560
+ (
561
+ "ordinary descendant of an initialized enclosing repository"
562
+ if marker_valid
563
+ else "ordinary descendant of the detected repository; CursorFleet marker is absent"
564
+ ),
565
+ )
566
+
567
+
568
+ def untrusted_reason(root: str) -> str | None: # noqa: PLR0911
569
+ """Why ``root`` (the runtime directory) must not be written to, or ``None`` if it is fine.
570
+
571
+ A missing directory is fine (it will be created 0700 by us). An existing one must be a
572
+ real directory (no symlink or Windows junction) owned by the current user and, on POSIX,
573
+ not writable by group or others. Otherwise another account could have pre-planted
574
+ symlinks or forged spool files in a shared ``.git`` directory (security review SR-02).
575
+ One ``lstat``; hot-path safe.
576
+ """
577
+ try:
578
+ info = os.lstat(root)
579
+ except FileNotFoundError:
580
+ return None
581
+ except OSError:
582
+ return "cannot be inspected"
583
+ if stat.S_ISLNK(info.st_mode):
584
+ return "is a symbolic link"
585
+ if not stat.S_ISDIR(info.st_mode):
586
+ return "is not a directory"
587
+ if getattr(info, "st_file_attributes", 0) & _FILE_ATTRIBUTE_REPARSE_POINT:
588
+ return "is a reparse point (junction)"
589
+ if os.name == "posix":
590
+ geteuid = getattr(os, "geteuid", None)
591
+ if geteuid is not None and info.st_uid != geteuid():
592
+ return "is owned by another user"
593
+ if info.st_mode & 0o022:
594
+ return "is writable by group or others"
595
+ return None
596
+
597
+
598
+ def mkdir_private(path: str | os.PathLike[str]) -> None:
599
+ """Create ``path`` (and missing parents) with mode 0700 regardless of umask.
600
+
601
+ Existing directories are left untouched (``doctor`` reports wrong modes). On
602
+ Windows the mode is a no-op; user-only ACLs are best effort and not set here.
603
+ """
604
+ target = os.fspath(path)
605
+ missing: list[str] = []
606
+ current = target
607
+ while current and not os.path.isdir(current):
608
+ missing.append(current)
609
+ parent = os.path.dirname(current)
610
+ if parent == current:
611
+ break
612
+ current = parent
613
+ for directory in reversed(missing):
614
+ try:
615
+ os.mkdir(directory, DIR_MODE)
616
+ except FileExistsError:
617
+ continue
618
+ if os.name == "posix":
619
+ os.chmod(directory, DIR_MODE)
620
+
621
+
622
+ def write_private_file(path: str | os.PathLike[str], data: bytes, *, exclusive: bool) -> bool:
623
+ """Write ``data`` to a new 0600 file. With ``exclusive`` never overwrite; return created."""
624
+ flags = os.O_WRONLY | os.O_CREAT | getattr(os, "O_BINARY", 0) | getattr(os, "O_NOFOLLOW", 0)
625
+ flags |= os.O_EXCL if exclusive else os.O_TRUNC
626
+ try:
627
+ fd = os.open(os.fspath(path), flags, FILE_MODE)
628
+ except FileExistsError:
629
+ return False
630
+ try:
631
+ if os.name == "posix":
632
+ fchmod = getattr(os, "fchmod", None)
633
+ if fchmod is not None:
634
+ fchmod(fd, FILE_MODE)
635
+ os.write(fd, data)
636
+ finally:
637
+ os.close(fd)
638
+ return True
639
+
640
+
641
+ def ensure_runtime_root(paths: RuntimePaths) -> None:
642
+ """Create the runtime root and write ``LAYOUT`` once."""
643
+ if not os.path.isdir(paths.root):
644
+ mkdir_private(paths.root)
645
+ write_private_file(paths.layout, (LAYOUT_VERSION + "\n").encode("ascii"), exclusive=True)