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,237 @@
1
+ """Pinned optional skill installation, client policy, and conservative upgrades."""
2
+ import contextlib
3
+ import hashlib
4
+ import io
5
+ import json
6
+ from pathlib import Path
7
+ import shutil
8
+ import sys
9
+ import tempfile
10
+ import unittest
11
+ from unittest.mock import patch
12
+
13
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
14
+ import doctor
15
+ import install
16
+
17
+
18
+ class SecurityAuditTests(unittest.TestCase):
19
+ def setUp(self):
20
+ self.temp = tempfile.TemporaryDirectory(prefix="ai-kit-security-audit-")
21
+ self.addCleanup(self.temp.cleanup)
22
+ base = Path(self.temp.name).resolve()
23
+ self.source = base / "reference"
24
+ self.target = base / "project with spaces"
25
+ shutil.copytree(install.ROOT, self.source, ignore=shutil.ignore_patterns("__pycache__", ".git"))
26
+
27
+ def plan(self, **options):
28
+ return install.build_plan(self.target, root=self.source, **options)
29
+
30
+ def apply(self, **options):
31
+ plan = self.plan(**options)
32
+ self.assertFalse(install.needs_review(plan), plan)
33
+ self.assertTrue(install.apply_plan(plan))
34
+ return plan
35
+
36
+ def package_path(self, name, *, source=False):
37
+ root = self.source / "template" if source else self.target
38
+ return root / install.SECURITY_AUDIT_PACKAGE / name
39
+
40
+ def update_fixture_pin(self, name, data):
41
+ """Model a reviewed upstream update inside a disposable distribution."""
42
+ self.package_path(name, source=True).write_bytes(data)
43
+ manifest_path = self.source / install.SECURITY_AUDIT_MANIFEST
44
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
45
+ manifest["revision"] = "f" * 40
46
+ record = manifest["files"][name]
47
+ record.update(size=len(data), sha256=install.digest(data),
48
+ git_blob=hashlib.sha1(b"blob " + str(len(data)).encode() + b"\0" + data,
49
+ usedforsecurity=False).hexdigest())
50
+ manifest_path.write_text(json.dumps(manifest) + "\n", encoding="utf-8")
51
+
52
+ def test_every_preset_leaves_the_skill_absent_until_opted_in(self):
53
+ for preset in (None, *install.PRESETS):
54
+ with self.subTest(preset=preset):
55
+ plan = self.plan(preset=preset, agents=["codex", "claude"])
56
+ self.assertNotIn("security-audit", plan["extras"])
57
+ self.assertFalse(any("/security-audit/" in path for path in plan["actions"]))
58
+ self.assertNotIn(b"../.agents/skills/security-audit/SKILL.md",
59
+ plan["actions"]["ai-kit/SKILLS.md"]["data"])
60
+ self.apply()
61
+ self.assertFalse((self.target / install.SECURITY_AUDIT_SKILL).exists())
62
+ self.assertFalse((self.target / install.SECURITY_AUDIT_PACKAGE).exists())
63
+ self.assertEqual(doctor.link_warnings(self.target), [])
64
+
65
+ def test_preview_and_apply_need_no_network_node_or_audit_execution(self):
66
+ with patch("subprocess.Popen", side_effect=AssertionError("No processes during installation")), \
67
+ patch("socket.socket", side_effect=AssertionError("Offline installation")):
68
+ plan = self.plan(extras=["security-audit"])
69
+ self.assertFalse(self.target.exists())
70
+ self.assertIn("security-audit", plan["extras"])
71
+ self.assertTrue(install.apply_plan(plan))
72
+ manifest = json.loads(self.package_path("UPSTREAM.json").read_text(encoding="utf-8"))
73
+ self.assertEqual(manifest["revision"], "c1c8a8c1471069fb0e188eeaff69b8e8db6564a8")
74
+ self.assertIn(b"Cloudflare", self.package_path("LICENSE").read_bytes())
75
+ for name, record in manifest["files"].items():
76
+ self.assertEqual(install.digest(self.package_path(name).read_bytes()), record["sha256"])
77
+ self.assertFalse(any(self.target.rglob("findings.json")))
78
+ self.assertFalse(any(self.target.rglob("coverage-ledger.json")))
79
+
80
+ def test_client_entry_points_are_manual_and_links_survive_installation(self):
81
+ self.apply(extras=["security-audit"], agents=["codex", "claude"])
82
+ source_entry = self.target / install.SECURITY_AUDIT_SKILL / "SKILL.md"
83
+ native_entry = self.target / ".claude/skills/security-audit/SKILL.md"
84
+ source_text = source_entry.read_text(encoding="utf-8")
85
+ native_text = native_entry.read_text(encoding="utf-8")
86
+ self.assertLess(source_entry.stat().st_size, 2500)
87
+ self.assertEqual(native_text.replace("disable-model-invocation: true\n", "", 1), source_text)
88
+ policy = (self.target / install.SECURITY_AUDIT_SKILL / "agents/openai.yaml").read_text()
89
+ self.assertEqual([line.strip() for line in policy.splitlines()],
90
+ ["policy:", "allow_implicit_invocation: false"])
91
+ self.assertEqual((self.target / ".claude/skills/security-audit/agents/openai.yaml").read_text(), policy)
92
+ self.assertEqual(doctor.link_warnings(self.target), [])
93
+ self.assertEqual(doctor.examine(self.target, root=self.source)["errors"], [])
94
+
95
+ def test_selection_persists_and_presets_keep_the_explicit_choice(self):
96
+ self.apply(extras=["security-audit"], agents=["codex", "claude"])
97
+ self.assertEqual(self.plan()["actions"], {})
98
+ plan = self.apply(preset="minimal")
99
+ self.assertEqual(plan["extras"], ["security-audit"])
100
+ settings = json.loads((self.target / "ai-kit/settings.json").read_text())
101
+ self.assertIs(settings["security_audit"], True)
102
+ self.assertEqual(self.plan()["actions"], {})
103
+
104
+ def test_local_changes_survive_reruns_and_updated_pins_become_conflicts(self):
105
+ self.apply(extras=["security-audit"])
106
+ entry = self.target / install.SECURITY_AUDIT_SKILL / "SKILL.md"
107
+ entry.write_bytes(entry.read_bytes() + b"\nLocal project guidance.\n")
108
+ local = self.package_path("ATTACK-CLASSES.md")
109
+ local.write_bytes(local.read_bytes() + b"\nLocal reviewed adaptation.\n")
110
+ self.assertEqual(self.plan()["conflicts"], {})
111
+ self.assertEqual(self.plan()["actions"], {})
112
+ previous_state = (self.target / "ai-kit/.install-state.json").read_bytes()
113
+ previous_pin = self.package_path("UPSTREAM.json").read_bytes()
114
+ previous_local = local.read_bytes()
115
+ updated = self.package_path("ATTACK-CLASSES.md", source=True).read_bytes() + b"\nUpstream change.\n"
116
+ self.update_fixture_pin("ATTACK-CLASSES.md", updated)
117
+ plan = self.plan()
118
+ relative = install.SECURITY_AUDIT_PACKAGE + "ATTACK-CLASSES.md"
119
+ self.assertIn(relative, plan["conflicts"])
120
+ self.assertFalse(install.apply_plan(plan))
121
+ self.assertEqual(local.read_bytes(), previous_local)
122
+ self.assertEqual(self.package_path("UPSTREAM.json").read_bytes(), previous_pin)
123
+ self.assertEqual((self.target / "ai-kit/.install-state.json").read_bytes(), previous_state)
124
+ self.assertEqual((self.target / plan["candidates"][relative]["path"]).read_bytes(), updated)
125
+
126
+ def test_unchanged_package_follows_a_reviewed_pin_update(self):
127
+ self.apply(extras=["security-audit"])
128
+ old = self.package_path("ATTACK-CLASSES.md").read_bytes()
129
+ updated = old + b"\nReviewed upstream change.\n"
130
+ self.update_fixture_pin("ATTACK-CLASSES.md", updated)
131
+ self.apply()
132
+ self.assertEqual(self.package_path("ATTACK-CLASSES.md").read_bytes(), updated)
133
+ self.assertEqual(json.loads(self.package_path("UPSTREAM.json").read_text())["revision"], "f" * 40)
134
+ self.assertTrue(any(path.read_bytes() == old
135
+ for path in (self.target / "ai-kit/.upstream-cache/backups").rglob("ATTACK-CLASSES.md")))
136
+ self.assertEqual(self.plan()["actions"], {})
137
+
138
+ def test_existing_untracked_entry_is_a_conflict_and_is_not_overwritten(self):
139
+ entry = self.target / install.SECURITY_AUDIT_SKILL / "SKILL.md"
140
+ entry.parent.mkdir(parents=True)
141
+ entry.write_bytes(b"Existing project skill.\n")
142
+ plan = self.plan(extras=["security-audit"])
143
+ self.assertIn(install.SECURITY_AUDIT_SKILL + "SKILL.md", plan["conflicts"])
144
+ self.assertFalse(install.apply_plan(plan))
145
+ self.assertEqual(entry.read_bytes(), b"Existing project skill.\n")
146
+ self.assertFalse(self.package_path("UPSTREAM.json").exists())
147
+
148
+ def test_deselecting_stops_management_and_preserves_existing_files(self):
149
+ self.apply(extras=["security-audit"])
150
+ settings_path = self.target / "ai-kit/settings.json"
151
+ settings = json.loads(settings_path.read_text())
152
+ settings["security_audit"] = False
153
+ settings_path.write_text(json.dumps(settings) + "\n", encoding="utf-8")
154
+ previous = self.package_path("SKILL.md").read_bytes()
155
+ plan = self.apply()
156
+ self.assertNotIn("security-audit", plan["extras"])
157
+ self.assertEqual(self.package_path("SKILL.md").read_bytes(), previous)
158
+ self.assertTrue(any("Previously managed path retained" in warning
159
+ and "security-audit" in warning for warning in plan["warnings"]))
160
+ self.assertEqual(doctor.link_warnings(self.target), [])
161
+
162
+ def test_native_retirement_requires_review_and_keeps_baseline(self):
163
+ self.apply(extras=["security-audit"], agents=["codex", "claude"])
164
+ state = (self.target / "ai-kit/.install-state.json").read_bytes()
165
+ plan = self.plan(agents=["codex"])
166
+ self.assertIn(".claude/skills/security-audit/SKILL.md", plan["adapter_conflicts"])
167
+ self.assertIn(".claude/skills/security-audit/agents/openai.yaml", plan["adapter_conflicts"])
168
+ self.assertFalse(install.apply_plan(plan))
169
+ self.assertEqual((self.target / "ai-kit/.install-state.json").read_bytes(), state)
170
+
171
+ def test_corrupted_upstream_is_refused_before_any_target_write(self):
172
+ self.package_path("SKILL.md", source=True).write_bytes(b"Changed upstream instructions.\n")
173
+ with self.assertRaisesRegex(ValueError, "integrity mismatch"):
174
+ self.plan(extras=["security-audit"])
175
+ self.assertFalse(self.target.exists())
176
+ self.assertNotIn("security-audit", self.plan()["extras"])
177
+
178
+ def test_unlisted_upstream_files_and_unsafe_manifest_names_are_refused(self):
179
+ extra = self.package_path("extra.cjs", source=True)
180
+ extra.write_bytes(b"Unreviewed extra file.\n")
181
+ with self.assertRaisesRegex(ValueError, "differ from the manifest"):
182
+ self.plan(extras=["security-audit"])
183
+ extra.unlink()
184
+ manifest_path = self.source / install.SECURITY_AUDIT_MANIFEST
185
+ manifest = json.loads(manifest_path.read_text())
186
+ for name in ("../../escape", "UPSTREAM.json", "license"):
187
+ with self.subTest(name=name):
188
+ changed = dict(manifest, files=dict(manifest["files"], **{name: manifest["files"]["LICENSE"]}))
189
+ manifest_path.write_text(json.dumps(changed) + "\n", encoding="utf-8")
190
+ with self.assertRaisesRegex(ValueError, "Unsafe.*filename"):
191
+ self.plan(extras=["security-audit"])
192
+ self.assertFalse(self.target.exists())
193
+
194
+ def test_linked_source_and_target_package_paths_are_refused(self):
195
+ original = install.is_link
196
+ source = self.package_path("SKILL.md", source=True)
197
+ with patch.object(install, "is_link", side_effect=lambda path: path == source or original(path)):
198
+ with self.assertRaisesRegex(ValueError, "[Ll]ink"):
199
+ self.plan(extras=["security-audit"])
200
+ target = self.target / install.SECURITY_AUDIT_PACKAGE.rstrip("/")
201
+ with patch.object(install, "is_link", side_effect=lambda path: path == target or original(path)):
202
+ with self.assertRaisesRegex(ValueError, "[Ll]ink"):
203
+ self.plan(extras=["security-audit"])
204
+ self.assertFalse(self.target.exists())
205
+
206
+ def test_invalid_settings_and_manifest_metadata_are_refused(self):
207
+ defaults = json.loads((self.source / "template/ai-kit/settings.json").read_text())
208
+ defaults["security_audit"] = "true"
209
+ with self.assertRaisesRegex(ValueError, "must be a boolean"):
210
+ install.validate_settings(defaults)
211
+ manifest_path = self.source / install.SECURITY_AUDIT_MANIFEST
212
+ original = json.loads(manifest_path.read_text())
213
+ for field, value in (("revision", "main"), ("schema", True), ("license", "unknown")):
214
+ with self.subTest(field=field):
215
+ changed = dict(original, **{field: value})
216
+ manifest_path.write_text(json.dumps(changed) + "\n", encoding="utf-8")
217
+ with self.assertRaises(ValueError):
218
+ self.plan(extras=["security-audit"])
219
+ manifest_path.write_text(json.dumps(original) + "\n", encoding="utf-8")
220
+ original["files"]["LICENSE"]["git_mode"] = []
221
+ manifest_path.write_text(json.dumps(original) + "\n", encoding="utf-8")
222
+ with self.assertRaisesRegex(ValueError, "Invalid.*record"):
223
+ self.plan(extras=["security-audit"])
224
+ self.assertFalse(self.target.exists())
225
+
226
+ def test_cli_flag_previews_the_opt_in_package(self):
227
+ output = io.StringIO()
228
+ with contextlib.redirect_stdout(output):
229
+ self.assertEqual(install.main([str(self.target), "--with-security-audit"]), 0)
230
+ report = json.loads(output.getvalue())
231
+ self.assertEqual(report["extras"], ["security-audit"])
232
+ self.assertIn(install.SECURITY_AUDIT_PACKAGE + "LICENSE", report["writes"])
233
+ self.assertFalse(self.target.exists())
234
+
235
+
236
+ if __name__ == "__main__":
237
+ unittest.main()
@@ -0,0 +1,245 @@
1
+ """Optional Strix references install offline without starting the active runtime."""
2
+ from __future__ import annotations
3
+
4
+ import contextlib
5
+ import hashlib
6
+ import io
7
+ import json
8
+ from pathlib import Path
9
+ import shutil
10
+ import sys
11
+ import tempfile
12
+ import unittest
13
+ from unittest.mock import patch
14
+
15
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
16
+ import doctor
17
+ import install
18
+
19
+ PACKAGE = "ai-kit/vendor/strix/"
20
+ ENTRY = ".agents/skills/strix/"
21
+ REFERENCE = "penetration-testing-with-strix/SKILL.md"
22
+
23
+
24
+ class StrixTests(unittest.TestCase):
25
+ def setUp(self):
26
+ self.temp = tempfile.TemporaryDirectory(prefix="ai-kit-strix-")
27
+ self.addCleanup(self.temp.cleanup)
28
+ base = Path(self.temp.name).resolve()
29
+ self.source = base / "reference"
30
+ self.target = base / "project with spaces"
31
+ shutil.copytree(install.ROOT, self.source, ignore=shutil.ignore_patterns("__pycache__", ".git"))
32
+
33
+ def plan(self, **options):
34
+ return install.build_plan(self.target, root=self.source, **options)
35
+
36
+ def apply(self, **options):
37
+ plan = self.plan(**options)
38
+ self.assertFalse(install.needs_review(plan), plan)
39
+ self.assertTrue(install.apply_plan(plan))
40
+ return plan
41
+
42
+ def package(self, filename, *, source=False):
43
+ root = self.source / "template" if source else self.target
44
+ return root / PACKAGE / filename
45
+
46
+ def manifest(self):
47
+ return json.loads((self.source / "integrations/strix.json").read_text(encoding="utf-8"))
48
+
49
+ def write_manifest(self, value):
50
+ (self.source / "integrations/strix.json").write_text(json.dumps(value) + "\n", encoding="utf-8")
51
+
52
+ def update_pin(self, data):
53
+ self.package(REFERENCE, source=True).write_bytes(data)
54
+ manifest = self.manifest()
55
+ manifest["revision"] = "f" * 40
56
+ manifest["files"][REFERENCE].update(
57
+ size=len(data), sha256=install.digest(data),
58
+ git_blob=hashlib.sha1(b"blob " + str(len(data)).encode() + b"\0" + data,
59
+ usedforsecurity=False).hexdigest())
60
+ self.write_manifest(manifest)
61
+
62
+ def test_default_and_every_preset_omit_runtime_skill_and_payload(self):
63
+ for preset in (None, *install.PRESETS):
64
+ with self.subTest(preset=preset):
65
+ plan = self.plan(preset=preset, agents=["codex", "claude"])
66
+ self.assertNotIn("strix", plan["extras"])
67
+ self.assertFalse(any("/strix/" in path for path in plan["actions"]))
68
+ self.assertNotIn(b"../.agents/skills/strix/SKILL.md", plan["actions"]["ai-kit/SKILLS.md"]["data"])
69
+ self.apply()
70
+ self.assertFalse((self.target / ENTRY).exists())
71
+ self.assertFalse((self.target / PACKAGE).exists())
72
+ self.assertEqual(doctor.link_warnings(self.target), [])
73
+
74
+ def test_install_is_offline_process_free_and_preserves_the_complete_pin(self):
75
+ with patch("subprocess.Popen", side_effect=AssertionError("Installation must start no runtime")), \
76
+ patch("socket.socket", side_effect=AssertionError("Installation must stay offline")):
77
+ plan = self.plan(extras=["strix"], agents=["codex", "claude"])
78
+ self.assertFalse(self.target.exists())
79
+ self.assertTrue(install.apply_plan(plan))
80
+ pin = json.loads(self.package("UPSTREAM.json").read_text())
81
+ self.assertEqual(pin, self.manifest())
82
+ self.assertEqual(pin["revision"], "f7a32ae62d94872631bc8415073868d02a6ffdaf")
83
+ self.assertEqual(len(pin["files"]), 10)
84
+ self.assertEqual(pin["license"], "Apache-2.0")
85
+ for filename, record in pin["files"].items():
86
+ self.assertEqual(install.digest(self.package(filename).read_bytes()), record["sha256"])
87
+ source_entry = self.target / ENTRY / "SKILL.md"
88
+ native_entry = self.target / ".claude/skills/strix/SKILL.md"
89
+ self.assertLess(source_entry.stat().st_size, 2500)
90
+ self.assertEqual(native_entry.read_text().replace("disable-model-invocation: true\n", "", 1),
91
+ source_entry.read_text())
92
+ policy = (self.target / ENTRY / "agents/openai.yaml").read_text()
93
+ self.assertEqual([line.strip() for line in policy.splitlines()],
94
+ ["policy:", "allow_implicit_invocation: false"])
95
+ self.assertEqual((native_entry.parent / "agents/openai.yaml").read_text(), policy)
96
+ self.assertFalse(any((self.target / ".agents/skills").glob("*-with-strix")))
97
+ self.assertFalse(any((self.target / ".claude/skills").glob("*-with-strix")))
98
+ self.assertFalse((self.target / "strix_runs").exists())
99
+ self.assertEqual(doctor.link_warnings(self.target), [])
100
+ self.assertEqual(doctor.examine(self.target, root=self.source)["errors"], [])
101
+
102
+ def test_choices_are_independent_and_persist_outside_presets(self):
103
+ for name, other in (("strix", "security-audit"), ("security-audit", "strix")):
104
+ with self.subTest(name=name):
105
+ plan = self.plan(extras=[name])
106
+ self.assertFalse(any("/" + other + "/" in path for path in plan["actions"]))
107
+ self.apply(extras=["strix", "security-audit"])
108
+ self.assertEqual(self.plan()["actions"], {})
109
+ self.apply(preset="minimal")
110
+ self.assertEqual(self.plan()["extras"], ["security-audit", "strix"])
111
+ settings = json.loads((self.target / "ai-kit/settings.json").read_text())
112
+ self.assertIs(settings["strix"], True)
113
+ self.assertIs(settings["security_audit"], True)
114
+ self.assertEqual(self.plan()["actions"], {})
115
+
116
+ def test_nested_local_edits_survive_and_new_pins_preserve_conflicts(self):
117
+ self.apply(extras=["strix"])
118
+ entry = self.target / ENTRY / "SKILL.md"
119
+ entry.write_bytes(entry.read_bytes() + b"\nLocal guidance.\n")
120
+ local = self.package(REFERENCE)
121
+ local.write_bytes(local.read_bytes() + b"\nReviewed project adaptation.\n")
122
+ self.assertEqual(self.plan()["actions"], {})
123
+ self.assertEqual(self.plan()["conflicts"], {})
124
+ old_state = (self.target / "ai-kit/.install-state.json").read_bytes()
125
+ old_pin, old_local = self.package("UPSTREAM.json").read_bytes(), local.read_bytes()
126
+ new = self.package(REFERENCE, source=True).read_bytes() + b"\nReviewed upstream change.\n"
127
+ self.update_pin(new)
128
+ plan = self.plan()
129
+ relative = PACKAGE + REFERENCE
130
+ self.assertIn(relative, plan["conflicts"])
131
+ self.assertFalse(install.apply_plan(plan))
132
+ self.assertEqual(local.read_bytes(), old_local)
133
+ self.assertEqual(self.package("UPSTREAM.json").read_bytes(), old_pin)
134
+ self.assertEqual((self.target / "ai-kit/.install-state.json").read_bytes(), old_state)
135
+ self.assertEqual((self.target / plan["candidates"][relative]["path"]).read_bytes(), new)
136
+
137
+ def test_unchanged_nested_reference_updates_with_backup(self):
138
+ self.apply(extras=["strix"])
139
+ old = self.package(REFERENCE).read_bytes()
140
+ new = old + b"\nReviewed upstream change.\n"
141
+ self.update_pin(new)
142
+ self.apply()
143
+ self.assertEqual(self.package(REFERENCE).read_bytes(), new)
144
+ self.assertEqual(json.loads(self.package("UPSTREAM.json").read_text())["revision"], "f" * 40)
145
+ self.assertTrue(any(p.read_bytes() == old for p in
146
+ (self.target / "ai-kit/.upstream-cache/backups").rglob("SKILL.md")))
147
+ self.assertEqual(self.plan()["actions"], {})
148
+
149
+ def test_existing_entry_conflicts_before_any_payload_is_written(self):
150
+ entry = self.target / ENTRY / "SKILL.md"
151
+ entry.parent.mkdir(parents=True)
152
+ entry.write_bytes(b"Existing project skill.\n")
153
+ plan = self.plan(extras=["strix"])
154
+ self.assertIn(ENTRY + "SKILL.md", plan["conflicts"])
155
+ self.assertFalse(install.apply_plan(plan))
156
+ self.assertEqual(entry.read_bytes(), b"Existing project skill.\n")
157
+ self.assertFalse(self.package("UPSTREAM.json").exists())
158
+
159
+ def test_deselection_retains_files_and_native_retirement_still_requires_review(self):
160
+ self.apply(extras=["strix"], agents=["codex", "claude"])
161
+ plan = self.plan(agents=["codex"])
162
+ self.assertIn(".claude/skills/strix/SKILL.md", plan["adapter_conflicts"])
163
+ self.assertFalse(install.apply_plan(plan))
164
+ settings_path = self.target / "ai-kit/settings.json"
165
+ settings = json.loads(settings_path.read_text())
166
+ settings["strix"] = False
167
+ settings_path.write_text(json.dumps(settings) + "\n", encoding="utf-8")
168
+ old = self.package(REFERENCE).read_bytes()
169
+ plan = self.apply()
170
+ self.assertNotIn("strix", plan["extras"])
171
+ self.assertEqual(self.package(REFERENCE).read_bytes(), old)
172
+ self.assertTrue(any("Previously managed path retained" in warning and "/strix/" in warning
173
+ for warning in plan["warnings"]))
174
+ self.assertEqual(doctor.link_warnings(self.target), [])
175
+
176
+ def test_invalid_metadata_and_corrupt_bytes_are_refused_without_writes(self):
177
+ original = self.manifest()
178
+ for field, value in (("schema", True), ("revision", "main"), ("repository", "https://example.com"),
179
+ ("license", "MIT"), ("source_directory", "strix")):
180
+ with self.subTest(field=field):
181
+ self.write_manifest(dict(original, **{field: value}))
182
+ with self.assertRaisesRegex(ValueError, "upstream identity"):
183
+ self.plan(extras=["strix"])
184
+ self.write_manifest(original)
185
+ self.package(REFERENCE, source=True).write_bytes(b"Corrupt reference.\n")
186
+ with self.assertRaisesRegex(ValueError, "integrity mismatch"):
187
+ self.plan(extras=["strix"])
188
+ self.assertNotIn("strix", self.plan()["extras"])
189
+ defaults = json.loads((self.source / "template/ai-kit/settings.json").read_text())
190
+ defaults["strix"] = "true"
191
+ with self.assertRaisesRegex(ValueError, "must be a boolean"):
192
+ install.validate_settings(defaults)
193
+ self.assertFalse(self.target.exists())
194
+
195
+ def test_nested_traversal_reserved_names_and_directory_aliases_are_refused(self):
196
+ original = self.manifest()
197
+ for filename in ("../escape", "extra/../../escape", "extra\\SKILL.md", "C:/escape", "UPSTREAM.json",
198
+ "CON.md", "folder./file.md", "Penetration-testing-with-strix/extra.md"):
199
+ with self.subTest(filename=filename):
200
+ changed = dict(original, files=dict(original["files"], **{filename: original["files"]["LICENSE"]}))
201
+ self.write_manifest(changed)
202
+ with self.assertRaisesRegex(ValueError, "Unsafe.*filename"):
203
+ self.plan(extras=["strix"])
204
+ self.assertFalse(self.target.exists())
205
+
206
+ def test_unlisted_nested_files_and_missing_skills_are_refused(self):
207
+ directory = self.package("unlisted", source=True)
208
+ directory.mkdir()
209
+ with self.assertRaisesRegex(ValueError, "differ from the manifest"):
210
+ self.plan(extras=["strix"])
211
+ directory.rmdir()
212
+ extra = self.package("penetration-testing-with-strix/extra.md", source=True)
213
+ extra.write_bytes(b"Unlisted reference.\n")
214
+ with self.assertRaisesRegex(ValueError, "differ from the manifest"):
215
+ self.plan(extras=["strix"])
216
+ extra.unlink()
217
+ self.package("api-security-testing/SKILL.md", source=True).unlink()
218
+ with self.assertRaisesRegex(ValueError, "differ from the manifest"):
219
+ self.plan(extras=["strix"])
220
+ self.assertFalse(self.target.exists())
221
+
222
+ def test_linked_source_directory_and_target_ancestor_are_refused(self):
223
+ original = install.is_link
224
+ paths = (self.package(REFERENCE, source=True).parent, self.package(REFERENCE).parent,
225
+ self.source / "integrations/strix.json")
226
+ for linked in paths:
227
+ with self.subTest(linked=linked), patch.object(
228
+ install, "is_link", side_effect=lambda path: path == linked or original(path)):
229
+ with self.assertRaisesRegex(ValueError, "[Ll]ink"):
230
+ self.plan(extras=["strix"])
231
+ self.assertFalse(self.target.exists())
232
+
233
+ def test_cli_flag_selects_strix_without_selecting_cloudflare_or_scanning(self):
234
+ output = io.StringIO()
235
+ with contextlib.redirect_stdout(output):
236
+ self.assertEqual(install.main([str(self.target), "--with-strix"]), 0)
237
+ report = json.loads(output.getvalue())
238
+ self.assertEqual(report["extras"], ["strix"])
239
+ self.assertIn(PACKAGE + "LICENSE", report["writes"])
240
+ self.assertFalse(any("/security-audit/" in path for path in report["writes"]))
241
+ self.assertFalse(self.target.exists())
242
+
243
+
244
+ if __name__ == "__main__":
245
+ unittest.main()