@softspark/ai-toolkit 1.5.1 → 1.6.1

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.
package/llms.txt CHANGED
@@ -14,6 +14,10 @@
14
14
  - [Best Practices](kb/best-practices/README.md)
15
15
  - [No Hardcoded Counts in Secondary Docs](kb/best-practices/no-hardcoded-counts.md)
16
16
  - [How-To Guides](kb/howto/README.md)
17
+ - [Plan: Cloud Security Pack — Multi-Cloud Audit](kb/planning/cloud-security-pack-plan.md)
18
+ - [Plan: Enterprise Config Inheritance — Multi-Repo Governance with `extends`](kb/planning/enterprise-config-inheritance-plan.md)
19
+ - [Plan: Local Dashboard — `ai-toolkit ui`](kb/planning/local-dashboard-plan.md)
20
+ - [Plan: Offline-First SLM Profile — Lightweight Mode for Local Models](kb/planning/offline-slm-profile-plan.md)
17
21
  - [SOP: Claude Toolkit Maintenance](kb/procedures/maintenance-sop.md)
18
22
  - [SOP: Release Preparation](kb/procedures/release-preparation-sop.md)
19
23
  - [SOP: Release Verification](kb/procedures/release-verification-sop.md)
package/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.5.1",
2
+ "version": "1.6.1",
3
3
  "components": {
4
4
  "agents": {
5
5
  "description": "44 specialized agents (orchestrator, backend, frontend, security, devops, etc.)",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softspark/ai-toolkit",
3
- "version": "1.5.1",
3
+ "version": "1.6.1",
4
4
  "description": "Professional-grade AI coding toolkit: 92 skills, 44 agents, multi-platform support (Claude, Cursor, Windsurf, Copilot, Gemini, Cline, Roo Code, Aider, Augment, Google Antigravity), machine-enforced safety constitution, persona presets, skill security auditor, expanded lifecycle hooks, 11 plugin packs, and benchmark tooling.",
5
5
  "keywords": [
6
6
  "claude",
@@ -2,8 +2,9 @@
2
2
  """add-rule -- Register a rule file in ~/.ai-toolkit/rules/.
3
3
 
4
4
  Registered rules are automatically injected into all AI tool configs
5
- (Claude, Cursor, Windsurf, Gemini) on next 'ai-toolkit install',
6
- and into project-local configs (Copilot, Cline) on 'ai-toolkit install --local'.
5
+ on next 'ai-toolkit install' or 'ai-toolkit update':
6
+ Global: Claude, Cursor, Windsurf, Gemini, Augment
7
+ Local (--local): all of the above + Copilot, Cline, Roo, Aider, Antigravity
7
8
 
8
9
  Usage:
9
10
  add_rule.py <rule-file> [rule-name]
@@ -47,8 +48,8 @@ def main() -> None:
47
48
  print(f"Registered: '{rule_name}' -> {dest}")
48
49
  print()
49
50
  print("Apply now:")
50
- print(" ai-toolkit update # global (Claude, Cursor, Windsurf, Gemini)")
51
- print(" ai-toolkit update --local # project-local (Copilot, Cline)")
51
+ print(" ai-toolkit update # global (Claude, Cursor, Windsurf, Gemini, Augment)")
52
+ print(" ai-toolkit update --local # project-local (all editors)")
52
53
 
53
54
 
54
55
  if __name__ == "__main__":
@@ -439,3 +439,99 @@ def write_rules(target_dir: Path, rules: dict[str, callable],
439
439
  (out_dir / filename).write_text(content_fn(), encoding="utf-8")
440
440
  tag = f" ({label})" if label else ""
441
441
  print(f" Generated: {subdir}/{filename}{tag}")
442
+
443
+
444
+ # ---------------------------------------------------------------------------
445
+ # Language → glob patterns for editors with file-type activation
446
+ # ---------------------------------------------------------------------------
447
+
448
+ LANG_GLOBS: dict[str, list[str]] = {
449
+ "python": ["**/*.py"],
450
+ "typescript": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"],
451
+ "golang": ["**/*.go"],
452
+ "rust": ["**/*.rs"],
453
+ "java": ["**/*.java"],
454
+ "kotlin": ["**/*.kt", "**/*.kts"],
455
+ "swift": ["**/*.swift"],
456
+ "dart": ["**/*.dart"],
457
+ "csharp": ["**/*.cs"],
458
+ "php": ["**/*.php"],
459
+ "cpp": ["**/*.cpp", "**/*.cc", "**/*.cxx", "**/*.hpp", "**/*.h"],
460
+ "ruby": ["**/*.rb"],
461
+ }
462
+
463
+
464
+ def _resolve_app_rules_dir() -> Path:
465
+ """Resolve path to app/rules/ directory."""
466
+ return Path(__file__).resolve().parent.parent / "app" / "rules"
467
+
468
+
469
+ def build_language_rules(
470
+ language_modules: list[str] | None,
471
+ ) -> dict[str, callable]:
472
+ """Build language-specific rule entries from app/rules/<lang>/.
473
+
474
+ Returns dict of filename -> callable (same shape as STANDARD_RULES).
475
+ Language modules use format ``rules-python``, ``rules-typescript``, etc.
476
+ Always includes ``common`` when any language is detected.
477
+ """
478
+ if not language_modules:
479
+ return {}
480
+
481
+ rules_src = _resolve_app_rules_dir()
482
+ if not rules_src.is_dir():
483
+ return {}
484
+
485
+ langs: list[str] = []
486
+ for mod in language_modules:
487
+ if mod.startswith("rules-"):
488
+ langs.append(mod[6:])
489
+ if not langs:
490
+ return {}
491
+
492
+ all_dirs: list[str] = ["common"]
493
+ for lang in langs:
494
+ if lang not in all_dirs:
495
+ all_dirs.append(lang)
496
+
497
+ result: dict[str, callable] = {}
498
+ for lang in all_dirs:
499
+ lang_path = rules_src / lang
500
+ if not lang_path.is_dir():
501
+ continue
502
+ parts: list[str] = []
503
+ for f in sorted(lang_path.glob("*.md")):
504
+ content = f.read_text(encoding="utf-8")
505
+ # Strip YAML frontmatter if present
506
+ if content.startswith("---"):
507
+ end = content.find("---", 3)
508
+ if end != -1:
509
+ content = content[end + 3:].lstrip("\n")
510
+ parts.append(content.rstrip("\n"))
511
+
512
+ if parts:
513
+ combined = "\n\n".join(parts) + "\n"
514
+ filename = f"{PREFIX}lang-{lang}.md"
515
+ # Capture value via default arg to avoid late-binding closure
516
+ result[filename] = (lambda c: lambda: c)(combined)
517
+
518
+ return result
519
+
520
+
521
+ def build_registered_rules(
522
+ rules_dir: Path | None,
523
+ ) -> dict[str, callable]:
524
+ """Build entries from user's registered rules (~/.ai-toolkit/rules/*.md).
525
+
526
+ Returns dict of filename -> callable (same shape as STANDARD_RULES).
527
+ """
528
+ if not rules_dir or not rules_dir.is_dir():
529
+ return {}
530
+
531
+ result: dict[str, callable] = {}
532
+ for rule_file in sorted(rules_dir.glob("*.md")):
533
+ content = rule_file.read_text(encoding="utf-8")
534
+ filename = f"{PREFIX}custom-{rule_file.stem}.md"
535
+ result[filename] = (lambda c: lambda: c)(content)
536
+
537
+ return result
@@ -18,8 +18,9 @@ def main() -> None:
18
18
  print("# Use the architect mode for complex tasks, aligned with our 2-phase workflow")
19
19
  print("architect: true")
20
20
  print()
21
- print("# Read global rules ")
21
+ print("# Read project rules (loaded as read-only context)")
22
22
  print("read:")
23
+ print(' - "CONVENTIONS.md"')
23
24
  print(' - ".claude/CLAUDE.md"')
24
25
  print(' - ".claude/constitution.md"')
25
26
  print(' - "AGENTS.md"')
@@ -18,12 +18,23 @@ import sys
18
18
  from pathlib import Path
19
19
 
20
20
  sys.path.insert(0, str(Path(__file__).resolve().parent))
21
- from dir_rules_shared import STANDARD_RULES, STANDARD_WORKFLOWS, write_rules
22
-
23
-
24
- def generate(target_dir: Path) -> None:
21
+ from dir_rules_shared import (
22
+ STANDARD_RULES,
23
+ STANDARD_WORKFLOWS,
24
+ build_language_rules,
25
+ build_registered_rules,
26
+ write_rules,
27
+ )
28
+
29
+
30
+ def generate(target_dir: Path, *,
31
+ language_modules: list[str] | None = None,
32
+ rules_dir: Path | None = None) -> None:
25
33
  """Write .agent/rules/ and .agent/workflows/ files to target_dir."""
26
- write_rules(target_dir, STANDARD_RULES, ".agent/rules")
34
+ rules = dict(STANDARD_RULES)
35
+ rules.update(build_language_rules(language_modules))
36
+ rules.update(build_registered_rules(rules_dir))
37
+ write_rules(target_dir, rules, ".agent/rules")
27
38
  write_rules(target_dir, STANDARD_WORKFLOWS, ".agent/workflows")
28
39
 
29
40
 
@@ -3,7 +3,7 @@
3
3
 
4
4
  Augment supports per-file rules with frontmatter:
5
5
  - type: always_apply — always in context
6
- - type: auto_attached + globs — attached for matching files
6
+ - type: agent_requested + globs — attached for matching files
7
7
 
8
8
  The existing generate_augment.py creates a single always_apply file.
9
9
  This generator adds granular per-category rules with appropriate types.
@@ -18,7 +18,10 @@ from pathlib import Path
18
18
 
19
19
  sys.path.insert(0, str(Path(__file__).resolve().parent))
20
20
  from dir_rules_shared import (
21
+ LANG_GLOBS,
21
22
  PREFIX,
23
+ build_language_rules,
24
+ build_registered_rules,
22
25
  cleanup_stale,
23
26
  rule_agents_and_skills,
24
27
  rule_code_style,
@@ -69,7 +72,7 @@ def _make_rules() -> dict[str, callable]:
69
72
  f"{PREFIX}code-style.md": lambda: _augment_wrap(
70
73
  rule_code_style(),
71
74
  description="Code style conventions",
72
- rule_type="auto_attached",
75
+ rule_type="agent_requested",
73
76
  globs=["*.py", "*.ts", "*.tsx", "*.js", "*.jsx", "*.go", "*.rs",
74
77
  "*.java", "*.kt", "*.swift", "*.dart", "*.cs", "*.php",
75
78
  "*.cpp", "*.cc", "*.rb"],
@@ -77,7 +80,7 @@ def _make_rules() -> dict[str, callable]:
77
80
  f"{PREFIX}testing.md": lambda: _augment_wrap(
78
81
  rule_testing(),
79
82
  description="Testing standards and patterns",
80
- rule_type="auto_attached",
83
+ rule_type="agent_requested",
81
84
  globs=["*.test.*", "*.spec.*", "test_*", "**/tests/**",
82
85
  "**/test/**", "**/__tests__/**"],
83
86
  ),
@@ -87,14 +90,45 @@ def _make_rules() -> dict[str, callable]:
87
90
  RULES = _make_rules()
88
91
 
89
92
 
90
- def generate(target_dir: Path) -> None:
93
+ def generate(target_dir: Path, *,
94
+ language_modules: list[str] | None = None,
95
+ rules_dir: Path | None = None) -> None:
91
96
  """Write .augment/rules/ai-toolkit-*.md files to target_dir."""
92
- rules_dir = target_dir / ".augment" / "rules"
93
- rules_dir.mkdir(parents=True, exist_ok=True)
94
- cleanup_stale(rules_dir, set(RULES.keys()) | {"ai-toolkit.md"})
95
-
96
- for filename, content_fn in RULES.items():
97
- (rules_dir / filename).write_text(content_fn(), encoding="utf-8")
97
+ out_dir = target_dir / ".augment" / "rules"
98
+ out_dir.mkdir(parents=True, exist_ok=True)
99
+
100
+ all_rules: dict[str, callable] = dict(RULES)
101
+
102
+ # Add language rules with Augment frontmatter
103
+ for filename, content_fn in build_language_rules(language_modules).items():
104
+ lang = filename.removeprefix(f"{PREFIX}lang-").removesuffix(".md")
105
+ globs = LANG_GLOBS.get(lang)
106
+ if globs:
107
+ all_rules[filename] = (lambda fn, l, g: lambda: _augment_wrap(
108
+ fn(),
109
+ description=f"{l.title()} language rules",
110
+ rule_type="agent_requested",
111
+ globs=g,
112
+ ))(content_fn, lang, globs)
113
+ else:
114
+ # common — always apply
115
+ all_rules[filename] = (lambda fn, l: lambda: _augment_wrap(
116
+ fn(),
117
+ description=f"{l.title()} language rules",
118
+ ))(content_fn, lang)
119
+
120
+ # Add registered rules with Augment frontmatter
121
+ for filename, content_fn in build_registered_rules(rules_dir).items():
122
+ name = filename.removeprefix(f"{PREFIX}custom-").removesuffix(".md")
123
+ all_rules[filename] = (lambda fn, n: lambda: _augment_wrap(
124
+ fn(),
125
+ description=f"Custom rule: {n}",
126
+ ))(content_fn, name)
127
+
128
+ cleanup_stale(out_dir, set(all_rules.keys()) | {"ai-toolkit.md"})
129
+
130
+ for filename, content_fn in all_rules.items():
131
+ (out_dir / filename).write_text(content_fn(), encoding="utf-8")
98
132
  print(f" Generated: .augment/rules/{filename}")
99
133
 
100
134
 
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env python3
2
- """Generate .cline/rules/*.md files for Cline.
2
+ """Generate .clinerules/*.md files for Cline.
3
3
 
4
- Cline reads directory-based rules from .cline/rules/*.md (since Q1 2025).
5
- The legacy .clinerules single-file format is still generated separately
6
- by generate_cline.py for backwards compatibility.
4
+ Cline reads rules from the .clinerules/ directory (since Cline 3.7).
5
+ Each .md file inside .clinerules/ is automatically loaded.
6
+ The legacy .clinerules single-file format is replaced by this directory.
7
7
 
8
8
  Usage:
9
9
  python3 scripts/generate_cline_rules.py [target-dir]
@@ -14,12 +14,27 @@ import sys
14
14
  from pathlib import Path
15
15
 
16
16
  sys.path.insert(0, str(Path(__file__).resolve().parent))
17
- from dir_rules_shared import STANDARD_RULES, write_rules
18
-
19
-
20
- def generate(target_dir: Path) -> None:
21
- """Write .cline/rules/*.md files to target_dir."""
22
- write_rules(target_dir, STANDARD_RULES, ".cline/rules")
17
+ from dir_rules_shared import (
18
+ STANDARD_RULES,
19
+ build_language_rules,
20
+ build_registered_rules,
21
+ write_rules,
22
+ )
23
+
24
+
25
+ def generate(target_dir: Path, *,
26
+ language_modules: list[str] | None = None,
27
+ rules_dir: Path | None = None) -> None:
28
+ """Write .clinerules/*.md files to target_dir."""
29
+ # Migrate: if .clinerules exists as a single file, remove it
30
+ # so the directory can be created (Cline 3.7+ uses directory format)
31
+ clinerules = target_dir / ".clinerules"
32
+ if clinerules.is_file():
33
+ clinerules.unlink()
34
+ rules = dict(STANDARD_RULES)
35
+ rules.update(build_language_rules(language_modules))
36
+ rules.update(build_registered_rules(rules_dir))
37
+ write_rules(target_dir, rules, ".clinerules")
23
38
 
24
39
 
25
40
  def main() -> None:
@@ -20,7 +20,10 @@ from pathlib import Path
20
20
 
21
21
  sys.path.insert(0, str(Path(__file__).resolve().parent))
22
22
  from dir_rules_shared import (
23
+ LANG_GLOBS,
23
24
  PREFIX,
25
+ build_language_rules,
26
+ build_registered_rules,
24
27
  cleanup_stale,
25
28
  rule_agents_and_skills,
26
29
  rule_code_style,
@@ -100,21 +103,47 @@ RULES = _make_rules()
100
103
  # Main
101
104
  # ---------------------------------------------------------------------------
102
105
 
103
- def generate(target_dir: Path) -> None:
106
+ def generate(target_dir: Path, *,
107
+ language_modules: list[str] | None = None,
108
+ rules_dir: Path | None = None) -> None:
104
109
  """Write .cursor/rules/*.mdc files to target_dir."""
105
- rules_dir = target_dir / ".cursor" / "rules"
106
- rules_dir.mkdir(parents=True, exist_ok=True)
110
+ out_dir = target_dir / ".cursor" / "rules"
111
+ out_dir.mkdir(parents=True, exist_ok=True)
112
+
113
+ all_rules: dict[str, callable] = dict(RULES)
114
+
115
+ # Add language rules wrapped in .mdc frontmatter
116
+ for filename, content_fn in build_language_rules(language_modules).items():
117
+ lang = filename.removeprefix(f"{PREFIX}lang-").removesuffix(".md")
118
+ globs = LANG_GLOBS.get(lang)
119
+ mdc_name = filename.replace(".md", ".mdc")
120
+ all_rules[mdc_name] = (lambda fn, l, g: lambda: _mdc(
121
+ fn(),
122
+ description=f"{l.title()} language rules",
123
+ globs=g if g else None,
124
+ always_apply=not g,
125
+ ))(content_fn, lang, globs)
126
+
127
+ # Add registered rules wrapped in .mdc frontmatter
128
+ for filename, content_fn in build_registered_rules(rules_dir).items():
129
+ name = filename.removeprefix(f"{PREFIX}custom-").removesuffix(".md")
130
+ mdc_name = filename.replace(".md", ".mdc")
131
+ all_rules[mdc_name] = (lambda fn, n: lambda: _mdc(
132
+ fn(),
133
+ description=f"Custom rule: {n}",
134
+ always_apply=True,
135
+ ))(content_fn, name)
107
136
 
108
137
  # Clean stale ai-toolkit-*.mdc files
109
- current = set(RULES.keys())
110
- if rules_dir.is_dir():
111
- for f in rules_dir.iterdir():
138
+ current = set(all_rules.keys())
139
+ if out_dir.is_dir():
140
+ for f in out_dir.iterdir():
112
141
  if f.name.startswith(PREFIX) and f.suffix == ".mdc" and f.name not in current:
113
142
  f.unlink()
114
143
  print(f" Removed stale: .cursor/rules/{f.name}")
115
144
 
116
- for filename, content_fn in RULES.items():
117
- (rules_dir / filename).write_text(content_fn(), encoding="utf-8")
145
+ for filename, content_fn in all_rules.items():
146
+ (out_dir / filename).write_text(content_fn(), encoding="utf-8")
118
147
  print(f" Generated: .cursor/rules/{filename}")
119
148
 
120
149
 
@@ -13,12 +13,22 @@ import sys
13
13
  from pathlib import Path
14
14
 
15
15
  sys.path.insert(0, str(Path(__file__).resolve().parent))
16
- from dir_rules_shared import STANDARD_RULES, write_rules
16
+ from dir_rules_shared import (
17
+ STANDARD_RULES,
18
+ build_language_rules,
19
+ build_registered_rules,
20
+ write_rules,
21
+ )
17
22
 
18
23
 
19
- def generate(target_dir: Path) -> None:
24
+ def generate(target_dir: Path, *,
25
+ language_modules: list[str] | None = None,
26
+ rules_dir: Path | None = None) -> None:
20
27
  """Write .roo/rules/*.md files to target_dir."""
21
- write_rules(target_dir, STANDARD_RULES, ".roo/rules")
28
+ rules = dict(STANDARD_RULES)
29
+ rules.update(build_language_rules(language_modules))
30
+ rules.update(build_registered_rules(rules_dir))
31
+ write_rules(target_dir, rules, ".roo/rules")
22
32
 
23
33
 
24
34
  def main() -> None:
@@ -14,12 +14,22 @@ import sys
14
14
  from pathlib import Path
15
15
 
16
16
  sys.path.insert(0, str(Path(__file__).resolve().parent))
17
- from dir_rules_shared import STANDARD_RULES, write_rules
17
+ from dir_rules_shared import (
18
+ STANDARD_RULES,
19
+ build_language_rules,
20
+ build_registered_rules,
21
+ write_rules,
22
+ )
18
23
 
19
24
 
20
- def generate(target_dir: Path) -> None:
25
+ def generate(target_dir: Path, *,
26
+ language_modules: list[str] | None = None,
27
+ rules_dir: Path | None = None) -> None:
21
28
  """Write .windsurf/rules/*.md files to target_dir."""
22
- write_rules(target_dir, STANDARD_RULES, ".windsurf/rules")
29
+ rules = dict(STANDARD_RULES)
30
+ rules.update(build_language_rules(language_modules))
31
+ rules.update(build_registered_rules(rules_dir))
32
+ write_rules(target_dir, rules, ".windsurf/rules")
23
33
 
24
34
 
25
35
  def main() -> None:
@@ -136,7 +136,6 @@ _EDITOR_MARKERS: dict[str, str] = {
136
136
  ".windsurfrules": "windsurf",
137
137
  ".windsurf/rules": "windsurf",
138
138
  ".clinerules": "cline",
139
- ".cline/rules": "cline",
140
139
  ".roomodes": "roo",
141
140
  ".roo/rules": "roo",
142
141
  ".aider.conf.yml": "aider",
@@ -228,7 +227,8 @@ def install_local_project(rules_dir: Path, dry_run: bool, reset: bool,
228
227
  _inject_language_rules(cwd, language_modules)
229
228
 
230
229
  # Install editor configs only for resolved editors
231
- _create_local_ai_tool_configs(cwd, rules_dir, resolved_editors)
230
+ _create_local_ai_tool_configs(cwd, rules_dir, resolved_editors,
231
+ language_modules=language_modules)
232
232
 
233
233
 
234
234
  def _inject_language_rules(cwd: Path, language_modules: list[str] | None) -> None:
@@ -308,7 +308,7 @@ def _install_local_dry_run(reset: bool, editors: list[str] | None = None) -> Non
308
308
  "copilot": " Would inject: .github/copilot-instructions.md",
309
309
  "cursor": " Would generate: .cursorrules + .cursor/rules/*.mdc",
310
310
  "windsurf": " Would generate: .windsurfrules + .windsurf/rules/*.md",
311
- "cline": " Would generate: .clinerules + .cline/rules/*.md",
311
+ "cline": " Would generate: .clinerules/*.md",
312
312
  "roo": " Would generate: .roomodes + .roo/rules/*.md",
313
313
  "aider": " Would generate: .aider.conf.yml + CONVENTIONS.md",
314
314
  "augment": " Would generate: .augment/rules/ai-toolkit-*.md",
@@ -396,7 +396,8 @@ def _create_local_settings(cwd: Path, reset: bool) -> None:
396
396
 
397
397
 
398
398
  def _create_local_ai_tool_configs(cwd: Path, rules_dir: Path,
399
- editors: list[str]) -> None:
399
+ editors: list[str],
400
+ language_modules: list[str] | None = None) -> None:
400
401
  eds = set(editors)
401
402
 
402
403
  if "copilot" in eds:
@@ -413,7 +414,8 @@ def _create_local_ai_tool_configs(cwd: Path, rules_dir: Path,
413
414
  rules_dir,
414
415
  )
415
416
  from generate_cursor_mdc import generate as gen_cursor_mdc
416
- gen_cursor_mdc(cwd)
417
+ gen_cursor_mdc(cwd, language_modules=language_modules,
418
+ rules_dir=rules_dir)
417
419
 
418
420
  if "windsurf" in eds:
419
421
  inject_with_rules(
@@ -422,23 +424,26 @@ def _create_local_ai_tool_configs(cwd: Path, rules_dir: Path,
422
424
  rules_dir,
423
425
  )
424
426
  from generate_windsurf_rules import generate as gen_windsurf_rules
425
- gen_windsurf_rules(cwd)
427
+ gen_windsurf_rules(cwd, language_modules=language_modules,
428
+ rules_dir=rules_dir)
426
429
 
427
430
  if "cline" in eds:
428
- inject_with_rules(
429
- "generate-cline.sh",
430
- cwd / ".clinerules",
431
- rules_dir,
432
- )
431
+ # Migrate: remove legacy .clinerules single file (replaced by directory)
432
+ legacy_clinerules = cwd / ".clinerules"
433
+ if legacy_clinerules.is_file():
434
+ legacy_clinerules.unlink()
435
+ print(" Migrated: .clinerules file → .clinerules/ directory")
433
436
  from generate_cline_rules import generate as gen_cline_rules
434
- gen_cline_rules(cwd)
437
+ gen_cline_rules(cwd, language_modules=language_modules,
438
+ rules_dir=rules_dir)
435
439
 
436
440
  if "roo" in eds:
437
441
  roo_output = run_script("generate-roo-modes.sh", capture=True)
438
442
  (cwd / ".roomodes").write_text(roo_output, encoding="utf-8")
439
443
  print(" Created: .roomodes")
440
444
  from generate_roo_rules import generate as gen_roo_rules
441
- gen_roo_rules(cwd)
445
+ gen_roo_rules(cwd, language_modules=language_modules,
446
+ rules_dir=rules_dir)
442
447
 
443
448
  if "aider" in eds:
444
449
  aider_output = run_script("generate-aider-conf.sh", capture=True)
@@ -448,10 +453,12 @@ def _create_local_ai_tool_configs(cwd: Path, rules_dir: Path,
448
453
 
449
454
  if "augment" in eds:
450
455
  from generate_augment_rules import generate as gen_augment_rules
451
- gen_augment_rules(cwd)
456
+ gen_augment_rules(cwd, language_modules=language_modules,
457
+ rules_dir=rules_dir)
452
458
 
453
459
  if "antigravity" in eds:
454
460
  from generate_antigravity import generate as gen_antigravity
455
- gen_antigravity(cwd)
461
+ gen_antigravity(cwd, language_modules=language_modules,
462
+ rules_dir=rules_dir)
456
463
 
457
464
  run_script("install-git-hooks.sh", str(cwd))
@@ -46,6 +46,7 @@ VALID_HOOK_EVENTS = frozenset({
46
46
 
47
47
  VALID_KB_CATEGORIES = frozenset({
48
48
  "reference", "howto", "procedures", "troubleshooting", "best-practices",
49
+ "planning",
49
50
  })
50
51
 
51
52
  PLANNED_ASSETS = [