@softspark/ai-toolkit 1.9.0 → 2.0.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +40 -14
  2. package/README.md +18 -18
  3. package/app/ARCHITECTURE.md +3 -3
  4. package/app/hooks/governance-capture.sh +1 -1
  5. package/app/hooks/pre-compact-save.sh +1 -1
  6. package/app/hooks/session-context.sh +1 -1
  7. package/app/hooks/track-usage.sh +2 -2
  8. package/app/hooks.json +20 -20
  9. package/app/plugins/memory-pack/README.md +1 -1
  10. package/app/plugins/memory-pack/hooks/observation-capture.sh +2 -2
  11. package/app/plugins/memory-pack/hooks/session-summary.sh +1 -1
  12. package/app/plugins/memory-pack/scripts/init_db.py +2 -2
  13. package/app/plugins/memory-pack/skills/mem-search/SKILL.md +3 -3
  14. package/app/skills/hook-creator/SKILL.md +2 -2
  15. package/app/skills/mem-search/SKILL.md +3 -3
  16. package/app/skills/repeat/SKILL.md +1 -1
  17. package/bin/ai-toolkit.js +9 -4
  18. package/kb/history/completed/enterprise-config-inheritance-plan-20260412.md +32 -34
  19. package/kb/history/completed/offline-slm-profile-plan-20260411.md +12 -12
  20. package/kb/planning/cloud-security-pack-plan.md +3 -3
  21. package/kb/procedures/maintenance-sop.md +3 -3
  22. package/kb/reference/architecture-overview.md +5 -5
  23. package/kb/reference/benchmark-config.md +1 -1
  24. package/kb/reference/competitive-features-implementation.md +2 -2
  25. package/kb/reference/enterprise-config-guide.md +9 -9
  26. package/kb/reference/extension-api.md +1 -1
  27. package/kb/reference/global-install-model.md +2 -2
  28. package/kb/reference/hooks-catalog.md +30 -30
  29. package/kb/reference/integrations.md +3 -3
  30. package/kb/reference/manifest-install.md +4 -4
  31. package/kb/reference/plugin-pack-conventions.md +5 -5
  32. package/kb/reference/stats.md +3 -3
  33. package/kb/reference/sync.md +3 -3
  34. package/llms-full.txt +118 -899
  35. package/llms.txt +0 -1
  36. package/manifest.json +3 -3
  37. package/package.json +1 -1
  38. package/scripts/add_rule.py +3 -2
  39. package/scripts/benchmark_config.py +3 -1
  40. package/scripts/compile_slm.py +9 -7
  41. package/scripts/config_cli.py +7 -7
  42. package/scripts/config_lock.py +12 -4
  43. package/scripts/config_merger.py +1 -1
  44. package/scripts/config_resolver.py +20 -7
  45. package/scripts/config_scaffold.py +4 -4
  46. package/scripts/config_validator.py +3 -3
  47. package/scripts/dir_rules_shared.py +1 -1
  48. package/scripts/doctor.py +3 -2
  49. package/scripts/install.py +12 -7
  50. package/scripts/install_git_hooks.py +1 -1
  51. package/scripts/install_steps/ai_tools.py +4 -4
  52. package/scripts/install_steps/hooks.py +1 -1
  53. package/scripts/install_steps/install_state.py +8 -6
  54. package/scripts/install_steps/project_registry.py +5 -5
  55. package/scripts/migrate.py +241 -0
  56. package/scripts/paths.py +50 -0
  57. package/scripts/plugin.py +8 -6
  58. package/scripts/remove_rule.py +5 -4
  59. package/scripts/stats.py +3 -2
  60. package/scripts/sync.py +3 -2
  61. package/scripts/update_projects.py +1 -1
  62. package/scripts/version_check.py +5 -2
  63. package/kb/planning/local-dashboard-plan.md +0 -773
package/llms.txt CHANGED
@@ -17,7 +17,6 @@
17
17
  - [Plan: Offline-First SLM Profile — Lightweight Mode for Local Models](kb/history/completed/offline-slm-profile-plan-20260411.md)
18
18
  - [How-To Guides](kb/howto/README.md)
19
19
  - [Plan: Cloud Security Pack — Multi-Cloud Audit](kb/planning/cloud-security-pack-plan.md)
20
- - [Plan: Local Dashboard — `ai-toolkit ui`](kb/planning/local-dashboard-plan.md)
21
20
  - [SOP: Claude Toolkit Maintenance](kb/procedures/maintenance-sop.md)
22
21
  - [SOP: Release Preparation](kb/procedures/release-preparation-sop.md)
23
22
  - [SOP: Release Verification](kb/procedures/release-verification-sop.md)
package/manifest.json CHANGED
@@ -18,7 +18,7 @@
18
18
  "hooks": {
19
19
  "description": "21 global hook entries across 12 lifecycle events + 5 skill-scoped lifecycle hooks",
20
20
  "path": "app/hooks.json + app/hooks/*.sh",
21
- "target": "~/.claude/settings.json (merge) + ~/.ai-toolkit/hooks/ (copy)",
21
+ "target": "~/.claude/settings.json (merge) + ~/.softspark/ai-toolkit/hooks/ (copy)",
22
22
  "type": "merge+copy",
23
23
  "tags": ["core", "hooks", "quality"]
24
24
  },
@@ -159,9 +159,9 @@
159
159
  },
160
160
  "config_inheritance": {
161
161
  "schema": "scripts/schemas/ai-toolkit-config.schema.json",
162
- "project_config": ".ai-toolkit.json",
162
+ "project_config": ".softspark-toolkit.json",
163
163
  "base_config": "ai-toolkit.config.json",
164
- "lock_file": ".ai-toolkit.lock.json",
164
+ "lock_file": ".softspark-toolkit.lock.json",
165
165
  "v1_fields": ["extends", "profile", "agents", "rules", "constitution", "enforce"],
166
166
  "max_extends_depth": 5
167
167
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softspark/ai-toolkit",
3
- "version": "1.9.0",
3
+ "version": "2.0.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",
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """add-rule -- Register a rule file in ~/.ai-toolkit/rules/.
2
+ """add-rule -- Register a rule file in ~/.softspark/ai-toolkit/rules/.
3
3
 
4
4
  Registered rules are automatically injected into all AI tool configs
5
5
  on next 'ai-toolkit install' or 'ai-toolkit update':
@@ -39,7 +39,8 @@ def main() -> None:
39
39
  if not rule_name:
40
40
  print("Error: rule name is empty after sanitization", file=sys.stderr)
41
41
  sys.exit(1)
42
- rules_dir = Path.home() / ".ai-toolkit" / "rules"
42
+ from paths import RULES_DIR
43
+ rules_dir = RULES_DIR
43
44
  rules_dir.mkdir(parents=True, exist_ok=True)
44
45
 
45
46
  dest = rules_dir / f"{rule_name}.md"
@@ -9,8 +9,10 @@ from pathlib import Path
9
9
  sys.path.insert(0, str(Path(__file__).resolve().parent))
10
10
  from _common import toolkit_dir
11
11
 
12
+ from paths import HOOKS_DIR as _HOOKS_DIR
13
+
12
14
  CLAUDE_DIR = Path.home() / ".claude"
13
- HOOKS_DIR = Path.home() / ".ai-toolkit" / "hooks"
15
+ HOOKS_DIR = _HOOKS_DIR
14
16
  DASHBOARD = toolkit_dir / "benchmarks" / "ecosystem-dashboard.json"
15
17
 
16
18
 
@@ -14,7 +14,7 @@ Options:
14
14
  --model-size SIZE Model size: 7b, 8b, 14b, 32b, 70b (default: auto-detect)
15
15
  --persona NAME Persona preset to prioritize (e.g., backend-lead)
16
16
  --lang LANGS Comma-separated languages to include (e.g., python,typescript)
17
- --output PATH Output file path (default: ~/.ai-toolkit/compiled/slm-system-prompt.md)
17
+ --output PATH Output file path (default: ~/.softspark/ai-toolkit/compiled/slm-system-prompt.md)
18
18
  --format FORMAT Output format: raw, ollama, json-string, aider (default: raw)
19
19
  --dry-run Show what would be included without writing output
20
20
  --level LEVEL Compression level: ultra-light, light, standard, extended (default: auto)
@@ -171,7 +171,8 @@ class Component:
171
171
 
172
172
  def _load_usage_stats() -> dict[str, int]:
173
173
  """Load skill invocation counts from stats.json."""
174
- stats_path = Path.home() / ".ai-toolkit" / "stats.json"
174
+ from paths import STATS_FILE
175
+ stats_path = STATS_FILE
175
176
  if not stats_path.is_file():
176
177
  return {}
177
178
  try:
@@ -840,13 +841,13 @@ Note: Replace the FROM line in the Modelfile with your base model (e.g., FROM ll
840
841
  ## Aider Setup
841
842
 
842
843
  1. Compile system prompt:
843
- ai-toolkit compile-slm --format aider --model-size {model_size} --output .ai-toolkit-system.md
844
+ ai-toolkit compile-slm --format aider --model-size {model_size} --output .softspark-toolkit-system.md
844
845
 
845
846
  2. Run Aider with custom system prompt:
846
- aider --system-prompt-file .ai-toolkit-system.md
847
+ aider --system-prompt-file .softspark-toolkit-system.md
847
848
 
848
849
  3. Or add to .aider.conf.yml:
849
- system-prompt-file: .ai-toolkit-system.md""",
850
+ system-prompt-file: .softspark-toolkit-system.md""",
850
851
 
851
852
  "continue-dev": """\
852
853
  ## Continue.dev Setup
@@ -949,7 +950,7 @@ def build_parser() -> argparse.ArgumentParser:
949
950
  )
950
951
  parser.add_argument(
951
952
  "--output", default="",
952
- help="Output file path (default: ~/.ai-toolkit/compiled/slm-system-prompt.md)",
953
+ help="Output file path (default: ~/.softspark/ai-toolkit/compiled/slm-system-prompt.md)",
953
954
  )
954
955
  parser.add_argument(
955
956
  "--format",
@@ -1021,7 +1022,8 @@ def main(argv: list[str] | None = None) -> int:
1021
1022
  # Write output
1022
1023
  output_path = args.output
1023
1024
  if not output_path:
1024
- compiled_dir = Path.home() / ".ai-toolkit" / "compiled"
1025
+ from paths import COMPILED_DIR
1026
+ compiled_dir = COMPILED_DIR
1025
1027
  compiled_dir.mkdir(parents=True, exist_ok=True)
1026
1028
  output_path = str(compiled_dir / "slm-system-prompt.md")
1027
1029
 
@@ -2,7 +2,7 @@
2
2
  """CLI dispatcher for ai-toolkit config subcommands.
3
3
 
4
4
  Usage:
5
- ai-toolkit config validate [path] — Validate .ai-toolkit.json
5
+ ai-toolkit config validate [path] — Validate .softspark-toolkit.json
6
6
  ai-toolkit config diff [path] — Show project vs base differences
7
7
  ai-toolkit config init — Interactive config setup (MVP Phase 3)
8
8
  ai-toolkit config create-base <name> — Scaffold base config package (MVP Phase 3)
@@ -33,7 +33,7 @@ from config_scaffold import create_base_package, create_project_config
33
33
  from config_lock import check_lock_staleness
34
34
 
35
35
 
36
- PROJECT_CONFIG_FILENAME = ".ai-toolkit.json"
36
+ from paths import PROJECT_CONFIG_FILENAME
37
37
 
38
38
 
39
39
  # ---------------------------------------------------------------------------
@@ -41,7 +41,7 @@ PROJECT_CONFIG_FILENAME = ".ai-toolkit.json"
41
41
  # ---------------------------------------------------------------------------
42
42
 
43
43
  def cmd_validate(args: list[str]) -> int:
44
- """Validate .ai-toolkit.json schema and extends resolution."""
44
+ """Validate .softspark-toolkit.json schema and extends resolution."""
45
45
  project_dir = Path(args[0]) if args else Path.cwd()
46
46
 
47
47
  try:
@@ -350,7 +350,7 @@ def cmd_check(args: list[str]) -> int:
350
350
  Exit codes:
351
351
  0 — project complies with base config
352
352
  1 — violations found
353
- 2 — .ai-toolkit.json not found
353
+ 2 — .softspark-toolkit.json not found
354
354
  """
355
355
  project_dir = Path(args[0]) if args and not args[0].startswith("-") else Path.cwd()
356
356
  json_output = "--json" in args
@@ -365,7 +365,7 @@ def cmd_check(args: list[str]) -> int:
365
365
  return 1
366
366
  if config is None:
367
367
  if json_output:
368
- print(json.dumps({"status": "error", "code": 2, "message": "No .ai-toolkit.json found"}))
368
+ print(json.dumps({"status": "error", "code": 2, "message": "No .softspark-toolkit.json found"}))
369
369
  else:
370
370
  print(f" ✗ No {PROJECT_CONFIG_FILENAME} found in {project_dir}")
371
371
  return 2
@@ -513,9 +513,9 @@ def main() -> None:
513
513
  print("Usage: ai-toolkit config <subcommand> [args...]")
514
514
  print()
515
515
  print("Subcommands:")
516
- print(" validate [path] Validate .ai-toolkit.json schema + extends")
516
+ print(" validate [path] Validate .softspark-toolkit.json schema + extends")
517
517
  print(" diff [path] Show project vs base config differences")
518
- print(" init [flags] Create .ai-toolkit.json (--extends, --profile, --no-extends)")
518
+ print(" init [flags] Create .softspark-toolkit.json (--extends, --profile, --no-extends)")
519
519
  print(" create-base <name> Scaffold base config npm package")
520
520
  print(" check [path] CI enforcement check (exit 0=pass, 1=fail, 2=no config)")
521
521
  sys.exit(0)
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  """Lock file management for ai-toolkit config inheritance.
3
3
 
4
- Generates and consumes .ai-toolkit.lock.json for reproducible
4
+ Generates and consumes .softspark-toolkit.lock.json for reproducible
5
5
  extends resolution across team members and CI.
6
6
 
7
7
  Stdlib-only — no external dependencies.
@@ -15,7 +15,7 @@ from pathlib import Path
15
15
  from typing import Any
16
16
 
17
17
 
18
- LOCK_FILENAME = ".ai-toolkit.lock.json"
18
+ LOCK_FILENAME = ".softspark-toolkit.lock.json"
19
19
  LOCK_VERSION = 1
20
20
 
21
21
 
@@ -23,14 +23,22 @@ LOCK_VERSION = 1
23
23
  # Public API
24
24
  # ---------------------------------------------------------------------------
25
25
 
26
+ LEGACY_LOCK_FILENAME = ".ai-toolkit.lock.json"
27
+
28
+
26
29
  def load_lock_file(project_dir: Path) -> dict[str, Any] | None:
27
30
  """Load lock file from project directory.
28
31
 
29
- Returns None if the file doesn't exist.
32
+ Falls back to legacy .ai-toolkit.lock.json if the new file doesn't exist.
33
+ Returns None if neither file exists.
30
34
  """
31
35
  lock_path = project_dir / LOCK_FILENAME
32
36
  if not lock_path.is_file():
33
- return None
37
+ legacy_path = project_dir / LEGACY_LOCK_FILENAME
38
+ if legacy_path.is_file():
39
+ lock_path = legacy_path
40
+ else:
41
+ return None
34
42
  try:
35
43
  with open(lock_path, encoding="utf-8") as f:
36
44
  return json.load(f)
@@ -66,7 +66,7 @@ def merge_config_chain(
66
66
 
67
67
  Args:
68
68
  base_configs: Ordered list of base config dicts (deepest ancestor first).
69
- project_config: The project-level .ai-toolkit.json data.
69
+ project_config: The project-level .softspark-toolkit.json data.
70
70
 
71
71
  Returns:
72
72
  MergeResult with the final merged config.
@@ -2,7 +2,7 @@
2
2
  """Config resolver for ai-toolkit extends system.
3
3
 
4
4
  Resolves base configs from npm packages, git URLs, or local paths.
5
- Caches resolved configs in ~/.ai-toolkit/config-cache/.
5
+ Caches resolved configs in ~/.softspark/ai-toolkit/config-cache/.
6
6
 
7
7
  Stdlib-only — no external dependencies.
8
8
  """
@@ -25,15 +25,23 @@ from typing import Any
25
25
  # Constants
26
26
  # ---------------------------------------------------------------------------
27
27
 
28
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
29
+ from paths import (
30
+ BASE_CONFIG_FILENAME,
31
+ CONFIG_CACHE_DIR,
32
+ LEGACY_PROJECT_CONFIG,
33
+ PROJECT_CONFIG_FILENAME as _PROJECT_CONFIG,
34
+ )
35
+
28
36
  MAX_EXTENDS_DEPTH = 5
29
- CONFIG_FILENAME = "ai-toolkit.config.json"
30
- PROJECT_CONFIG_FILENAME = ".ai-toolkit.json"
37
+ CONFIG_FILENAME = BASE_CONFIG_FILENAME
38
+ PROJECT_CONFIG_FILENAME = _PROJECT_CONFIG
31
39
  CACHE_DIR_NAME = "config-cache"
32
40
 
33
41
 
34
42
  def _cache_root() -> Path:
35
43
  """Return the cache root directory."""
36
- return Path(os.environ.get("AI_TOOLKIT_HOME", Path.home() / ".ai-toolkit")) / CACHE_DIR_NAME
44
+ return CONFIG_CACHE_DIR
37
45
 
38
46
 
39
47
  # ---------------------------------------------------------------------------
@@ -84,7 +92,7 @@ def resolve_extends(
84
92
  """Resolve an extends chain into an ordered list of base configs.
85
93
 
86
94
  Args:
87
- extends_value: The extends string from .ai-toolkit.json.
95
+ extends_value: The extends string from .softspark-toolkit.json.
88
96
  project_root: The project directory (for resolving relative paths).
89
97
  refresh: Force re-fetch ignoring cache.
90
98
 
@@ -100,12 +108,17 @@ def resolve_extends(
100
108
 
101
109
 
102
110
  def load_project_config(project_root: str | Path) -> dict[str, Any] | None:
103
- """Load .ai-toolkit.json from the project root.
111
+ """Load .softspark-toolkit.json from the project root.
104
112
 
105
- Returns None if the file doesn't exist.
113
+ Falls back to legacy .ai-toolkit.json if the new file doesn't exist.
114
+ Returns None if neither file exists.
106
115
  """
107
116
  config_path = Path(project_root) / PROJECT_CONFIG_FILENAME
108
117
  if not config_path.is_file():
118
+ # Fallback to legacy filename
119
+ legacy_path = Path(project_root) / LEGACY_PROJECT_CONFIG
120
+ if legacy_path.is_file():
121
+ return _load_json(legacy_path)
109
122
  return None
110
123
  return _load_json(config_path)
111
124
 
@@ -2,7 +2,7 @@
2
2
  """Scaffolding for ai-toolkit config inheritance.
3
3
 
4
4
  - create_base_package(): Scaffold an npm base config package
5
- - create_project_config(): Generate .ai-toolkit.json for a project
5
+ - create_project_config(): Generate .softspark-toolkit.json for a project
6
6
 
7
7
  Stdlib-only — no external dependencies.
8
8
  """
@@ -118,7 +118,7 @@ Shared ai-toolkit configuration for {org}.
118
118
 
119
119
  ### In your project
120
120
 
121
- Create `.ai-toolkit.json` in your project root:
121
+ Create `.softspark-toolkit.json` in your project root:
122
122
 
123
123
  ```json
124
124
  {{
@@ -201,7 +201,7 @@ def create_project_config(
201
201
  extends: str = "",
202
202
  profile: str = "standard",
203
203
  ) -> Path:
204
- """Generate .ai-toolkit.json for a project.
204
+ """Generate .softspark-toolkit.json for a project.
205
205
 
206
206
  Args:
207
207
  project_dir: Project root directory.
@@ -218,7 +218,7 @@ def create_project_config(
218
218
 
219
219
  config["profile"] = profile
220
220
 
221
- config_path = project_dir / ".ai-toolkit.json"
221
+ config_path = project_dir / ".softspark-toolkit.json"
222
222
  _write_json(config_path, config)
223
223
  return config_path
224
224
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  """Config validator for ai-toolkit extends system.
3
3
 
4
- Validates .ai-toolkit.json against schema, checks enforce constraints,
4
+ Validates .softspark-toolkit.json against schema, checks enforce constraints,
5
5
  verifies constitution integrity, and validates referenced files exist.
6
6
 
7
7
  Stdlib-only — no external dependencies.
@@ -45,7 +45,7 @@ def validate_project_config(
45
45
  config: dict[str, Any],
46
46
  project_root: Path | None = None,
47
47
  ) -> list[str]:
48
- """Validate a project-level .ai-toolkit.json.
48
+ """Validate a project-level .softspark-toolkit.json.
49
49
 
50
50
  Returns list of error strings (empty = valid).
51
51
  """
@@ -352,7 +352,7 @@ def _validate_file_references(
352
352
  # ---------------------------------------------------------------------------
353
353
 
354
354
  def main() -> None:
355
- """CLI: validate .ai-toolkit.json and print results."""
355
+ """CLI: validate .softspark-toolkit.json and print results."""
356
356
  if len(sys.argv) < 2:
357
357
  print("Usage: config_validator.py <config.json> [--strict]", file=sys.stderr)
358
358
  sys.exit(1)
@@ -521,7 +521,7 @@ def build_language_rules(
521
521
  def build_registered_rules(
522
522
  rules_dir: Path | None,
523
523
  ) -> dict[str, callable]:
524
- """Build entries from user's registered rules (~/.ai-toolkit/rules/*.md).
524
+ """Build entries from user's registered rules (~/.softspark/ai-toolkit/rules/*.md).
525
525
 
526
526
  Returns dict of filename -> callable (same shape as STANDARD_RULES).
527
527
  """
package/scripts/doctor.py CHANGED
@@ -27,6 +27,7 @@ from pathlib import Path
27
27
 
28
28
  sys.path.insert(0, str(Path(__file__).resolve().parent))
29
29
  from _common import toolkit_dir
30
+ from paths import HOOKS_DIR as _HOOKS_DIR, RULES_DIR as _RULES_DIR
30
31
 
31
32
 
32
33
  # ---------------------------------------------------------------------------
@@ -34,8 +35,8 @@ from _common import toolkit_dir
34
35
  # ---------------------------------------------------------------------------
35
36
 
36
37
  CLAUDE_DIR = Path.home() / ".claude"
37
- HOOKS_DIR = Path.home() / ".ai-toolkit" / "hooks"
38
- RULES_DIR = Path.home() / ".ai-toolkit" / "rules"
38
+ HOOKS_DIR = _HOOKS_DIR
39
+ RULES_DIR = _RULES_DIR
39
40
  BENCHMARK_DASHBOARD = toolkit_dir / "benchmarks" / "ecosystem-dashboard.json"
40
41
 
41
42
  VALID_EVENTS = frozenset({
@@ -16,7 +16,7 @@ Other tools (global config locations):
16
16
  - Windsurf: ~/.codeium/windsurf/memories/global_rules.md
17
17
  - Gemini: ~/.gemini/GEMINI.md
18
18
 
19
- Registered rules (~/.ai-toolkit/rules/*.md) are also injected into
19
+ Registered rules (~/.softspark/ai-toolkit/rules/*.md) are also injected into
20
20
  all of the above. Add rules with: ai-toolkit add-rule <rule.md>
21
21
 
22
22
  Usage:
@@ -467,9 +467,9 @@ def resolve_extends_config(
467
467
  config_path: str = "",
468
468
  refresh: bool = False,
469
469
  ) -> dict | None:
470
- """Resolve .ai-toolkit.json extends and return merged config.
470
+ """Resolve .softspark-toolkit.json extends and return merged config.
471
471
 
472
- Returns None if no .ai-toolkit.json or no extends field.
472
+ Returns None if no .softspark-toolkit.json or no extends field.
473
473
  Prints warnings/errors and exits on fatal errors.
474
474
  """
475
475
  if config_path:
@@ -596,6 +596,10 @@ def main() -> None:
596
596
  print_status()
597
597
  sys.exit(0)
598
598
 
599
+ # Auto-migrate from legacy ~/.ai-toolkit if needed
600
+ from migrate import run_full_migration
601
+ run_full_migration(dry_run=cfg.get("dry_run", False))
602
+
599
603
  target_dir: Path = cfg["target_dir"]
600
604
  only: str = cfg["only"]
601
605
  skip: str = cfg["skip"]
@@ -618,8 +622,9 @@ def main() -> None:
618
622
  if not local:
619
623
  local = True # language rules are project-local
620
624
 
621
- rules_dir = Path.home() / ".ai-toolkit" / "rules"
622
- hooks_scripts_dir = Path.home() / ".ai-toolkit" / "hooks"
625
+ from paths import RULES_DIR, HOOKS_DIR, TOOLKIT_DATA_DIR
626
+ rules_dir = RULES_DIR
627
+ hooks_scripts_dir = HOOKS_DIR
623
628
 
624
629
  # --local always implies --auto-detect (why install locally without language rules?)
625
630
  if local and not auto_detect and not modules_arg:
@@ -653,7 +658,7 @@ def main() -> None:
653
658
 
654
659
  if local:
655
660
  # --local: project-local only, no global install
656
- # Check for .ai-toolkit.json extends system
661
+ # Check for .softspark-toolkit.json extends system
657
662
  config_path_arg: str = cfg["config"]
658
663
  refresh_base: bool = cfg["refresh_base"]
659
664
  merged_config = resolve_extends_config(
@@ -716,7 +721,7 @@ def main() -> None:
716
721
  extends=extends_source,
717
722
  )
718
723
  if is_new:
719
- print(f" Registered project in ~/.ai-toolkit/projects.json")
724
+ print(f" Registered project in {TOOLKIT_DATA_DIR / 'projects.json'}")
720
725
 
721
726
  print_summary(local=local)
722
727
 
@@ -30,7 +30,7 @@ if git diff --cached -S'<<<<<<<' --name-only | grep -q '.*'; then
30
30
  fi
31
31
 
32
32
  # 2. Call the global quality-check if it exists
33
- QUALITY_CHECK="$HOME/.ai-toolkit/hooks/quality-check.sh"
33
+ QUALITY_CHECK="$HOME/.softspark/ai-toolkit/hooks/quality-check.sh"
34
34
  if [ -x "$QUALITY_CHECK" ]; then
35
35
  if ! "$QUALITY_CHECK"; then
36
36
  echo "ERROR: Linter or type checks failed."
@@ -182,7 +182,7 @@ def install_local_project(rules_dir: Path, dry_run: bool, reset: bool,
182
182
  - ``--editors cursor,aider``: install only these
183
183
  - (empty): auto-detect from existing project files, install only those
184
184
 
185
- If ``merged_config`` is provided (from .ai-toolkit.json extends resolution),
185
+ If ``merged_config`` is provided (from .softspark-toolkit.json extends resolution),
186
186
  additional rules and constitution amendments from the base config are injected.
187
187
  """
188
188
  cwd = Path.cwd()
@@ -192,7 +192,7 @@ def install_local_project(rules_dir: Path, dry_run: bool, reset: bool,
192
192
  print(f"## Project-local ({cwd})")
193
193
  if merged_config and merged_config.get("_extends_meta"):
194
194
  meta = merged_config["_extends_meta"]
195
- print(f" Config: .ai-toolkit.json (extends: {meta['source']})")
195
+ print(f" Config: .softspark-toolkit.json (extends: {meta['source']})")
196
196
  if reset:
197
197
  print(" Mode: RESET (all local configs will be wiped and recreated)")
198
198
  if resolved_editors:
@@ -326,9 +326,9 @@ def _apply_extends_config(cwd: Path, merged: dict) -> None:
326
326
  # Record merged config summary
327
327
  meta = merged.get("_extends_meta")
328
328
  if meta:
329
- state_file = cwd / ".ai-toolkit-extends.json"
329
+ state_file = cwd / ".softspark-toolkit-extends.json"
330
330
  state_file.write_text(_json.dumps(meta, indent=2) + "\n", encoding="utf-8")
331
- print(f" Saved: .ai-toolkit-extends.json (resolution metadata)")
331
+ print(f" Saved: .softspark-toolkit-extends.json (resolution metadata)")
332
332
 
333
333
 
334
334
  def _inject_language_rules(cwd: Path, language_modules: list[str] | None) -> None:
@@ -55,7 +55,7 @@ def _copy_hook_scripts(claude_dir: Path, hooks_scripts_dir: Path) -> None:
55
55
  shutil.copy2(hook_file, dst)
56
56
  dst.chmod(dst.stat().st_mode | 0o111)
57
57
  copied += 1
58
- print(f" Copied: {copied} hook scripts to ~/.ai-toolkit/hooks/")
58
+ print(f" Copied: {copied} hook scripts to ~/.softspark/ai-toolkit/hooks/")
59
59
  legacy_hooks = claude_dir / "hooks"
60
60
  if legacy_hooks.is_symlink():
61
61
  legacy_hooks.unlink()
@@ -1,20 +1,22 @@
1
- """Track installed modules and versions in ~/.ai-toolkit/state.json."""
1
+ """Track installed modules and versions in ~/.softspark/ai-toolkit/state.json."""
2
2
  from __future__ import annotations
3
3
 
4
4
  import json
5
+ import sys
5
6
  from datetime import datetime, timezone
6
7
  from pathlib import Path
7
8
 
8
- STATE_FILENAME = "state.json"
9
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
10
+ from paths import TOOLKIT_DATA_DIR, STATE_FILE
9
11
 
10
12
 
11
13
  def _state_path() -> Path:
12
14
  """Return the canonical path to state.json."""
13
- return Path.home() / ".ai-toolkit" / STATE_FILENAME
15
+ return STATE_FILE
14
16
 
15
17
 
16
18
  def load_state() -> dict:
17
- """Load state from ~/.ai-toolkit/state.json.
19
+ """Load state from ~/.softspark/ai-toolkit/state.json.
18
20
 
19
21
  Returns an empty dict if the file does not exist or is malformed.
20
22
  """
@@ -32,7 +34,7 @@ def load_state() -> dict:
32
34
 
33
35
 
34
36
  def save_state(state: dict) -> None:
35
- """Save state to ~/.ai-toolkit/state.json.
37
+ """Save state to ~/.softspark/ai-toolkit/state.json.
36
38
 
37
39
  Creates the parent directory if it does not exist.
38
40
  """
@@ -113,7 +115,7 @@ def print_status() -> None:
113
115
  """Print a human-readable summary of the install state."""
114
116
  state = load_state()
115
117
  if not state:
116
- print("No install state found (~/.ai-toolkit/state.json missing).")
118
+ print(f"No install state found ({STATE_FILE} missing).")
117
119
  print("Run 'ai-toolkit install' to set up the toolkit.")
118
120
  return
119
121
 
@@ -1,6 +1,6 @@
1
1
  """Project registry — tracks which directories have ai-toolkit installed locally.
2
2
 
3
- Stores registry in ~/.ai-toolkit/projects.json.
3
+ Stores registry in ~/.softspark/ai-toolkit/projects.json.
4
4
  Used by `ai-toolkit update` to propagate updates to all registered projects,
5
5
  and by `ai-toolkit projects` to list/manage them.
6
6
 
@@ -9,18 +9,18 @@ Stdlib-only — no external dependencies.
9
9
  from __future__ import annotations
10
10
 
11
11
  import json
12
- import os
12
+ import sys
13
13
  from datetime import datetime, timezone
14
14
  from pathlib import Path
15
15
  from typing import Any
16
16
 
17
-
18
- REGISTRY_FILENAME = "projects.json"
17
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
18
+ from paths import PROJECTS_FILE
19
19
 
20
20
 
21
21
  def _registry_path() -> Path:
22
22
  """Return the canonical path to projects.json."""
23
- return Path(os.environ.get("AI_TOOLKIT_HOME", Path.home() / ".ai-toolkit")) / REGISTRY_FILENAME
23
+ return PROJECTS_FILE
24
24
 
25
25
 
26
26
  def _now_iso() -> str: