spawn-cli 0.1.15__tar.gz → 0.1.16__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 (52) hide show
  1. {spawn_cli-0.1.15/src/spawn_cli.egg-info → spawn_cli-0.1.16}/PKG-INFO +7 -4
  2. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/README.md +6 -3
  3. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/pyproject.toml +1 -1
  4. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/core/low_level.py +70 -5
  5. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/models/__init__.py +2 -0
  6. spawn_cli-0.1.16/src/spawn_cli/models/navigation.py +52 -0
  7. {spawn_cli-0.1.15 → spawn_cli-0.1.16/src/spawn_cli.egg-info}/PKG-INFO +7 -4
  8. spawn_cli-0.1.15/src/spawn_cli/models/navigation.py +0 -30
  9. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/LICENSE +0 -0
  10. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/setup.cfg +0 -0
  11. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/__init__.py +0 -0
  12. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/cli.py +0 -0
  13. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/core/__init__.py +0 -0
  14. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/core/download.py +0 -0
  15. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/core/errors.py +0 -0
  16. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/core/high_level.py +0 -0
  17. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/core/mcp_stdio.py +0 -0
  18. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/core/scripts.py +0 -0
  19. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/errors.py +0 -0
  20. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/ide/__init__.py +0 -0
  21. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/ide/_helpers.py +0 -0
  22. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/ide/_stub.py +0 -0
  23. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/ide/_vacancy.py +0 -0
  24. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/ide/claude_code.py +0 -0
  25. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/ide/codex.py +0 -0
  26. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/ide/cursor.py +0 -0
  27. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/ide/gemini_cli.py +0 -0
  28. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/ide/github_copilot.py +0 -0
  29. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/ide/mcp_stdio_argv.py +0 -0
  30. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/ide/registry.py +0 -0
  31. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/ide/windsurf.py +0 -0
  32. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/io/__init__.py +0 -0
  33. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/io/json_io.py +0 -0
  34. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/io/lock.py +0 -0
  35. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/io/paths.py +0 -0
  36. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/io/text_io.py +0 -0
  37. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/io/toml_io.py +0 -0
  38. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/io/yaml_io.py +0 -0
  39. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/models/config.py +0 -0
  40. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/models/mcp.py +0 -0
  41. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/models/metadata.py +0 -0
  42. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/models/skill.py +0 -0
  43. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/resources/__init__.py +0 -0
  44. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/resources/default_core_config.yaml +0 -0
  45. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli/warnings_display.py +0 -0
  46. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli.egg-info/SOURCES.txt +0 -0
  47. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli.egg-info/dependency_links.txt +0 -0
  48. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli.egg-info/entry_points.txt +0 -0
  49. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli.egg-info/requires.txt +0 -0
  50. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/src/spawn_cli.egg-info/top_level.txt +0 -0
  51. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/tests/test_cli.py +0 -0
  52. {spawn_cli-0.1.15 → spawn_cli-0.1.16}/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.15
3
+ Version: 0.1.16
4
4
  Summary: A minimal installable Python CLI package.
5
5
  License: MIT
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -217,9 +217,11 @@ That rescans **`spawn/rules/`**, attaches missing rule files under a **`rules`**
217
217
 
218
218
  ### 2. Hints in `spawn/navigation.yaml`
219
219
 
220
- For **short, always-visible reminders** (tone, language, repo-specific habits), add an optional **`hint`** field on rows under **`read-required` → `rules`**—alongside **`path`** and **`description`**.
220
+ For **short, always-visible reminders** (tone, language, repo-specific habits), add **hint-only** rows under **`read-required` → `rules`** (a **`hint`** entry **without** a **`path`**). Mandatory rule files use rows with **`path`** and **`description`** only—do **not** add **`hint`** on the same row as **`path`**; if refresh finds legacy pairs, it splits them into a file row and a following hint-only row.
221
221
 
222
- Spawn merges those strings into the **Hints** section of **rendered skills** and into the **Hints** rollup in managed IDE entry points (for example **`AGENTS.md`**), using the same ordering and deduping rules as extension **`hints.global`**.
222
+ Hints from **`read-required` → `rules`** (hint-only rows) appear in skills’ **Hints** section and in the managed **`AGENTS.md`** rollup together with extension **`hints.global`**, using YAML row order within maintainer hints before dedupe (**`hint`** on **`read-contextual` → `rules`** is still **not** included there).
223
+
224
+ If a **`path`** names a **`spawn/rules/…`** file that **does not** exist when navigation is refreshed, that row is **removed entirely** — fix or drop **`path`** first; **`hint`** on that same removed row is **not** downgraded to a hint-only row.
223
225
 
224
226
  Example:
225
227
 
@@ -228,7 +230,8 @@ read-required:
228
230
  - rules:
229
231
  - path: spawn/rules/team.md
230
232
  description: Team conventions.
231
- hint: Prefer British spelling in user-facing copy.
233
+ - hint: Prefer British spelling in user-facing copy.
234
+ - hint: Short standalone reminder with no rule file.
232
235
  ```
233
236
 
234
237
  Then run **`spawn refresh`** so skills and entry points pick up the hint.
@@ -200,9 +200,11 @@ That rescans **`spawn/rules/`**, attaches missing rule files under a **`rules`**
200
200
 
201
201
  ### 2. Hints in `spawn/navigation.yaml`
202
202
 
203
- For **short, always-visible reminders** (tone, language, repo-specific habits), add an optional **`hint`** field on rows under **`read-required` → `rules`**—alongside **`path`** and **`description`**.
203
+ For **short, always-visible reminders** (tone, language, repo-specific habits), add **hint-only** rows under **`read-required` → `rules`** (a **`hint`** entry **without** a **`path`**). Mandatory rule files use rows with **`path`** and **`description`** only—do **not** add **`hint`** on the same row as **`path`**; if refresh finds legacy pairs, it splits them into a file row and a following hint-only row.
204
204
 
205
- Spawn merges those strings into the **Hints** section of **rendered skills** and into the **Hints** rollup in managed IDE entry points (for example **`AGENTS.md`**), using the same ordering and deduping rules as extension **`hints.global`**.
205
+ Hints from **`read-required` → `rules`** (hint-only rows) appear in skills’ **Hints** section and in the managed **`AGENTS.md`** rollup together with extension **`hints.global`**, using YAML row order within maintainer hints before dedupe (**`hint`** on **`read-contextual` → `rules`** is still **not** included there).
206
+
207
+ If a **`path`** names a **`spawn/rules/…`** file that **does not** exist when navigation is refreshed, that row is **removed entirely** — fix or drop **`path`** first; **`hint`** on that same removed row is **not** downgraded to a hint-only row.
206
208
 
207
209
  Example:
208
210
 
@@ -211,7 +213,8 @@ read-required:
211
213
  - rules:
212
214
  - path: spawn/rules/team.md
213
215
  description: Team conventions.
214
- hint: Prefer British spelling in user-facing copy.
216
+ - hint: Prefer British spelling in user-facing copy.
217
+ - hint: Short standalone reminder with no rule file.
215
218
  ```
216
219
 
217
220
  Then run **`spawn refresh`** so skills and entry points pick up the hint.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "spawn-cli"
7
- version = "0.1.15"
7
+ version = "0.1.16"
8
8
  description = "A minimal installable Python CLI package."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -194,9 +194,12 @@ def _merge_hint_streams_ordered(streams: list[list[str]]) -> list[str]:
194
194
 
195
195
 
196
196
  def _navigation_read_required_rule_hints_ordered(target_root: Path) -> list[str]:
197
- """Plain-text hints from maintainer-authored read-required ``rules`` rows only (YAML order).
197
+ """Plain-text hints from maintainer-authored ``read-required`` ``rules`` rows (YAML order).
198
198
 
199
+ Only **hint-only** rows (no non-empty stripped ``path``) contribute; ``hint`` on file-backed
200
+ rows is ignored here (and dropped on navigation refresh).
199
201
  Hint fields on ``read-contextual`` rules are ignored here.
202
+ Strips each hint; whitespace-only hints are omitted.
200
203
  """
201
204
  path = _spawn(target_root) / "navigation.yaml"
202
205
  raw = load_yaml(path)
@@ -215,9 +218,14 @@ def _navigation_read_required_rule_hints_ordered(target_root: Path) -> list[str]
215
218
  for entry in rules_raw:
216
219
  if not isinstance(entry, dict):
217
220
  continue
221
+ path_val = entry.get("path")
222
+ if isinstance(path_val, str) and path_val.strip():
223
+ continue
218
224
  hint_val = entry.get("hint")
219
225
  if isinstance(hint_val, str):
220
- out.append(hint_val)
226
+ stripped = hint_val.strip()
227
+ if stripped:
228
+ out.append(stripped)
221
229
  return out
222
230
 
223
231
 
@@ -1202,7 +1210,64 @@ def save_rules_navigation(target_root: Path) -> None:
1202
1210
  def posix_norm(p: str) -> str:
1203
1211
  return Path(p).as_posix().replace('\\', '/')
1204
1212
 
1205
- def prune(lst: list[Any]) -> None:
1213
+ def prune_read_required_rules(lst: list[Any]) -> None:
1214
+ """Keep file-backed rows with existing paths (no ``hint`` on those rows); keep hint-only rows."""
1215
+
1216
+ out: list[Any] = []
1217
+ for entry in lst:
1218
+ if not isinstance(entry, dict):
1219
+ continue
1220
+ hint_raw = entry.get('hint')
1221
+ hint_is_str = isinstance(hint_raw, str)
1222
+ hint_stripped = hint_raw.strip() if hint_is_str else ''
1223
+ path_in_entry = 'path' in entry
1224
+ path_raw = entry.get('path')
1225
+
1226
+ if path_in_entry:
1227
+ if isinstance(path_raw, str) and path_raw.strip():
1228
+ rk = posix_norm(path_raw.strip())
1229
+ exists = rk in paths_on_disk and (Path(target_root) / Path(rk)).is_file()
1230
+ if exists:
1231
+ row: dict[str, Any] = {
1232
+ 'path': rk,
1233
+ 'description': str(entry.get('description') or 'Local rule file.'),
1234
+ }
1235
+ out.append(row)
1236
+ if hint_stripped:
1237
+ warnings.warn(
1238
+ f'Moved maintainer hint from file-backed rule row to standalone '
1239
+ f'hint row: {rk}',
1240
+ SpawnWarning,
1241
+ )
1242
+ out.append({'hint': hint_stripped})
1243
+ else:
1244
+ warnings.warn(
1245
+ f"Removed missing rule path from navigation: {rk}",
1246
+ SpawnWarning,
1247
+ )
1248
+ else:
1249
+ warnings.warn(
1250
+ 'Removed invalid rule row from navigation (empty path field).',
1251
+ SpawnWarning,
1252
+ )
1253
+ continue
1254
+
1255
+ if hint_stripped:
1256
+ row_h: dict[str, Any] = {'hint': hint_stripped}
1257
+ desc_raw = entry.get('description')
1258
+ if isinstance(desc_raw, str) and desc_raw.strip():
1259
+ row_h['description'] = desc_raw.strip()
1260
+ out.append(row_h)
1261
+ continue
1262
+
1263
+ warnings.warn(
1264
+ 'Removed invalid rule row from navigation (no path and no hint).',
1265
+ SpawnWarning,
1266
+ )
1267
+
1268
+ lst[:] = out
1269
+
1270
+ def prune_contextual_rules(lst: list[Any]) -> None:
1206
1271
  out: list[Any] = []
1207
1272
  for entry in lst:
1208
1273
  if not isinstance(entry, dict) or 'path' not in entry:
@@ -1225,9 +1290,9 @@ def save_rules_navigation(target_root: Path) -> None:
1225
1290
  )
1226
1291
  lst[:] = out
1227
1292
 
1228
- prune(rq_rules_list)
1293
+ prune_read_required_rules(rq_rules_list)
1229
1294
  if cq_rules_list is not None:
1230
- prune(cq_rules_list)
1295
+ prune_contextual_rules(cq_rules_list)
1231
1296
 
1232
1297
  raw["read-required"] = rr
1233
1298
  raw["read-contextual"] = rc
@@ -27,6 +27,7 @@ from spawn_cli.models.metadata import (
27
27
  from spawn_cli.models.navigation import (
28
28
  NavExtGroup,
29
29
  NavFile,
30
+ NavRuleRow,
30
31
  NavRulesGroup,
31
32
  NavigationFile,
32
33
  )
@@ -55,6 +56,7 @@ __all__ = [
55
56
  "RenderedSkillsMeta",
56
57
  "NavExtGroup",
57
58
  "NavFile",
59
+ "NavRuleRow",
58
60
  "NavRulesGroup",
59
61
  "NavigationFile",
60
62
  "SkillFileRef",
@@ -0,0 +1,52 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
4
+
5
+
6
+ class NavFile(BaseModel):
7
+ model_config = ConfigDict(populate_by_name=True)
8
+
9
+ path: str
10
+ description: str
11
+
12
+
13
+ class NavExtGroup(BaseModel):
14
+ model_config = ConfigDict(populate_by_name=True)
15
+
16
+ ext: str
17
+ files: list[NavFile] = Field(default_factory=list)
18
+
19
+
20
+ class NavRuleRow(BaseModel):
21
+ """Maintainer ``read-required`` ``rules`` row: file-backed (``path``) or hint-only (``hint``), never both."""
22
+
23
+ model_config = ConfigDict(populate_by_name=True)
24
+
25
+ path: str | None = None
26
+ description: str | None = None
27
+ hint: str | None = None
28
+
29
+ @model_validator(mode='after')
30
+ def path_xor_hint(self) -> NavRuleRow:
31
+ path_ok = bool(self.path and str(self.path).strip())
32
+ hint_ok = bool(self.hint and str(self.hint).strip())
33
+ if not path_ok and not hint_ok:
34
+ raise ValueError('navigation rule row needs a non-empty path or a non-empty hint')
35
+ if path_ok and hint_ok:
36
+ raise ValueError(
37
+ 'navigation rule row cannot combine path and hint; use a standalone hint-only row'
38
+ )
39
+ return self
40
+
41
+
42
+ class NavRulesGroup(BaseModel):
43
+ model_config = ConfigDict(populate_by_name=True)
44
+
45
+ rules: list[NavRuleRow] = Field(default_factory=list)
46
+
47
+
48
+ class NavigationFile(BaseModel):
49
+ model_config = ConfigDict(populate_by_name=True)
50
+
51
+ read_required: list[dict] = Field(default_factory=list, alias="read-required")
52
+ read_contextual: list[dict] = Field(default_factory=list, alias="read-contextual")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spawn-cli
3
- Version: 0.1.15
3
+ Version: 0.1.16
4
4
  Summary: A minimal installable Python CLI package.
5
5
  License: MIT
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -217,9 +217,11 @@ That rescans **`spawn/rules/`**, attaches missing rule files under a **`rules`**
217
217
 
218
218
  ### 2. Hints in `spawn/navigation.yaml`
219
219
 
220
- For **short, always-visible reminders** (tone, language, repo-specific habits), add an optional **`hint`** field on rows under **`read-required` → `rules`**—alongside **`path`** and **`description`**.
220
+ For **short, always-visible reminders** (tone, language, repo-specific habits), add **hint-only** rows under **`read-required` → `rules`** (a **`hint`** entry **without** a **`path`**). Mandatory rule files use rows with **`path`** and **`description`** only—do **not** add **`hint`** on the same row as **`path`**; if refresh finds legacy pairs, it splits them into a file row and a following hint-only row.
221
221
 
222
- Spawn merges those strings into the **Hints** section of **rendered skills** and into the **Hints** rollup in managed IDE entry points (for example **`AGENTS.md`**), using the same ordering and deduping rules as extension **`hints.global`**.
222
+ Hints from **`read-required` → `rules`** (hint-only rows) appear in skills’ **Hints** section and in the managed **`AGENTS.md`** rollup together with extension **`hints.global`**, using YAML row order within maintainer hints before dedupe (**`hint`** on **`read-contextual` → `rules`** is still **not** included there).
223
+
224
+ If a **`path`** names a **`spawn/rules/…`** file that **does not** exist when navigation is refreshed, that row is **removed entirely** — fix or drop **`path`** first; **`hint`** on that same removed row is **not** downgraded to a hint-only row.
223
225
 
224
226
  Example:
225
227
 
@@ -228,7 +230,8 @@ read-required:
228
230
  - rules:
229
231
  - path: spawn/rules/team.md
230
232
  description: Team conventions.
231
- hint: Prefer British spelling in user-facing copy.
233
+ - hint: Prefer British spelling in user-facing copy.
234
+ - hint: Short standalone reminder with no rule file.
232
235
  ```
233
236
 
234
237
  Then run **`spawn refresh`** so skills and entry points pick up the hint.
@@ -1,30 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from pydantic import BaseModel, ConfigDict, Field
4
-
5
-
6
- class NavFile(BaseModel):
7
- model_config = ConfigDict(populate_by_name=True)
8
-
9
- path: str
10
- description: str
11
-
12
-
13
- class NavExtGroup(BaseModel):
14
- model_config = ConfigDict(populate_by_name=True)
15
-
16
- ext: str
17
- files: list[NavFile] = Field(default_factory=list)
18
-
19
-
20
- class NavRulesGroup(BaseModel):
21
- model_config = ConfigDict(populate_by_name=True)
22
-
23
- rules: list[NavFile] = Field(default_factory=list)
24
-
25
-
26
- class NavigationFile(BaseModel):
27
- model_config = ConfigDict(populate_by_name=True)
28
-
29
- read_required: list[dict] = Field(default_factory=list, alias="read-required")
30
- read_contextual: list[dict] = Field(default_factory=list, alias="read-contextual")
File without changes
File without changes
File without changes