spawn-cli 0.1.9__tar.gz → 0.1.10__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 (49) hide show
  1. {spawn_cli-0.1.9/src/spawn_cli.egg-info → spawn_cli-0.1.10}/PKG-INFO +1 -1
  2. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/pyproject.toml +1 -1
  3. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/core/high_level.py +63 -21
  4. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/core/low_level.py +31 -4
  5. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/__init__.py +8 -0
  6. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/_helpers.py +158 -0
  7. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/claude_code.py +6 -0
  8. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/cursor.py +16 -2
  9. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/gemini_cli.py +17 -3
  10. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/registry.py +17 -0
  11. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/windsurf.py +17 -3
  12. {spawn_cli-0.1.9 → spawn_cli-0.1.10/src/spawn_cli.egg-info}/PKG-INFO +1 -1
  13. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/LICENSE +0 -0
  14. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/README.md +0 -0
  15. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/setup.cfg +0 -0
  16. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/__init__.py +0 -0
  17. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/cli.py +0 -0
  18. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/core/__init__.py +0 -0
  19. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/core/download.py +0 -0
  20. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/core/errors.py +0 -0
  21. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/core/scripts.py +0 -0
  22. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/errors.py +0 -0
  23. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/_stub.py +0 -0
  24. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/_vacancy.py +0 -0
  25. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/codex.py +0 -0
  26. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/github_copilot.py +0 -0
  27. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/io/__init__.py +0 -0
  28. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/io/json_io.py +0 -0
  29. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/io/lock.py +0 -0
  30. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/io/paths.py +0 -0
  31. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/io/text_io.py +0 -0
  32. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/io/toml_io.py +0 -0
  33. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/io/yaml_io.py +0 -0
  34. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/models/__init__.py +0 -0
  35. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/models/config.py +0 -0
  36. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/models/mcp.py +0 -0
  37. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/models/metadata.py +0 -0
  38. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/models/navigation.py +0 -0
  39. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/models/skill.py +0 -0
  40. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/resources/__init__.py +0 -0
  41. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/resources/default_core_config.yaml +0 -0
  42. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/warnings_display.py +0 -0
  43. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli.egg-info/SOURCES.txt +0 -0
  44. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli.egg-info/dependency_links.txt +0 -0
  45. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli.egg-info/entry_points.txt +0 -0
  46. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli.egg-info/requires.txt +0 -0
  47. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli.egg-info/top_level.txt +0 -0
  48. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/tests/test_cli.py +0 -0
  49. {spawn_cli-0.1.9 → spawn_cli-0.1.10}/tests/test_warnings_display.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spawn-cli
3
- Version: 0.1.9
3
+ Version: 0.1.10
4
4
  Summary: A minimal installable Python CLI package.
5
5
  License: MIT
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "spawn-cli"
7
- version = "0.1.9"
7
+ version = "0.1.10"
8
8
  description = "A minimal installable Python CLI package."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -63,12 +63,9 @@ def _extend_dir(target_root: Path, extension: str) -> Path:
63
63
 
64
64
 
65
65
  def _agent_ignore_merge_excluding(target_root: Path, skip_ext: str) -> list[str]:
66
+ """Merged extension ``agent-ignore`` only, omitting *skip_ext*."""
66
67
  merged: list[str] = []
67
68
  seen: set[str] = set()
68
- for g in ll.get_core_agent_ignore(target_root):
69
- if g not in seen:
70
- seen.add(g)
71
- merged.append(g)
72
69
  for ext in ll.list_extensions(target_root):
73
70
  if ext == skip_ext:
74
71
  continue
@@ -79,6 +76,48 @@ def _agent_ignore_merge_excluding(target_root: Path, skip_ext: str) -> list[str]
79
76
  return merged
80
77
 
81
78
 
79
+ def _sync_project_agent_ignore_permissions(target_root: Path, ide: str) -> None:
80
+ old = ll.get_agent_ignore_list(target_root, ide)
81
+ new = ll.get_all_agent_ignore(target_root)
82
+ adapter = ide_get(ide)
83
+ to_remove = sorted(set(old) - set(new))
84
+ to_add = sorted(set(new) - set(old))
85
+ if to_remove:
86
+ adapter.remove_agent_ignore(target_root, to_remove)
87
+ if to_add:
88
+ adapter.add_agent_ignore(target_root, to_add)
89
+ ll.save_agent_ignore_list(target_root, ide, new)
90
+
91
+
92
+ def refresh_core_agent_ignore(target_root: Path, ide: str) -> None:
93
+ _require_init(target_root)
94
+ adapter = ide_get(ide)
95
+ cap = adapter.detect(target_root).capabilities.agent_ignore
96
+ core = ll.get_core_agent_ignore(target_root)
97
+ if cap == "native":
98
+ adapter.rewrite_core_agent_ignore(target_root, core)
99
+ elif cap == "project":
100
+ _sync_project_agent_ignore_permissions(target_root, ide)
101
+
102
+
103
+ def refresh_extension_agent_ignore(target_root: Path, ide: str) -> None:
104
+ _require_init(target_root)
105
+ adapter = ide_get(ide)
106
+ cap = adapter.detect(target_root).capabilities.agent_ignore
107
+ ext = ll.get_merged_extension_agent_ignore(target_root)
108
+ if cap == "native":
109
+ adapter.rewrite_extension_agent_ignore(target_root, ext)
110
+ ll.save_agent_ignore_list(target_root, ide, ext)
111
+ elif cap == "project":
112
+ _sync_project_agent_ignore_permissions(target_root, ide)
113
+
114
+
115
+ def refresh_agent_ignore(target_root: Path, ide: str) -> None:
116
+ _require_init(target_root)
117
+ refresh_core_agent_ignore(target_root, ide)
118
+ refresh_extension_agent_ignore(target_root, ide)
119
+
120
+
82
121
  def refresh_gitignore(target_root: Path) -> None:
83
122
  _require_init(target_root)
84
123
  new_items: list[str] = []
@@ -95,16 +134,6 @@ def refresh_gitignore(target_root: Path) -> None:
95
134
  ll.remove_from_global_gitignore(target_root, sorted(ex_set - new_set))
96
135
 
97
136
 
98
- def refresh_agent_ignore(target_root: Path, ide: str) -> None:
99
- _require_init(target_root)
100
- old = ll.get_agent_ignore_list(target_root, ide)
101
- new = ll.get_all_agent_ignore(target_root)
102
- adapter = ide_get(ide)
103
- adapter.remove_agent_ignore(target_root, sorted(set(old) - set(new)))
104
- adapter.add_agent_ignore(target_root, sorted(set(new) - set(old)))
105
- ll.save_agent_ignore_list(target_root, ide, new)
106
-
107
-
108
137
  def refresh_skills(target_root: Path, ide: str, extension: str) -> None:
109
138
  _require_init(target_root)
110
139
  adapter = ide_get(ide)
@@ -208,12 +237,24 @@ def refresh_extension_for_ide(target_root: Path, ide: str, extension: str) -> No
208
237
  def remove_extension_for_ide(target_root: Path, ide: str, extension: str) -> None:
209
238
  remove_mcp(target_root, ide, extension)
210
239
  remove_skills(target_root, ide, extension)
211
- old = ll.get_agent_ignore_list(target_root, ide)
212
- new = _agent_ignore_merge_excluding(target_root, extension)
240
+ new_ext = _agent_ignore_merge_excluding(target_root, extension)
213
241
  adapter = ide_get(ide)
214
- adapter.remove_agent_ignore(target_root, sorted(set(old) - set(new)))
215
- adapter.add_agent_ignore(target_root, sorted(set(new) - set(old)))
216
- ll.save_agent_ignore_list(target_root, ide, new)
242
+ cap = adapter.detect(target_root).capabilities.agent_ignore
243
+ if cap == "native":
244
+ adapter.rewrite_extension_agent_ignore(target_root, new_ext)
245
+ ll.save_agent_ignore_list(target_root, ide, new_ext)
246
+ elif cap == "project":
247
+ desired = ll.merge_core_and_extension_agent_ignore(
248
+ ll.get_core_agent_ignore(target_root), new_ext
249
+ )
250
+ old = ll.get_agent_ignore_list(target_root, ide)
251
+ to_remove = sorted(set(old) - set(desired))
252
+ to_add = sorted(set(desired) - set(old))
253
+ if to_remove:
254
+ adapter.remove_agent_ignore(target_root, to_remove)
255
+ if to_add:
256
+ adapter.add_agent_ignore(target_root, to_add)
257
+ ll.save_agent_ignore_list(target_root, ide, desired)
217
258
 
218
259
 
219
260
  def refresh_navigation(target_root: Path) -> None:
@@ -546,8 +587,7 @@ def remove_ide(target_root: Path, ide: str) -> None:
546
587
  for ext in ll.list_extensions(target_root):
547
588
  remove_mcp(target_root, ide, ext)
548
589
  remove_skills(target_root, ide, ext)
549
- old = ll.get_agent_ignore_list(target_root, ide)
550
- ide_get(ide).remove_agent_ignore(target_root, old)
590
+ ide_get(ide).clear_spawn_agent_ignore(target_root)
551
591
  ide_get(ide).finalize_repo_after_ide_removed(target_root)
552
592
  ll.remove_ide_from_list(target_root, ide)
553
593
  ll.remove_ide_metadata_dir(target_root, ide)
@@ -571,6 +611,8 @@ __all__ = [
571
611
  "extension_init",
572
612
  "install_extension",
573
613
  "refresh_agent_ignore",
614
+ "refresh_core_agent_ignore",
615
+ "refresh_extension_agent_ignore",
574
616
  "refresh_entry_point",
575
617
  "refresh_extension",
576
618
  "refresh_extension_for_ide",
@@ -746,12 +746,16 @@ def get_ext_agent_ignore(target_root: Path, extension: str) -> list[str]:
746
746
 
747
747
 
748
748
  def get_all_agent_ignore(target_root: Path) -> list[str]:
749
+ return merge_core_and_extension_agent_ignore(
750
+ get_core_agent_ignore(target_root),
751
+ get_merged_extension_agent_ignore(target_root),
752
+ )
753
+
754
+
755
+ def get_merged_extension_agent_ignore(target_root: Path) -> list[str]:
756
+ """Stable deduped merge of ``agent-ignore`` from all installed extensions."""
749
757
  merged: list[str] = []
750
758
  seen: set[str] = set()
751
- for g in get_core_agent_ignore(target_root):
752
- if g not in seen:
753
- seen.add(g)
754
- merged.append(g)
755
759
  for ext in list_extensions(target_root):
756
760
  for g in get_ext_agent_ignore(target_root, ext):
757
761
  if g not in seen:
@@ -760,6 +764,20 @@ def get_all_agent_ignore(target_root: Path) -> list[str]:
760
764
  return merged
761
765
 
762
766
 
767
+ def merge_core_and_extension_agent_ignore(core: list[str], ext: list[str]) -> list[str]:
768
+ merged: list[str] = []
769
+ seen: set[str] = set()
770
+ for g in core:
771
+ if g not in seen:
772
+ seen.add(g)
773
+ merged.append(g)
774
+ for g in ext:
775
+ if g not in seen:
776
+ seen.add(g)
777
+ merged.append(g)
778
+ return merged
779
+
780
+
763
781
  def get_ext_git_ignore(target_root: Path, extension: str) -> list[str]:
764
782
  cfg = _load_ext_config(target_root, extension)
765
783
  return list(cfg.git_ignore)
@@ -851,6 +869,13 @@ def _agent_ignore_list_path(target_root: Path, ide: str) -> Path:
851
869
 
852
870
 
853
871
  def get_agent_ignore_list(target_root: Path, ide: str) -> list[str]:
872
+ """Last persisted ignore snapshot for *ide*.
873
+
874
+ Native file-based ignores: extension ``agent-ignore`` merge only.
875
+
876
+ Project-style ignores (e.g. Claude Code): full core+extension merge used for
877
+ JSON permissions diffing.
878
+ """
854
879
  lines = read_lines(_agent_ignore_list_path(target_root, ide))
855
880
  return [ln for ln in lines if ln.strip() and not ln.lstrip().startswith("#")]
856
881
 
@@ -1170,6 +1195,7 @@ __all__ = [
1170
1195
  "get_folders",
1171
1196
  "get_git_ignore_list",
1172
1197
  "get_global_gitignore",
1198
+ "get_merged_extension_agent_ignore",
1173
1199
  "get_navigation_metadata",
1174
1200
  "get_rendered_mcp",
1175
1201
  "get_rendered_skills",
@@ -1184,6 +1210,7 @@ __all__ = [
1184
1210
  "list_mcp",
1185
1211
  "list_skills",
1186
1212
  "sync_core_config_from_defaults",
1213
+ "merge_core_and_extension_agent_ignore",
1187
1214
  "METADATA_TEMP_MAX_AGE_SECONDS",
1188
1215
  "normalize_skill_name",
1189
1216
  "prune_metadata_temp",
@@ -17,6 +17,10 @@ from .github_copilot import GitHubCopilotAdapter
17
17
  from .windsurf import WindsurfAdapter
18
18
  from .registry import (
19
19
  ALIASES,
20
+ CORE_IGNORE_BLOCK_END,
21
+ CORE_IGNORE_BLOCK_START,
22
+ EXT_IGNORE_BLOCK_END,
23
+ EXT_IGNORE_BLOCK_START,
20
24
  IGNORE_BLOCK_END,
21
25
  IGNORE_BLOCK_START,
22
26
  SPAWN_BLOCK_END,
@@ -43,6 +47,10 @@ __all__ = [
43
47
  "GeminiCliAdapter",
44
48
  "GitHubCopilotAdapter",
45
49
  "DetectResult",
50
+ "CORE_IGNORE_BLOCK_END",
51
+ "CORE_IGNORE_BLOCK_START",
52
+ "EXT_IGNORE_BLOCK_END",
53
+ "EXT_IGNORE_BLOCK_START",
46
54
  "IGNORE_BLOCK_END",
47
55
  "IGNORE_BLOCK_START",
48
56
  "IdeAdapter",
@@ -14,6 +14,12 @@ SPAWN_BLOCK_END = "<!-- spawn:end -->"
14
14
  IGNORE_BLOCK_START = "# spawn:start"
15
15
  IGNORE_BLOCK_END = "# spawn:end"
16
16
 
17
+ # Dual-region agent ignore (core from spawn/.core vs extensions).
18
+ CORE_IGNORE_BLOCK_START = "# spawn:core:start"
19
+ CORE_IGNORE_BLOCK_END = "# spawn:core:end"
20
+ EXT_IGNORE_BLOCK_START = "# spawn:ext:start"
21
+ EXT_IGNORE_BLOCK_END = "# spawn:ext:end"
22
+
17
23
  _NAV_YAML_FILE = "spawn/navigation.yaml"
18
24
  _NAV_YAML_DESCRIPTION = "Merged Spawn navigation (read-required, read-contextual)."
19
25
 
@@ -118,6 +124,149 @@ def rewrite_ignore_block(file_path: Path, globs: list[str]) -> None:
118
124
  file_path.write_text("".join(new_lines), encoding="utf-8")
119
125
 
120
126
 
127
+ def _normalize_ignore_inner_lines(inner: list[str]) -> list[str]:
128
+ out: list[str] = []
129
+ seen: set[str] = set()
130
+ for ln in inner:
131
+ st = ln.strip()
132
+ if not st or st.startswith("#"):
133
+ continue
134
+ if st not in seen:
135
+ seen.add(st)
136
+ out.append(st)
137
+ return out
138
+
139
+
140
+ def _format_optional_block(start: str, end: str, globs: list[str]) -> list[str]:
141
+ body = [g for g in globs if g.strip()]
142
+ if not body:
143
+ return []
144
+ lines = [start + "\n"] + [g.strip() + "\n" for g in body] + [end + "\n"]
145
+ return lines
146
+
147
+
148
+ def _flatten_chunks(chunks: list[list[str]]) -> list[str]:
149
+ out: list[str] = []
150
+ for ch in chunks:
151
+ out.extend(ch)
152
+ return out
153
+
154
+
155
+ def _rebuild_from_user_chunks(
156
+ chunks: list[list[str]], core_globs: list[str], ext_globs: list[str]
157
+ ) -> list[str]:
158
+ """Interleave user segments with core and ext blocks (see spawn agent-ignore spec)."""
159
+ core_b = _format_optional_block(CORE_IGNORE_BLOCK_START, CORE_IGNORE_BLOCK_END, core_globs)
160
+ ext_b = _format_optional_block(EXT_IGNORE_BLOCK_START, EXT_IGNORE_BLOCK_END, ext_globs)
161
+ if not core_b and not ext_b:
162
+ return _flatten_chunks(chunks)
163
+ if len(chunks) == 0:
164
+ return core_b + ext_b
165
+ if len(chunks) == 1:
166
+ return chunks[0] + core_b + ext_b
167
+ if len(chunks) == 2:
168
+ return chunks[0] + core_b + ext_b + chunks[1]
169
+ out: list[str] = []
170
+ out += chunks[0] + core_b + chunks[1] + ext_b
171
+ for c in chunks[2:]:
172
+ out += c
173
+ return out
174
+
175
+
176
+ def _block_kind(line_stripped: str) -> str | None:
177
+ if line_stripped == IGNORE_BLOCK_START:
178
+ return "legacy"
179
+ if line_stripped == CORE_IGNORE_BLOCK_START:
180
+ return "core"
181
+ if line_stripped == EXT_IGNORE_BLOCK_START:
182
+ return "ext"
183
+ return None
184
+
185
+
186
+ def _end_marker_for(kind: str) -> str:
187
+ if kind == "legacy":
188
+ return IGNORE_BLOCK_END
189
+ if kind == "core":
190
+ return CORE_IGNORE_BLOCK_END
191
+ if kind == "ext":
192
+ return EXT_IGNORE_BLOCK_END
193
+ raise ValueError(kind)
194
+
195
+
196
+ def _parse_split_agent_ignore_lines(
197
+ raw_lines: list[str],
198
+ ) -> tuple[list[list[str]], list[str], list[str]]:
199
+ """Split file into user chunks (between blocks), core globs, ext globs.
200
+
201
+ Legacy ``# spawn:start`` … ``# spawn:end`` inner lines are discarded (migration).
202
+ Unclosed blocks: inner runs to EOF.
203
+ """
204
+ chunks: list[list[str]] = []
205
+ core: list[str] = []
206
+ ext: list[str] = []
207
+ cur: list[str] = []
208
+ i = 0
209
+ n = len(raw_lines)
210
+ while i < n:
211
+ kind = _block_kind(raw_lines[i].strip())
212
+ if kind is None:
213
+ cur.append(raw_lines[i])
214
+ i += 1
215
+ continue
216
+ if cur:
217
+ chunks.append(cur)
218
+ cur = []
219
+ end_marker = _end_marker_for(kind)
220
+ j = i + 1
221
+ inner: list[str] = []
222
+ while j < n and raw_lines[j].strip() != end_marker:
223
+ inner.append(raw_lines[j])
224
+ j += 1
225
+ if kind == "core":
226
+ core = _normalize_ignore_inner_lines(inner)
227
+ elif kind == "ext":
228
+ ext = _normalize_ignore_inner_lines(inner)
229
+ # legacy: discard inner
230
+ i = j + 1 if j < n and raw_lines[j].strip() == end_marker else j
231
+ if cur:
232
+ chunks.append(cur)
233
+ return chunks, core, ext
234
+
235
+
236
+ def parse_split_agent_ignore_file(file_path: Path) -> tuple[list[list[str]], list[str], list[str]]:
237
+ if not file_path.exists():
238
+ return [], [], []
239
+ raw = file_path.read_text(encoding="utf-8").splitlines(True)
240
+ return _parse_split_agent_ignore_lines(raw)
241
+
242
+
243
+ def write_split_agent_ignore_file(
244
+ file_path: Path, chunks: list[list[str]], core_globs: list[str], ext_globs: list[str]
245
+ ) -> None:
246
+ body = _rebuild_from_user_chunks(chunks, core_globs, ext_globs)
247
+ file_path.parent.mkdir(parents=True, exist_ok=True)
248
+ if not body:
249
+ if file_path.exists():
250
+ file_path.write_text("", encoding="utf-8")
251
+ return
252
+ file_path.write_text("".join(body), encoding="utf-8")
253
+
254
+
255
+ def rewrite_core_agent_ignore_region(file_path: Path, core_globs: list[str]) -> None:
256
+ chunks, _, ext = parse_split_agent_ignore_file(file_path)
257
+ write_split_agent_ignore_file(file_path, chunks, core_globs, ext)
258
+
259
+
260
+ def rewrite_extension_agent_ignore_region(file_path: Path, ext_globs: list[str]) -> None:
261
+ chunks, core, _ = parse_split_agent_ignore_file(file_path)
262
+ write_split_agent_ignore_file(file_path, chunks, core, ext_globs)
263
+
264
+
265
+ def clear_split_agent_ignore_file(file_path: Path) -> None:
266
+ chunks, _, _ = parse_split_agent_ignore_file(file_path)
267
+ write_split_agent_ignore_file(file_path, chunks, [], [])
268
+
269
+
121
270
  def remove_ignore_block(file_path: Path, globs: list[str]) -> None:
122
271
  """Remove Spawn-managed ignore lines or the whole managed block.
123
272
 
@@ -162,13 +311,22 @@ def remove_ignore_block(file_path: Path, globs: list[str]) -> None:
162
311
 
163
312
 
164
313
  __all__ = [
314
+ "CORE_IGNORE_BLOCK_END",
315
+ "CORE_IGNORE_BLOCK_START",
316
+ "EXT_IGNORE_BLOCK_END",
317
+ "EXT_IGNORE_BLOCK_START",
165
318
  "IGNORE_BLOCK_END",
166
319
  "IGNORE_BLOCK_START",
167
320
  "SPAWN_BLOCK_END",
168
321
  "SPAWN_BLOCK_START",
322
+ "clear_split_agent_ignore_file",
169
323
  "normalize_skill_name",
324
+ "parse_split_agent_ignore_file",
170
325
  "remove_ignore_block",
171
326
  "render_skill_md",
327
+ "rewrite_core_agent_ignore_region",
328
+ "rewrite_extension_agent_ignore_region",
172
329
  "rewrite_ignore_block",
173
330
  "rewrite_managed_block",
331
+ "write_split_agent_ignore_file",
174
332
  ]
@@ -6,6 +6,7 @@ import json
6
6
  import warnings
7
7
  from pathlib import Path
8
8
 
9
+ from spawn_cli.core import low_level as ll
9
10
  from spawn_cli.ide import _vacancy as _vac
10
11
  from spawn_cli.ide.registry import (
11
12
  DetectResult,
@@ -126,6 +127,11 @@ class ClaudeCodeAdapter(IdeAdapter):
126
127
  perms["deny"] = [g for g in deny if g not in drop]
127
128
  settings_path.write_text(json.dumps(data, indent=2), encoding="utf-8")
128
129
 
130
+ def clear_spawn_agent_ignore(self, target_root: Path) -> None:
131
+ recorded = ll.get_agent_ignore_list(target_root, self.key)
132
+ if recorded:
133
+ self.remove_agent_ignore(target_root, recorded)
134
+
129
135
  def rewrite_entry_point(self, target_root: Path, prompt: str) -> str:
130
136
  ep = target_root / "CLAUDE.md"
131
137
  if (target_root / ".claude" / "CLAUDE.md").exists():
@@ -7,6 +7,13 @@ import warnings
7
7
  from pathlib import Path
8
8
 
9
9
  from spawn_cli.ide import _vacancy as _vac
10
+ from spawn_cli.ide._helpers import (
11
+ clear_split_agent_ignore_file,
12
+ remove_ignore_block,
13
+ rewrite_core_agent_ignore_region,
14
+ rewrite_extension_agent_ignore_region,
15
+ rewrite_ignore_block,
16
+ )
10
17
  from spawn_cli.ide.registry import (
11
18
  DetectResult,
12
19
  IdeAdapter,
@@ -15,8 +22,6 @@ from spawn_cli.ide.registry import (
15
22
  normalize_skill_name,
16
23
  render_skill_md,
17
24
  rewrite_managed_block,
18
- rewrite_ignore_block,
19
- remove_ignore_block,
20
25
  )
21
26
  from spawn_cli.models.mcp import McpServer, NormalizedMcp
22
27
  from spawn_cli.models.skill import SkillMetadata
@@ -108,6 +113,15 @@ class CursorAdapter(IdeAdapter):
108
113
  def remove_agent_ignore(self, target_root: Path, globs: list[str]) -> None:
109
114
  remove_ignore_block(target_root / ".cursorignore", globs)
110
115
 
116
+ def rewrite_core_agent_ignore(self, target_root: Path, globs: list[str]) -> None:
117
+ rewrite_core_agent_ignore_region(target_root / ".cursorignore", globs)
118
+
119
+ def rewrite_extension_agent_ignore(self, target_root: Path, globs: list[str]) -> None:
120
+ rewrite_extension_agent_ignore_region(target_root / ".cursorignore", globs)
121
+
122
+ def clear_spawn_agent_ignore(self, target_root: Path) -> None:
123
+ clear_split_agent_ignore_file(target_root / ".cursorignore")
124
+
111
125
  def rewrite_entry_point(self, target_root: Path, prompt: str) -> str:
112
126
  ep = target_root / "AGENTS.md"
113
127
  rewrite_managed_block(ep, prompt)
@@ -7,6 +7,14 @@ import warnings
7
7
  from pathlib import Path
8
8
 
9
9
  from spawn_cli.ide import _vacancy as _vac
10
+ from spawn_cli.ide._helpers import (
11
+ clear_split_agent_ignore_file,
12
+ remove_ignore_block,
13
+ rewrite_core_agent_ignore_region,
14
+ rewrite_extension_agent_ignore_region,
15
+ rewrite_ignore_block,
16
+ rewrite_managed_block,
17
+ )
10
18
  from spawn_cli.ide.registry import (
11
19
  DetectResult,
12
20
  IdeAdapter,
@@ -14,9 +22,6 @@ from spawn_cli.ide.registry import (
14
22
  register,
15
23
  normalize_skill_name,
16
24
  render_skill_md,
17
- rewrite_managed_block,
18
- rewrite_ignore_block,
19
- remove_ignore_block,
20
25
  )
21
26
  from spawn_cli.models.mcp import McpServer, NormalizedMcp
22
27
  from spawn_cli.models.skill import SkillMetadata
@@ -111,6 +116,15 @@ class GeminiCliAdapter(IdeAdapter):
111
116
  def remove_agent_ignore(self, target_root: Path, globs: list[str]) -> None:
112
117
  remove_ignore_block(target_root / ".geminiignore", globs)
113
118
 
119
+ def rewrite_core_agent_ignore(self, target_root: Path, globs: list[str]) -> None:
120
+ rewrite_core_agent_ignore_region(target_root / ".geminiignore", globs)
121
+
122
+ def rewrite_extension_agent_ignore(self, target_root: Path, globs: list[str]) -> None:
123
+ rewrite_extension_agent_ignore_region(target_root / ".geminiignore", globs)
124
+
125
+ def clear_spawn_agent_ignore(self, target_root: Path) -> None:
126
+ clear_split_agent_ignore_file(target_root / ".geminiignore")
127
+
114
128
  def rewrite_entry_point(self, target_root: Path, prompt: str) -> str:
115
129
  ep = target_root / "GEMINI.md"
116
130
  rewrite_managed_block(ep, prompt)
@@ -9,6 +9,10 @@ from pathlib import Path
9
9
  from spawn_cli.core.errors import SpawnError
10
10
  from spawn_cli.core.low_level import CANONICAL_IDE_KEYS
11
11
  from spawn_cli.ide._helpers import (
12
+ CORE_IGNORE_BLOCK_END,
13
+ CORE_IGNORE_BLOCK_START,
14
+ EXT_IGNORE_BLOCK_END,
15
+ EXT_IGNORE_BLOCK_START,
12
16
  IGNORE_BLOCK_END,
13
17
  IGNORE_BLOCK_START,
14
18
  SPAWN_BLOCK_END,
@@ -74,6 +78,15 @@ class IdeAdapter(ABC):
74
78
  @abstractmethod
75
79
  def remove_agent_ignore(self, target_root: Path, globs: list[str]) -> None: ...
76
80
 
81
+ def rewrite_core_agent_ignore(self, target_root: Path, globs: list[str]) -> None:
82
+ del target_root, globs
83
+
84
+ def rewrite_extension_agent_ignore(self, target_root: Path, globs: list[str]) -> None:
85
+ del target_root, globs
86
+
87
+ def clear_spawn_agent_ignore(self, target_root: Path) -> None:
88
+ del target_root
89
+
77
90
  @abstractmethod
78
91
  def rewrite_entry_point(self, target_root: Path, prompt: str) -> str:
79
92
  """Returns rendered path or warning string."""
@@ -122,7 +135,11 @@ def detect_supported_ides(target_root: Path) -> dict[str, DetectResult]:
122
135
 
123
136
  __all__ = [
124
137
  "ALIASES",
138
+ "CORE_IGNORE_BLOCK_END",
139
+ "CORE_IGNORE_BLOCK_START",
125
140
  "DetectResult",
141
+ "EXT_IGNORE_BLOCK_END",
142
+ "EXT_IGNORE_BLOCK_START",
126
143
  "IdeAdapter",
127
144
  "IdeCapabilities",
128
145
  "IGNORE_BLOCK_END",
@@ -6,6 +6,14 @@ import warnings
6
6
  from pathlib import Path
7
7
 
8
8
  from spawn_cli.ide import _vacancy as _vac
9
+ from spawn_cli.ide._helpers import (
10
+ clear_split_agent_ignore_file,
11
+ remove_ignore_block,
12
+ rewrite_core_agent_ignore_region,
13
+ rewrite_extension_agent_ignore_region,
14
+ rewrite_ignore_block,
15
+ rewrite_managed_block,
16
+ )
9
17
  from spawn_cli.ide.registry import (
10
18
  DetectResult,
11
19
  IdeAdapter,
@@ -13,9 +21,6 @@ from spawn_cli.ide.registry import (
13
21
  register,
14
22
  normalize_skill_name,
15
23
  render_skill_md,
16
- rewrite_managed_block,
17
- rewrite_ignore_block,
18
- remove_ignore_block,
19
24
  )
20
25
  from spawn_cli.models.mcp import NormalizedMcp
21
26
  from spawn_cli.models.skill import SkillMetadata
@@ -77,6 +82,15 @@ class WindsurfAdapter(IdeAdapter):
77
82
  def remove_agent_ignore(self, target_root: Path, globs: list[str]) -> None:
78
83
  remove_ignore_block(target_root / ".codeiumignore", globs)
79
84
 
85
+ def rewrite_core_agent_ignore(self, target_root: Path, globs: list[str]) -> None:
86
+ rewrite_core_agent_ignore_region(target_root / ".codeiumignore", globs)
87
+
88
+ def rewrite_extension_agent_ignore(self, target_root: Path, globs: list[str]) -> None:
89
+ rewrite_extension_agent_ignore_region(target_root / ".codeiumignore", globs)
90
+
91
+ def clear_spawn_agent_ignore(self, target_root: Path) -> None:
92
+ clear_split_agent_ignore_file(target_root / ".codeiumignore")
93
+
80
94
  def rewrite_entry_point(self, target_root: Path, prompt: str) -> str:
81
95
  ep = target_root / "AGENTS.md"
82
96
  rewrite_managed_block(ep, prompt)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spawn-cli
3
- Version: 0.1.9
3
+ Version: 0.1.10
4
4
  Summary: A minimal installable Python CLI package.
5
5
  License: MIT
6
6
  Classifier: License :: OSI Approved :: MIT License
File without changes
File without changes
File without changes
File without changes