ai-kit-python 1.4.2__py3-none-any.whl

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 (278) hide show
  1. ai_kit_python-1.4.2.dist-info/METADATA +132 -0
  2. ai_kit_python-1.4.2.dist-info/RECORD +278 -0
  3. ai_kit_python-1.4.2.dist-info/WHEEL +5 -0
  4. ai_kit_python-1.4.2.dist-info/entry_points.txt +2 -0
  5. ai_kit_python-1.4.2.dist-info/licenses/LICENSE +21 -0
  6. ai_kit_python-1.4.2.dist-info/licenses/template/ai-kit/vendor/security-audit/LICENSE +21 -0
  7. ai_kit_python-1.4.2.dist-info/licenses/template/ai-kit/vendor/strix/LICENSE +201 -0
  8. ai_kit_python-1.4.2.dist-info/top_level.txt +1 -0
  9. aikit/.agents/skills/go-work/SKILL.md +8 -0
  10. aikit/.agents/skills/laravel-work/SKILL.md +8 -0
  11. aikit/.agents/skills/moodle-work/SKILL.md +8 -0
  12. aikit/.agents/skills/project-continuity/SKILL.md +34 -0
  13. aikit/.agents/skills/python-work/SKILL.md +8 -0
  14. aikit/.dockerignore +90 -0
  15. aikit/.gitattributes +3 -0
  16. aikit/.github/workflows/check-kit.yml +70 -0
  17. aikit/.github/workflows/publish-pypi.yml +108 -0
  18. aikit/.gitignore +60 -0
  19. aikit/BOOTSTRAP_PROMPT.md +18 -0
  20. aikit/CHANGELOG.md +85 -0
  21. aikit/CLAUDE.md +3 -0
  22. aikit/CONTRIBUTING.md +46 -0
  23. aikit/Dockerfile +44 -0
  24. aikit/KIMI.md +3 -0
  25. aikit/LICENSE +21 -0
  26. aikit/MANIFEST.in +22 -0
  27. aikit/MANUS.md +3 -0
  28. aikit/Makefile +70 -0
  29. aikit/README.md +1161 -0
  30. aikit/SECURITY.md +8 -0
  31. aikit/SKILL.md +105 -0
  32. aikit/VERSION +1 -0
  33. aikit/__init__.py +1 -0
  34. aikit/ai-kit/ADAPTERS.md +5 -0
  35. aikit/ai-kit/CONTAINERS.md +5 -0
  36. aikit/ai-kit/ENGINEERING.md +5 -0
  37. aikit/ai-kit/INTENT.md +5 -0
  38. aikit/ai-kit/MODELS.md +5 -0
  39. aikit/ai-kit/SKILLS.md +5 -0
  40. aikit/ai-kit/stacks/GO.md +5 -0
  41. aikit/ai-kit/stacks/LARAVEL.md +5 -0
  42. aikit/ai-kit/stacks/MOODLE.md +5 -0
  43. aikit/ai-kit/stacks/PYTHON.md +5 -0
  44. aikit/aikit_cli.py +59 -0
  45. aikit/deploy/k8s/install-preview-job.yaml +56 -0
  46. aikit/docker/build-requirements.txt +4 -0
  47. aikit/docker/check_version.py +29 -0
  48. aikit/docker/pypi-requirements.txt +3 -0
  49. aikit/docs/ADAPTER_PATH_FIX.md +23 -0
  50. aikit/docs/BENCHMARK.md +106 -0
  51. aikit/docs/CONTEXT7.md +33 -0
  52. aikit/docs/DECISIONS.md +22 -0
  53. aikit/docs/DEVELOPMENT_PLAN.md +16 -0
  54. aikit/docs/DIALOGUE_MEMORY.md +70 -0
  55. aikit/docs/DOCKER.md +74 -0
  56. aikit/docs/DOCKER_HUB_OVERVIEW.md +311 -0
  57. aikit/docs/EVALUATION.md +30 -0
  58. aikit/docs/IMPLEMENTATION.md +134 -0
  59. aikit/docs/MANUS_REVIEW.md +46 -0
  60. aikit/docs/PYPI.md +87 -0
  61. aikit/docs/PYPI_DESCRIPTION.md +103 -0
  62. aikit/docs/SECURITY_AUDIT.md +26 -0
  63. aikit/docs/STRIX.md +28 -0
  64. aikit/docs/V0_5_0.md +27 -0
  65. aikit/docs/V0_6_4.md +25 -0
  66. aikit/docs/V1_4_0.md +73 -0
  67. aikit/docs/V1_4_1.md +55 -0
  68. aikit/docs/V1_4_2.md +33 -0
  69. aikit/docs/WEB_DESIGN.md +72 -0
  70. aikit/docs/WINDOWS_CI_FIX.md +39 -0
  71. aikit/docs/adr/0000-template.md +24 -0
  72. aikit/docs/adr/0001-adaptive-model-routing.md +30 -0
  73. aikit/docs/adr/0002-template-separation.md +24 -0
  74. aikit/docs/adr/0003-agent-registry-and-router.md +24 -0
  75. aikit/docs/adr/0004-onboarding-and-project-facts.md +24 -0
  76. aikit/docs/adr/0005-client-settings-ownership.md +30 -0
  77. aikit/docs/adr/0006-routing-evidence-and-local-measurements.md +26 -0
  78. aikit/docs/adr/0007-generated-client-configuration-and-presets.md +34 -0
  79. aikit/docs/adr/0008-unified-cli-and-fact-drift.md +20 -0
  80. aikit/docs/adr/0009-provider-review-doctor-fix-and-ci-self-install.md +20 -0
  81. aikit/docs/adr/0010-metrics-analysis-and-gemini-verification.md +20 -0
  82. aikit/docs/adr/0011-evidence-lifecycle-and-evaluation.md +20 -0
  83. aikit/docs/adr/0012-measurement-led-routing.md +31 -0
  84. aikit/docs/adr/0013-docker-cli-image.md +24 -0
  85. aikit/docs/adr/0014-local-dialogue-memory.md +22 -0
  86. aikit/docs/history/original-bundle-checks.md +11 -0
  87. aikit/docs/history/v0.1-changelog.txt +37 -0
  88. aikit/docs/history/v0.10-changelog.md +12 -0
  89. aikit/docs/history/v0.2-changelog.md +35 -0
  90. aikit/docs/history/v0.3-changelog.md +25 -0
  91. aikit/docs/history/v0.4-changelog.md +31 -0
  92. aikit/docs/history/v0.5-changelog.md +14 -0
  93. aikit/docs/history/v0.6.4-changelog.md +12 -0
  94. aikit/docs/history/v0.6.5-v0.9-changelog.md +35 -0
  95. aikit/docs/history/v1.1.0-changelog.md +12 -0
  96. aikit/docs/history/v1.2.0-changelog.md +11 -0
  97. aikit/docs/history/verification-before-measurements.md +53 -0
  98. aikit/integrations/CONVENTIONS.md +3 -0
  99. aikit/integrations/README.md +7 -0
  100. aikit/integrations/agents.json +136 -0
  101. aikit/integrations/ai-kit-check.yml +66 -0
  102. aikit/integrations/ai-kit-cline.md +4 -0
  103. aikit/integrations/ai-kit-roo.md +4 -0
  104. aikit/integrations/ai-kit-windsurf.md +4 -0
  105. aikit/integrations/ai-kit.mdc +6 -0
  106. aikit/integrations/claude-settings.data-ask.json +23 -0
  107. aikit/integrations/claude-settings.git-ask.json +10 -0
  108. aikit/integrations/copilot-instructions.md +3 -0
  109. aikit/integrations/security-audit.json +135 -0
  110. aikit/integrations/strix.json +69 -0
  111. aikit/pyproject.toml +60 -0
  112. aikit/scripts/__init__.py +1 -0
  113. aikit/scripts/adapters.py +184 -0
  114. aikit/scripts/adaptive.py +167 -0
  115. aikit/scripts/adr.py +61 -0
  116. aikit/scripts/benchmark.py +543 -0
  117. aikit/scripts/changelog.py +55 -0
  118. aikit/scripts/check_distribution.py +178 -0
  119. aikit/scripts/check_kit.py +406 -0
  120. aikit/scripts/context.py +184 -0
  121. aikit/scripts/docker_test_usage.py +55 -0
  122. aikit/scripts/doctor.py +313 -0
  123. aikit/scripts/evaluate.py +125 -0
  124. aikit/scripts/install.py +1584 -0
  125. aikit/scripts/learning.py +260 -0
  126. aikit/scripts/memory.py +344 -0
  127. aikit/scripts/memory_import.py +177 -0
  128. aikit/scripts/memory_store.py +125 -0
  129. aikit/scripts/metrics.py +351 -0
  130. aikit/scripts/router.py +399 -0
  131. aikit/template/.agents/hooks/session-start.md +3 -0
  132. aikit/template/.agents/hooks/session-start.sh +21 -0
  133. aikit/template/.agents/skills/ai-kit-bootstrap/SKILL.md +14 -0
  134. aikit/template/.agents/skills/ai-kit-review/SKILL.md +11 -0
  135. aikit/template/.agents/skills/ai-kit-sync-context/SKILL.md +12 -0
  136. aikit/template/.agents/skills/context7-docs/SKILL.md +11 -0
  137. aikit/template/.agents/skills/design-extract/SKILL.md +9 -0
  138. aikit/template/.agents/skills/dotnet-work/SKILL.md +9 -0
  139. aikit/template/.agents/skills/filament-work/SKILL.md +9 -0
  140. aikit/template/.agents/skills/frontend-work/SKILL.md +9 -0
  141. aikit/template/.agents/skills/go-work/SKILL.md +9 -0
  142. aikit/template/.agents/skills/infra-work/SKILL.md +9 -0
  143. aikit/template/.agents/skills/java-work/SKILL.md +9 -0
  144. aikit/template/.agents/skills/laravel-blade-ui/SKILL.md +9 -0
  145. aikit/template/.agents/skills/laravel-work/SKILL.md +10 -0
  146. aikit/template/.agents/skills/library-work/SKILL.md +9 -0
  147. aikit/template/.agents/skills/moodle-work/SKILL.md +9 -0
  148. aikit/template/.agents/skills/php-work/SKILL.md +9 -0
  149. aikit/template/.agents/skills/project-continuity/SKILL.md +21 -0
  150. aikit/template/.agents/skills/python-work/SKILL.md +9 -0
  151. aikit/template/.agents/skills/rust-work/SKILL.md +9 -0
  152. aikit/template/.agents/skills/security-audit/SKILL.md +16 -0
  153. aikit/template/.agents/skills/security-audit/agents/openai.yaml +2 -0
  154. aikit/template/.agents/skills/strix/SKILL.md +16 -0
  155. aikit/template/.agents/skills/strix/agents/openai.yaml +2 -0
  156. aikit/template/.agents/skills/ui-review/SKILL.md +9 -0
  157. aikit/template/.agents/skills/web-design/SKILL.md +9 -0
  158. aikit/template/AGENTS.md +16 -0
  159. aikit/template/CHANGELOG.md +6 -0
  160. aikit/template/CLAUDE.md +5 -0
  161. aikit/template/GEMINI.md +4 -0
  162. aikit/template/KIMI.md +4 -0
  163. aikit/template/MANUS.md +4 -0
  164. aikit/template/PROJECT_CONTEXT.md +35 -0
  165. aikit/template/README.md +6 -0
  166. aikit/template/WIKI.md +37 -0
  167. aikit/template/ai-kit/ADAPTERS.md +100 -0
  168. aikit/template/ai-kit/BOOTSTRAP.md +15 -0
  169. aikit/template/ai-kit/CONTAINERS.md +63 -0
  170. aikit/template/ai-kit/CONTEXT7.md +96 -0
  171. aikit/template/ai-kit/CORE.md +23 -0
  172. aikit/template/ai-kit/ENGINEERING.md +34 -0
  173. aikit/template/ai-kit/MCP.md +17 -0
  174. aikit/template/ai-kit/METRICS.md +83 -0
  175. aikit/template/ai-kit/SECURITY-AUDIT.md +21 -0
  176. aikit/template/ai-kit/SECURITY.md +34 -0
  177. aikit/template/ai-kit/SKILLS.md +37 -0
  178. aikit/template/ai-kit/STRIX.md +44 -0
  179. aikit/template/ai-kit/UPSTREAM.md +38 -0
  180. aikit/template/ai-kit/WEB-DESIGN.md +37 -0
  181. aikit/template/ai-kit/profiles/OWNER.md +19 -0
  182. aikit/template/ai-kit/project.json +14 -0
  183. aikit/template/ai-kit/router/POLICY.md +79 -0
  184. aikit/template/ai-kit/router/providers/anthropic.json +44 -0
  185. aikit/template/ai-kit/router/providers/anthropic.md +14 -0
  186. aikit/template/ai-kit/router/providers/gemini.json +13 -0
  187. aikit/template/ai-kit/router/providers/gemini.md +10 -0
  188. aikit/template/ai-kit/router/providers/kimi.json +26 -0
  189. aikit/template/ai-kit/router/providers/kimi.md +13 -0
  190. aikit/template/ai-kit/router/providers/local.json +18 -0
  191. aikit/template/ai-kit/router/providers/local.md +10 -0
  192. aikit/template/ai-kit/router/providers/openai.json +48 -0
  193. aikit/template/ai-kit/router/providers/openai.md +14 -0
  194. aikit/template/ai-kit/router/routing.json +11 -0
  195. aikit/template/ai-kit/router/selection.json +14 -0
  196. aikit/template/ai-kit/settings.json +11 -0
  197. aikit/template/ai-kit/stacks/DOTNET.md +13 -0
  198. aikit/template/ai-kit/stacks/FILAMENT.md +18 -0
  199. aikit/template/ai-kit/stacks/FRONTEND.md +9 -0
  200. aikit/template/ai-kit/stacks/GO.md +19 -0
  201. aikit/template/ai-kit/stacks/INFRA.md +12 -0
  202. aikit/template/ai-kit/stacks/JAVA.md +13 -0
  203. aikit/template/ai-kit/stacks/LARAVEL.md +15 -0
  204. aikit/template/ai-kit/stacks/LIBRARY.md +9 -0
  205. aikit/template/ai-kit/stacks/MOODLE.md +12 -0
  206. aikit/template/ai-kit/stacks/PHP.md +11 -0
  207. aikit/template/ai-kit/stacks/PYTHON.md +11 -0
  208. aikit/template/ai-kit/stacks/RUST.md +18 -0
  209. aikit/template/ai-kit/vendor/security-audit/AI-AND-LLM.md +83 -0
  210. aikit/template/ai-kit/vendor/security-audit/ATTACK-CLASSES.md +130 -0
  211. aikit/template/ai-kit/vendor/security-audit/CLIENT-SIDE.md +83 -0
  212. aikit/template/ai-kit/vendor/security-audit/CLOUD-AND-DEPLOYMENT.md +86 -0
  213. aikit/template/ai-kit/vendor/security-audit/DATA-ISOLATION-AND-LIFECYCLE.md +84 -0
  214. aikit/template/ai-kit/vendor/security-audit/DESKTOP-MOBILE-AND-LOCAL-IPC.md +89 -0
  215. aikit/template/ai-kit/vendor/security-audit/HUNTING.md +251 -0
  216. aikit/template/ai-kit/vendor/security-audit/LICENSE +21 -0
  217. aikit/template/ai-kit/vendor/security-audit/MEMORY-SAFETY-AND-BINARY.md +101 -0
  218. aikit/template/ai-kit/vendor/security-audit/PROTOCOLS-RPC-AND-MESSAGING.md +81 -0
  219. aikit/template/ai-kit/vendor/security-audit/RECONNAISSANCE.md +156 -0
  220. aikit/template/ai-kit/vendor/security-audit/RESOURCE-EXHAUSTION-AND-AVAILABILITY.md +78 -0
  221. aikit/template/ai-kit/vendor/security-audit/SKILL.md +192 -0
  222. aikit/template/ai-kit/vendor/security-audit/SUPPLY-CHAIN-AND-RELEASE.md +73 -0
  223. aikit/template/ai-kit/vendor/security-audit/VALIDATION-AND-REPORTING.md +186 -0
  224. aikit/template/ai-kit/vendor/security-audit/WEB-PROTOCOL-AND-AUTH.md +105 -0
  225. aikit/template/ai-kit/vendor/security-audit/report-schema.json +461 -0
  226. aikit/template/ai-kit/vendor/security-audit/validate-coverage-ledger.cjs +872 -0
  227. aikit/template/ai-kit/vendor/security-audit/validate-coverage-ledger.test.cjs +740 -0
  228. aikit/template/ai-kit/vendor/security-audit/validate-findings.cjs +773 -0
  229. aikit/template/ai-kit/vendor/security-audit/validate-findings.test.cjs +652 -0
  230. aikit/template/ai-kit/vendor/strix/LICENSE +201 -0
  231. aikit/template/ai-kit/vendor/strix/api-security-testing/SKILL.md +61 -0
  232. aikit/template/ai-kit/vendor/strix/application-security-testing/SKILL.md +66 -0
  233. aikit/template/ai-kit/vendor/strix/ci-security-scanning-with-strix/SKILL.md +149 -0
  234. aikit/template/ai-kit/vendor/strix/find-security-vulnerabilities-in-code/SKILL.md +62 -0
  235. aikit/template/ai-kit/vendor/strix/fix-security-vulnerabilities-with-strix/SKILL.md +77 -0
  236. aikit/template/ai-kit/vendor/strix/managed-pentesting-with-strix/SKILL.md +321 -0
  237. aikit/template/ai-kit/vendor/strix/owasp-top-10-testing/SKILL.md +64 -0
  238. aikit/template/ai-kit/vendor/strix/penetration-testing-with-strix/SKILL.md +162 -0
  239. aikit/template/ai-kit/vendor/strix/web-app-penetration-testing/SKILL.md +54 -0
  240. aikit/template/ai-kit/web-design/LARAVEL.md +51 -0
  241. aikit/template/ai-kit/web-design/LIBRARIES.md +39 -0
  242. aikit/template/ai-kit/web-design/REVIEW.md +37 -0
  243. aikit/template/ai-kit/web-design/TOOLS.md +43 -0
  244. aikit/template/docs/DECISIONS.md +7 -0
  245. aikit/template/docs/adr/0000-template.md +20 -0
  246. aikit/template/docs/design/ACCEPTANCE.md +24 -0
  247. aikit/template/docs/design/BRIEF.md +26 -0
  248. aikit/template/docs/design/SYSTEM.md +30 -0
  249. aikit/templates/agent-secrets.ignore +19 -0
  250. aikit/templates/gitignore.private +160 -0
  251. aikit/templates/gitignore.team +139 -0
  252. aikit/tests/build_distribution.py +73 -0
  253. aikit/tests/docker_smoke.py +172 -0
  254. aikit/tests/fixtures/create-junction.ps1 +18 -0
  255. aikit/tests/fixtures/golden/claude-settings-local.json +58 -0
  256. aikit/tests/test_adapters.py +105 -0
  257. aikit/tests/test_aikit.py +79 -0
  258. aikit/tests/test_benchmark.py +241 -0
  259. aikit/tests/test_check_kit.py +203 -0
  260. aikit/tests/test_context.py +76 -0
  261. aikit/tests/test_context7.py +153 -0
  262. aikit/tests/test_context_selection.py +70 -0
  263. aikit/tests/test_distribution.py +181 -0
  264. aikit/tests/test_docker_image.py +63 -0
  265. aikit/tests/test_docker_test_usage.py +54 -0
  266. aikit/tests/test_doctor.py +148 -0
  267. aikit/tests/test_evaluate.py +66 -0
  268. aikit/tests/test_install.py +939 -0
  269. aikit/tests/test_learning.py +247 -0
  270. aikit/tests/test_memory.py +371 -0
  271. aikit/tests/test_metrics.py +209 -0
  272. aikit/tests/test_native.py +342 -0
  273. aikit/tests/test_onboarding.py +301 -0
  274. aikit/tests/test_router.py +336 -0
  275. aikit/tests/test_security_audit.py +237 -0
  276. aikit/tests/test_strix.py +245 -0
  277. aikit/tests/test_web_design.py +219 -0
  278. aikit/tests/wheel_smoke.py +104 -0
@@ -0,0 +1,219 @@
1
+ """Web Design is an offline optional workflow with preserved project design memory."""
2
+ from __future__ import annotations
3
+
4
+ import contextlib
5
+ import io
6
+ import json
7
+ from pathlib import Path
8
+ import shutil
9
+ import sys
10
+ import tempfile
11
+ import unittest
12
+ from unittest.mock import patch
13
+
14
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
15
+ import adapters
16
+ import context
17
+ import doctor
18
+ import install
19
+
20
+ SKILLS = install.OPTIONAL_ENTRY_POINTS["web-design"]
21
+ DOCUMENTS = install.OPTIONAL_DOCUMENTS["web-design"]
22
+ RESOURCES = install.OPTIONAL_RESOURCES["web-design"]
23
+
24
+
25
+ class WebDesignTests(unittest.TestCase):
26
+ def setUp(self):
27
+ self.temp = tempfile.TemporaryDirectory(prefix="ai-kit-design-")
28
+ self.addCleanup(self.temp.cleanup)
29
+ base = Path(self.temp.name).resolve()
30
+ self.source = base / "reference"
31
+ self.target = base / "project with spaces"
32
+ shutil.copytree(install.ROOT, self.source, ignore=shutil.ignore_patterns("__pycache__", ".git"))
33
+
34
+ def plan(self, **options):
35
+ return install.build_plan(self.target, root=self.source, **options)
36
+
37
+ def apply(self, **options):
38
+ plan = self.plan(**options)
39
+ self.assertFalse(install.needs_review(plan), plan)
40
+ self.assertTrue(install.apply_plan(plan))
41
+ return plan
42
+
43
+ def test_default_and_all_presets_omit_the_whole_module(self):
44
+ for preset in (None, *install.PRESETS):
45
+ with self.subTest(preset=preset):
46
+ plan = self.plan(preset=preset, agents=["codex", "claude"])
47
+ self.assertNotIn("web-design", plan["extras"])
48
+ for name in SKILLS:
49
+ self.assertNotIn(f".agents/skills/{name}/SKILL.md", plan["actions"])
50
+ self.assertNotIn(f".claude/skills/{name}/SKILL.md", plan["actions"])
51
+ self.assertNotIn(f"{name}/SKILL.md".encode(), plan["actions"]["ai-kit/SKILLS.md"]["data"])
52
+ self.assertTrue(set(RESOURCES + DOCUMENTS).isdisjoint(plan["actions"]))
53
+ self.apply()
54
+ self.assertEqual(doctor.link_warnings(self.target), [])
55
+ self.assertFalse(any("design" in warning for warning in doctor.examine(self.target, root=self.source)["warnings"]))
56
+
57
+ def test_apply_is_offline_and_copies_only_selected_native_skills(self):
58
+ client_files = (".codex/config.toml", ".mcp.json", ".cursor/mcp.json")
59
+ for name in client_files:
60
+ path = self.target / name
61
+ path.parent.mkdir(parents=True, exist_ok=True)
62
+ path.write_bytes(b"Client-owned configuration stays unchanged.\n")
63
+ with patch("subprocess.Popen", side_effect=AssertionError("No package/browser/client process")), \
64
+ patch("socket.socket", side_effect=AssertionError("No external network")):
65
+ self.apply(extras=["web-design"], agents=["codex", "claude", "cursor"])
66
+ for name in SKILLS:
67
+ source = self.target / f".agents/skills/{name}/SKILL.md"
68
+ native = self.target / f".claude/skills/{name}/SKILL.md"
69
+ self.assertEqual(source.read_bytes(), native.read_bytes())
70
+ self.assertNotIn(b"disable-model-invocation", native.read_bytes())
71
+ self.assertTrue(all((self.target / name).is_file() for name in RESOURCES + DOCUMENTS))
72
+ self.assertFalse((self.target / "ai-kit/.design").exists())
73
+ for name in client_files:
74
+ self.assertEqual((self.target / name).read_bytes(), b"Client-owned configuration stays unchanged.\n")
75
+ self.assertEqual(doctor.link_warnings(self.target), [])
76
+ self.assertEqual(doctor.examine(self.target, root=self.source)["errors"], [])
77
+
78
+ def test_choices_persist_independently_and_reruns_are_stable(self):
79
+ self.apply(extras=["web-design"], agents=["codex", "claude"])
80
+ self.assertEqual(self.plan(preset="minimal")["extras"], ["web-design"])
81
+ self.apply(extras=["context7", "security-audit", "strix"])
82
+ stable = self.plan()
83
+ self.assertEqual(stable["extras"], ["context7", "security-audit", "strix", "web-design"])
84
+ self.assertEqual(stable["actions"], {})
85
+ settings = json.loads((self.target / "ai-kit/settings.json").read_text(encoding="utf-8"))
86
+ self.assertIs(settings["web_design"], True)
87
+
88
+ def test_existing_design_documents_and_local_edits_survive_source_updates(self):
89
+ original = {}
90
+ for name in DOCUMENTS:
91
+ path = self.target / name
92
+ path.parent.mkdir(parents=True, exist_ok=True)
93
+ original[name] = ("Existing project design: " + name + "\r\n").encode()
94
+ path.write_bytes(original[name])
95
+ self.apply(extras=["web-design"])
96
+ entry = ".agents/skills/web-design/SKILL.md"
97
+ local = self.target / entry
98
+ local.write_bytes(local.read_bytes() + b"\nLocal project adjustment.\n")
99
+ local_bytes = local.read_bytes()
100
+ self.apply()
101
+ self.assertEqual(local.read_bytes(), local_bytes)
102
+ for name in DOCUMENTS:
103
+ source = self.source / "template" / name
104
+ source.write_bytes(source.read_bytes() + b"\nNew distribution scaffold.\n")
105
+ source_entry = self.source / "template" / entry
106
+ source_entry.write_bytes(source_entry.read_bytes() + b"\nNew distribution guidance.\n")
107
+ plan = self.plan()
108
+ self.assertIn(entry, plan["conflicts"])
109
+ self.assertFalse(install.apply_plan(plan))
110
+ self.assertEqual(local.read_bytes(), local_bytes)
111
+ for name, data in original.items():
112
+ self.assertEqual((self.target / name).read_bytes(), data)
113
+ self.assertNotIn(name, plan["conflicts"])
114
+ self.assertTrue(any((self.target / "ai-kit/.upstream-cache/candidates").rglob("SKILL.md")))
115
+
116
+ def test_project_documents_are_not_accept_local_managed_instructions(self):
117
+ self.apply(extras=["web-design"])
118
+ with self.assertRaisesRegex(ValueError, "managed instructions"):
119
+ self.plan(accept_local=[DOCUMENTS[0]])
120
+ (self.target / DOCUMENTS[-1]).unlink()
121
+ report = doctor.examine(self.target, root=self.source)
122
+ self.assertIn("Owned project document missing: " + DOCUMENTS[-1], report["warnings"])
123
+ self.apply()
124
+ self.assertTrue((self.target / DOCUMENTS[-1]).is_file())
125
+ original = install.is_link
126
+ with patch.object(install, "is_link", side_effect=lambda p: p == self.target / DOCUMENTS[0] or original(p)):
127
+ self.assertTrue(any("Unsafe project document" in error
128
+ for error in doctor.examine(self.target, root=self.source)["errors"]))
129
+ with self.assertRaisesRegex(ValueError, "[Ll]ink"):
130
+ context.select_context(self.target, workflow="web-design")
131
+
132
+ def test_disabled_module_retains_files_but_cannot_select_workflow(self):
133
+ self.apply(extras=["web-design"], agents=["codex", "claude"])
134
+ paths = list(RESOURCES + DOCUMENTS) + [f".agents/skills/{name}/SKILL.md" for name in SKILLS]
135
+ before = {name: (self.target / name).read_bytes() for name in paths}
136
+ settings_path = self.target / "ai-kit/settings.json"
137
+ settings = json.loads(settings_path.read_text(encoding="utf-8"))
138
+ settings["web_design"] = False
139
+ settings_path.write_text(json.dumps(settings), encoding="utf-8")
140
+ plan = self.apply()
141
+ self.assertNotIn("web-design", plan["extras"])
142
+ self.assertEqual(before, {name: (self.target / name).read_bytes() for name in paths})
143
+ with self.assertRaisesRegex(ValueError, "disabled"):
144
+ context.select_context(self.target, workflow="web-design")
145
+ self.assertEqual(doctor.link_warnings(self.target), [])
146
+ retire = adapters.retirement_plan(self.target, "claude", root=self.source)
147
+ self.assertTrue(any(item["path"].endswith("web-design/SKILL.md") for item in retire["removals"]))
148
+ self.assertTrue(adapters.apply_retirement(retire))
149
+ self.assertTrue((self.target / ".agents/skills/web-design/SKILL.md").is_file())
150
+
151
+ def test_invalid_selection_and_blocked_or_linked_parents_refuse_before_writes(self):
152
+ defaults_path = self.source / "template/ai-kit/settings.json"
153
+ defaults = json.loads(defaults_path.read_text(encoding="utf-8"))
154
+ defaults["web_design"] = "true"
155
+ defaults_path.write_text(json.dumps(defaults), encoding="utf-8")
156
+ with self.assertRaisesRegex(ValueError, "web_design"):
157
+ self.plan(extras=["web-design"])
158
+ defaults.pop("web_design")
159
+ defaults_path.write_text(json.dumps(defaults), encoding="utf-8")
160
+ (self.target / "docs").mkdir(parents=True)
161
+ (self.target / "docs/design").write_bytes(b"Existing file marker.\n")
162
+ with self.assertRaisesRegex(NotADirectoryError, "directory"):
163
+ self.plan(extras=["web-design"])
164
+ self.assertFalse((self.target / "AGENTS.md").exists())
165
+ self.assertEqual((self.target / "docs/design").read_bytes(), b"Existing file marker.\n")
166
+ (self.target / "docs/design").unlink()
167
+ original = install.is_link
168
+ with patch.object(install, "is_link", side_effect=lambda p: p == self.target / "docs/design" or original(p)):
169
+ with self.assertRaisesRegex(ValueError, "[Ll]ink"):
170
+ self.plan(extras=["web-design"])
171
+
172
+ def test_plain_html_workflow_is_explicit_scoped_and_read_only(self):
173
+ self.target.mkdir(parents=True)
174
+ (self.target / "index.html").write_text("<!doctype html><title>Website</title>", encoding="utf-8")
175
+ self.apply(extras=["web-design"])
176
+ before = {p.relative_to(self.target): p.read_bytes() for p in self.target.rglob("*") if p.is_file()}
177
+ ordinary = context.select_context(self.target)
178
+ self.assertNotIn("ai-kit/WEB-DESIGN.md", {item["path"] for item in ordinary["selected"]})
179
+ report = context.select_context(self.target, workflow="web-design", budget=1)
180
+ selected = {item["path"] for item in report["selected"]}
181
+ self.assertTrue(set(DOCUMENTS).issubset(selected))
182
+ self.assertIn("ai-kit/WEB-DESIGN.md", selected)
183
+ self.assertNotIn("ai-kit/web-design/LARAVEL.md", selected)
184
+ self.assertNotIn("ai-kit/web-design/TOOLS.md", selected)
185
+ self.assertTrue(report["over_budget"])
186
+ self.assertEqual(report["profiles"], [])
187
+ review = context.select_context(self.target, workflow="ui-review")
188
+ self.assertIn("ai-kit/web-design/REVIEW.md", {item["path"] for item in review["selected"]})
189
+ self.assertEqual(before, {p.relative_to(self.target): p.read_bytes() for p in self.target.rglob("*") if p.is_file()})
190
+
191
+ def test_declared_laravel_scope_selects_blade_guidance_without_sibling_leakage(self):
192
+ self.apply(extras=["web-design"])
193
+ facts = self.target / "ai-kit/project.json"
194
+ facts.write_text(json.dumps({"schema": 1, "modules": [
195
+ {"path": "/web/", "profiles": ["LARAVEL", "FRONTEND"]},
196
+ {"path": "/worker/", "profiles": ["GO"]}]}), encoding="utf-8")
197
+ report = context.select_context(self.target, path="web", workflow="web-design")
198
+ selected = {item["path"] for item in report["selected"]}
199
+ self.assertIn("ai-kit/web-design/LARAVEL.md", selected)
200
+ self.assertIn(".agents/skills/laravel-blade-ui/SKILL.md", selected)
201
+ self.assertIn("ai-kit/profiles/OWNER.md", selected)
202
+ worker = context.select_context(self.target, path="worker", workflow="web-design")
203
+ self.assertNotIn("ai-kit/web-design/LARAVEL.md", {item["path"] for item in worker["selected"]})
204
+ with self.assertRaisesRegex(ValueError, "Unknown context workflow"):
205
+ context.select_context(self.target, workflow="unknown")
206
+ (self.target / "ai-kit/web-design/TOOLS.md").unlink()
207
+ with self.assertRaisesRegex(ValueError, "Missing required context"):
208
+ context.select_context(self.target, workflow="design-extract")
209
+
210
+ def test_cli_preview_is_available_without_applying_or_creating_a_target(self):
211
+ output = io.StringIO()
212
+ with contextlib.redirect_stdout(output), patch.object(install, "ROOT", self.source):
213
+ self.assertEqual(install.main([str(self.target), "--with-web-design"]), 0)
214
+ self.assertIn("web-design", json.loads(output.getvalue())["extras"])
215
+ self.assertFalse(self.target.exists())
216
+
217
+
218
+ if __name__ == "__main__":
219
+ unittest.main()
@@ -0,0 +1,104 @@
1
+ """Install a built wheel into a clean venv and exercise its bundled resources."""
2
+ from __future__ import annotations
3
+
4
+ from pathlib import Path
5
+ import os
6
+ import subprocess
7
+ import sys
8
+ import tempfile
9
+ import venv
10
+ import zipfile
11
+
12
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
13
+ import check_distribution
14
+
15
+
16
+ def venv_python(root: Path) -> Path:
17
+ return root / ("Scripts/python.exe" if sys.platform == "win32" else "bin/python")
18
+
19
+
20
+ def venv_aikit(root: Path) -> Path:
21
+ return root / ("Scripts/aikit.exe" if sys.platform == "win32" else "bin/aikit")
22
+
23
+
24
+ def main(argv: list[str] | None = None) -> int:
25
+ arguments = argv if argv is not None else sys.argv[1:]
26
+ if len(arguments) != 1:
27
+ raise SystemExit("Usage: wheel_smoke.py WHEEL")
28
+ wheel = Path(arguments[0]).resolve()
29
+ if wheel.is_dir():
30
+ candidates = sorted(wheel.glob("*.whl"))
31
+ if len(candidates) != 1:
32
+ raise SystemExit("Expected exactly one wheel in: " + str(wheel))
33
+ wheel = candidates[0]
34
+ if not wheel.is_file():
35
+ raise SystemExit("Wheel not found: " + str(wheel))
36
+ version = (Path(__file__).resolve().parents[1] / "VERSION").read_text(encoding="utf-8").strip()
37
+ check_distribution.inspect_distribution(wheel, version)
38
+ with zipfile.ZipFile(wheel) as package:
39
+ for name in ("AGENTS.md", "PROJECT_CONTEXT.md", "WIKI.md"):
40
+ if "aikit/" + name in package.namelist() or "aikit/template/" + name not in package.namelist():
41
+ raise RuntimeError("Wheel must exclude local maintainer files and retain project templates: " + name)
42
+ with tempfile.TemporaryDirectory(prefix="ai-kit-wheel-") as folder:
43
+ root = Path(folder).resolve()
44
+ env = root / "venv"
45
+ venv.EnvBuilder(with_pip=True, clear=True).create(env)
46
+ python = venv_python(env)
47
+ environment = dict(os.environ)
48
+ environment.pop("PYTHONPATH", None)
49
+ def run(command):
50
+ subprocess.run([str(item) for item in command], cwd=root, env=environment, check=True)
51
+ run([python, "-m", "pip", "install", "--no-index", "--no-deps", wheel])
52
+ aikit = venv_aikit(env)
53
+ run([python, "-c", "import importlib.metadata; assert importlib.metadata.version("
54
+ + repr(check_distribution.PROJECT_NAME) + ") == " + repr(version)])
55
+ run([aikit, "--version"])
56
+ run([aikit, "route", "implement a feature"])
57
+ for module in ("learning", "adaptive", "benchmark", "memory"):
58
+ run([python, "-c", "from aikit.scripts import " + module])
59
+ run([aikit, "benchmark", "--help"])
60
+ run([aikit, "check"])
61
+ # Exercise the packaged offline dialogue pipeline, not only dispatcher help.
62
+ workspace = root / "private memory"
63
+ for name in ("chatgpt", "openrouter"):
64
+ source = root / (name + ".json")
65
+ source.write_text('{"messages":[{"role":"user","content":"Fact stack = Laravel"}]}', encoding="utf-8")
66
+ run([aikit, "memory", "import", workspace, source, "--name", name, "--apply"])
67
+ merged = subprocess.run([str(aikit), "memory", "merge", str(workspace), "--left", "chatgpt",
68
+ "--right", "openrouter", "--apply"], cwd=root, env=environment,
69
+ capture_output=True, text=True, check=True)
70
+ import json
71
+ merge_id = json.loads(merged.stdout)["merge_id"]
72
+ run([aikit, "memory", "export", workspace, "--merge", merge_id, "--draft", "--apply"])
73
+ if not (workspace / "exports" / merge_id / "chatgpt-draft.md").is_file():
74
+ raise RuntimeError("Wheel dialogue export is missing")
75
+ project = root / "project"
76
+ run([aikit, "install", project, "--preset", "minimal", "--agent", "codex", "--apply", "--check"])
77
+ run([aikit, "adapters", "report", "--project", project])
78
+ run([aikit, "context", "select", project])
79
+ audit_project = root / "audit project"
80
+ run([aikit, "install", audit_project, "--with-security-audit", "--with-strix", "--with-context7", "--with-web-design", "--agent", "codex",
81
+ "--agent", "claude", "--apply", "--check"])
82
+ from_manifest = audit_project / "ai-kit/vendor/security-audit/UPSTREAM.json"
83
+ if not from_manifest.is_file() or not (audit_project / ".agents/skills/security-audit/agents/openai.yaml").is_file():
84
+ raise RuntimeError("Wheel did not install the optional audit package and invocation policy")
85
+ strix_manifest = audit_project / "ai-kit/vendor/strix/UPSTREAM.json"
86
+ strix_reference = audit_project / "ai-kit/vendor/strix/penetration-testing-with-strix/SKILL.md"
87
+ if not strix_manifest.is_file() or not strix_reference.is_file() \
88
+ or not (audit_project / ".claude/skills/strix/SKILL.md").is_file():
89
+ raise RuntimeError("Wheel did not install the optional Strix references and native entry point")
90
+ if not (audit_project / "ai-kit/CONTEXT7.md").is_file() \
91
+ or not (audit_project / ".claude/skills/context7-docs/SKILL.md").is_file():
92
+ raise RuntimeError("Wheel did not install the Context7 guide and native documentation skill")
93
+ for name in ("web-design", "laravel-blade-ui", "ui-review", "design-extract"):
94
+ if not (audit_project / f".claude/skills/{name}/SKILL.md").is_file():
95
+ raise RuntimeError("Wheel did not install the Web Design native skill: " + name)
96
+ if not (audit_project / "docs/design/BRIEF.md").is_file():
97
+ raise RuntimeError("Wheel did not initialize the Web Design project documents")
98
+ run([aikit, "context", "select", audit_project, "--workflow", "web-design"])
99
+ run([aikit, "install", audit_project, "--apply", "--check"])
100
+ return 0
101
+
102
+
103
+ if __name__ == "__main__":
104
+ raise SystemExit(main())