@softspark/ai-toolkit 4.6.0 → 4.7.0

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.
@@ -1,17 +1,25 @@
1
1
  #!/usr/bin/env python3
2
- """Generate ``.windsurf/rules/*.md`` (and workflows) for Windsurf IDE.
2
+ """Generate ``.devin/rules/*.md`` and ``.windsurf/rules/*.md`` (and workflows)
3
+ for Devin Desktop (formerly Windsurf).
3
4
 
4
- Windsurf reads directory-based rules from ``.windsurf/rules/*.md`` (since
5
- mid-2025). Each rule file supports YAML frontmatter with these fields
6
- (from docs.windsurf.com/windsurf/cascade/memories):
5
+ Windsurf rebranded to Devin Desktop on 2026-06-02. ``.devin/`` is now the
6
+ primary read+write workspace tree; ``.windsurf/`` is a legacy read-only
7
+ fallback that current builds still honor (docs.devin.ai/desktop/devin-desktop-faq:
8
+ "The application already supports .devin/ as the primary workspace directory
9
+ and falls back to .windsurf/ for backward compatibility"). We dual-emit the
10
+ same content to both trees so old Windsurf builds keep working during the
11
+ transition.
12
+
13
+ Each rule file supports YAML frontmatter with these fields
14
+ (from docs.devin.ai/desktop — memories docs):
7
15
 
8
16
  * ``trigger`` — activation mode: ``always_on`` | ``glob`` | ``model_decision``
9
- | ``manual``. When omitted, Windsurf defaults to manual-only.
17
+ | ``manual``. When omitted, the IDE defaults to manual-only.
10
18
  * ``globs`` — comma-separated glob patterns (only when ``trigger: glob``).
11
19
  * ``description`` — shown to the model when ``trigger: model_decision``.
12
20
 
13
- Windsurf also reads workflow markdown files from ``.windsurf/workflows/*.md``
14
- which users invoke via ``/<name>`` slash commands (Cascade). This generator
21
+ Workflow markdown files (invoked via ``/<name>`` slash commands) are emitted
22
+ to ``.devin/workflows/*.md`` and ``.windsurf/workflows/*.md``. This generator
15
23
  emits the same workflow catalogue used by Antigravity and Cline.
16
24
 
17
25
  The legacy ``.windsurfrules`` single-file format is still produced by
@@ -142,20 +150,26 @@ def _build_rules(language_modules: list[str] | None,
142
150
 
143
151
 
144
152
  # ---------------------------------------------------------------------------
145
- # Workflows — .windsurf/workflows/<name>.md invocable via /<name>
153
+ # Workflows — <tree>/workflows/<name>.md invocable via /<name>
146
154
  # ---------------------------------------------------------------------------
147
155
 
156
+ # .devin/ is primary since the 2026-06-02 Devin Desktop rebrand; .windsurf/
157
+ # is the legacy fallback still read by pre-rebrand builds.
158
+ CONFIG_TREES: tuple[str, ...] = (".devin", ".windsurf")
159
+
160
+
148
161
  def _write_workflows(target_dir: Path, *, cleanup: bool = True) -> None:
149
- """Write ``.windsurf/workflows/*.md`` files for Cascade slash commands."""
150
- workflows_dir = target_dir / ".windsurf" / "workflows"
151
- workflows_dir.mkdir(parents=True, exist_ok=True)
162
+ """Write ``workflows/*.md`` slash-command files to both config trees."""
163
+ for tree in CONFIG_TREES:
164
+ workflows_dir = target_dir / tree / "workflows"
165
+ workflows_dir.mkdir(parents=True, exist_ok=True)
152
166
 
153
- if cleanup:
154
- cleanup_stale(workflows_dir, set(STANDARD_WORKFLOWS.keys()))
167
+ if cleanup:
168
+ cleanup_stale(workflows_dir, set(STANDARD_WORKFLOWS.keys()))
155
169
 
156
- for filename, content_fn in STANDARD_WORKFLOWS.items():
157
- (workflows_dir / filename).write_text(content_fn(), encoding="utf-8")
158
- print(f" Generated: .windsurf/workflows/{filename}")
170
+ for filename, content_fn in STANDARD_WORKFLOWS.items():
171
+ (workflows_dir / filename).write_text(content_fn(), encoding="utf-8")
172
+ print(f" Generated: {tree}/workflows/{filename}")
159
173
 
160
174
 
161
175
  # ---------------------------------------------------------------------------
@@ -170,15 +184,16 @@ def generate(target_dir: Path, *,
170
184
  managed_scopes: tuple[str, ...] = (
171
185
  STANDARD_SCOPE, LANG_SCOPE, CUSTOM_SCOPE,
172
186
  )) -> None:
173
- """Write ``.windsurf/rules/*.md`` and ``.windsurf/workflows/*.md``."""
187
+ """Write ``rules/*.md`` and ``workflows/*.md`` to both config trees."""
174
188
  rules = _build_rules(language_modules, rules_dir)
175
- write_rules(
176
- target_dir,
177
- rules,
178
- ".windsurf/rules",
179
- cleanup=cleanup,
180
- managed_scopes=managed_scopes,
181
- )
189
+ for tree in CONFIG_TREES:
190
+ write_rules(
191
+ target_dir,
192
+ rules,
193
+ f"{tree}/rules",
194
+ cleanup=cleanup,
195
+ managed_scopes=managed_scopes,
196
+ )
182
197
 
183
198
  if emit_workflows:
184
199
  _write_workflows(target_dir, cleanup=cleanup)
@@ -1,5 +1,11 @@
1
1
  #!/usr/bin/env python3
2
- """Generate a Windsurf skill pointer under ``.windsurf/skills/``."""
2
+ """Generate a Devin Desktop (formerly Windsurf) skill pointer.
3
+
4
+ Dual-emits under ``.devin/skills/`` (primary since the 2026-06-02 rebrand)
5
+ and ``.windsurf/skills/`` (legacy fallback). Pass an explicit ``skill_root``
6
+ to emit a single location instead (used for the ``~/.codeium/windsurf/``
7
+ global install path).
8
+ """
3
9
  from __future__ import annotations
4
10
 
5
11
  import sys
@@ -8,13 +14,17 @@ from pathlib import Path
8
14
  sys.path.insert(0, str(Path(__file__).resolve().parent))
9
15
  from skill_pointer import POINTER_SKILL_NAME, write_pointer_skill
10
16
 
17
+ DEFAULT_SKILL_ROOTS: tuple[str, ...] = (".devin/skills", ".windsurf/skills")
18
+
11
19
 
12
20
  def generate(target_dir: Path, *, emit_skill_pointer: bool = True,
13
- skill_root: str = ".windsurf/skills") -> None:
21
+ skill_root: str | None = None) -> None:
14
22
  if not emit_skill_pointer:
15
23
  return
16
- write_pointer_skill(target_dir, skill_root, "Windsurf")
17
- print(f" Generated: {skill_root}/{POINTER_SKILL_NAME}/SKILL.md")
24
+ roots = (skill_root,) if skill_root else DEFAULT_SKILL_ROOTS
25
+ for root in roots:
26
+ write_pointer_skill(target_dir, root, "Windsurf")
27
+ print(f" Generated: {root}/{POINTER_SKILL_NAME}/SKILL.md")
18
28
 
19
29
 
20
30
  def main() -> None:
@@ -381,6 +381,9 @@ _EDITOR_MARKERS: dict[str, str] = {
381
381
  ".cursor/rules": "cursor",
382
382
  ".windsurfrules": "windsurf",
383
383
  ".windsurf/rules": "windsurf",
384
+ # .devin/ is the primary Devin Desktop tree since the 2026-06-02 rebrand;
385
+ # .windsurf/ markers stay to detect legacy installs.
386
+ ".devin/rules": "windsurf",
384
387
  ".clinerules": "cline",
385
388
  ".roomodes": "roo",
386
389
  ".roo/rules": "roo",
@@ -411,11 +414,25 @@ def _detect_editors(cwd: Path) -> list[str]:
411
414
  found: set[str] = set()
412
415
  for marker, editor in _EDITOR_MARKERS.items():
413
416
  p = cwd / marker
414
- if p.exists():
415
- found.add(editor)
417
+ if not p.exists():
418
+ continue
419
+ if marker == ".agents/skills" and _is_pointer_only_skills_dir(p):
420
+ # The Antigravity CLI pointer skill also lives in .agents/skills/;
421
+ # only real (materialized) skills indicate a Codex install.
422
+ continue
423
+ found.add(editor)
416
424
  return sorted(found)
417
425
 
418
426
 
427
+ def _is_pointer_only_skills_dir(skills_dir: Path) -> bool:
428
+ """True when a skills dir holds only the ai-toolkit pointer skill."""
429
+ try:
430
+ entries = [e.name for e in skills_dir.iterdir() if not e.name.startswith(".")]
431
+ except OSError:
432
+ return False
433
+ return entries == ["ai-toolkit-skill-catalogue"]
434
+
435
+
419
436
  def _resolve_editors(editors_arg: str, cwd: Path) -> list[str]:
420
437
  """Resolve --editors argument to a list of editor names.
421
438
 
@@ -709,7 +726,7 @@ def _install_local_dry_run(reset: bool, editors: list[str] | None = None,
709
726
  _EDITOR_DRY_RUN = {
710
727
  "copilot": " Would inject: .github/copilot-instructions.md",
711
728
  "cursor": " Would generate: .cursorrules + .cursor/rules/*.mdc",
712
- "windsurf": " Would generate: .windsurfrules + .windsurf/rules/*.md",
729
+ "windsurf": " Would generate: .windsurfrules + .devin/rules/*.md + .windsurf/rules/*.md",
713
730
  "cline": " Would generate: .clinerules/*.md",
714
731
  "roo": " Would generate: .roomodes + .roo/rules/*.md",
715
732
  "aider": " Would generate: .aider.conf.yml + CONVENTIONS.md",
@@ -731,7 +748,7 @@ def _install_local_dry_run(reset: bool, editors: list[str] | None = None,
731
748
  if "cursor" in eds:
732
749
  print(" Would generate: .cursor/hooks.json + .cursor/agents/ + .cursor/skills/ (profile=full)")
733
750
  if "windsurf" in eds:
734
- print(" Would generate: .windsurf/hooks.json + .windsurf/skills/ (profile=full)")
751
+ print(" Would generate: .windsurf/hooks.json + .devin/skills/ + .windsurf/skills/ (profile=full)")
735
752
  if "cline" in eds:
736
753
  print(" Would generate: .cline/skills/ (profile=full)")
737
754
  if "augment" in eds:
@@ -851,6 +851,7 @@ _README_PLATFORM_KEY = {
851
851
  "claude code": "claude",
852
852
  "cursor": "cursor",
853
853
  "windsurf": "windsurf",
854
+ "windsurf (devin desktop)": "windsurf",
854
855
  "gemini cli": "gemini",
855
856
  "github copilot": "copilot",
856
857
  "cline": "cline",