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.
- {spawn_cli-0.1.9/src/spawn_cli.egg-info → spawn_cli-0.1.10}/PKG-INFO +1 -1
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/pyproject.toml +1 -1
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/core/high_level.py +63 -21
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/core/low_level.py +31 -4
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/__init__.py +8 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/_helpers.py +158 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/claude_code.py +6 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/cursor.py +16 -2
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/gemini_cli.py +17 -3
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/registry.py +17 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/windsurf.py +17 -3
- {spawn_cli-0.1.9 → spawn_cli-0.1.10/src/spawn_cli.egg-info}/PKG-INFO +1 -1
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/LICENSE +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/README.md +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/setup.cfg +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/__init__.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/cli.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/core/__init__.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/core/download.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/core/errors.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/core/scripts.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/errors.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/_stub.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/_vacancy.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/codex.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/ide/github_copilot.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/io/__init__.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/io/json_io.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/io/lock.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/io/paths.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/io/text_io.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/io/toml_io.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/io/yaml_io.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/models/__init__.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/models/config.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/models/mcp.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/models/metadata.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/models/navigation.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/models/skill.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/resources/__init__.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/resources/default_core_config.yaml +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli/warnings_display.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli.egg-info/SOURCES.txt +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli.egg-info/dependency_links.txt +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli.egg-info/entry_points.txt +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli.egg-info/requires.txt +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/src/spawn_cli.egg-info/top_level.txt +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/tests/test_cli.py +0 -0
- {spawn_cli-0.1.9 → spawn_cli-0.1.10}/tests/test_warnings_display.py +0 -0
|
@@ -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
|
-
|
|
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.
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
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)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|