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,73 @@
1
+ """Build release archives in an isolated copy with synthetic private-file probes."""
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ from pathlib import Path
6
+ import shutil
7
+ import subprocess
8
+ import sys
9
+ import tempfile
10
+
11
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
12
+ import check_distribution
13
+
14
+ ROOT = Path(__file__).resolve().parents[1]
15
+ PRIVATE_PROBES = ("AGENTS.md", "PROJECT_CONTEXT.md", "WIKI.md", "auth.json",
16
+ "ai-kit/.memory/session/private.json", "ai-kit/.metrics/private.json",
17
+ "ai-kit/.upstream-cache/private.md", "ai-kit/.design/private.md",
18
+ "docs/.private/private.md", "docs/auth.json", "docs/.env.release",
19
+ "template/.env", "docs/private.pem", "docs/id_ed25519")
20
+ MARKER = b"SYNTHETIC_PRIVATE_" + b"DISTRIBUTION_PROBE_MUST_NOT_SHIP"
21
+
22
+
23
+ def prepare(destination: Path) -> list[dict]:
24
+ destination = destination.resolve()
25
+ if destination.is_relative_to(ROOT):
26
+ raise ValueError("Distribution output must be outside the source checkout")
27
+ if list(destination.glob("*.whl")) or list(destination.glob("*.tar.gz")):
28
+ raise ValueError("Choose an output directory without existing distribution archives")
29
+ destination.mkdir(parents=True, exist_ok=True)
30
+ with tempfile.TemporaryDirectory(prefix="aikit-source-", dir=destination.parent) as temporary:
31
+ source = Path(temporary) / "source"
32
+ source.mkdir()
33
+ names = {p.name for pattern in ("*.md", "*.py") for p in ROOT.glob(pattern)} - check_distribution.LOCAL_FILES
34
+ names.update(("VERSION", "LICENSE", "MANIFEST.in", "pyproject.toml", "Makefile", "Dockerfile",
35
+ ".dockerignore", ".gitignore", ".gitattributes"))
36
+ for name in sorted(names):
37
+ shutil.copy2(ROOT / name, source / name)
38
+ ignore = shutil.ignore_patterns(".git", "__pycache__", "*.pyc", "*.egg-info", ".memory", ".metrics",
39
+ ".upstream-cache", ".design", ".private", ".env", ".env.*", "auth.json",
40
+ ".aws", ".codex", ".claude", ".secrets", ".netrc", ".pypirc",
41
+ "*.pem", "*.p12", "*.pfx", "id_rsa*", "id_ed25519*")
42
+ for name in ("scripts", "tests", "docs", "template", "templates", "integrations", "ai-kit", ".agents", ".github", "docker", "deploy"):
43
+ shutil.copytree(ROOT / name, source / name, ignore=ignore)
44
+ for name in PRIVATE_PROBES:
45
+ path = source / name
46
+ path.parent.mkdir(parents=True, exist_ok=True)
47
+ path.write_bytes(MARKER + b"\n")
48
+ subprocess.run([sys.executable, "-m", "build", "--no-isolation", "--outdir", str(destination)],
49
+ cwd=source, check=True)
50
+ version = (ROOT / "VERSION").read_text(encoding="utf-8").strip()
51
+ result = check_distribution.check_archives(destination, version)
52
+ for item in result:
53
+ members = check_distribution._members(destination / item["file"])
54
+ if any(MARKER in data for data in members.values()):
55
+ raise ValueError("Synthetic private marker leaked into an archive")
56
+ return result
57
+
58
+
59
+ def main(argv: list[str] | None = None) -> int:
60
+ parser = argparse.ArgumentParser(description=__doc__)
61
+ parser.add_argument("destination", type=Path)
62
+ args = parser.parse_args(argv)
63
+ try:
64
+ for item in prepare(args.destination):
65
+ print(item["file"] + ": metadata/resources/privacy passed; sha256=" + item["sha256"])
66
+ return 0
67
+ except (OSError, ValueError, subprocess.SubprocessError) as exc:
68
+ print("Distribution build refused: " + str(exc))
69
+ return 1
70
+
71
+
72
+ if __name__ == "__main__":
73
+ raise SystemExit(main())
@@ -0,0 +1,172 @@
1
+ """Exercise a local CLI image with isolated fixtures and no runtime network access."""
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+ import subprocess
9
+ import tempfile
10
+ import time
11
+ import uuid
12
+
13
+
14
+ def docker(arguments: list[str], *, expected: int = 0, timeout: int = 180) -> str:
15
+ result = subprocess.run(["docker", *arguments], capture_output=True, text=True,
16
+ encoding="utf-8", timeout=timeout)
17
+ if result.returncode != expected:
18
+ raise RuntimeError(f"Docker exited {result.returncode}, expected {expected}: "
19
+ + result.stdout + result.stderr)
20
+ return result.stdout
21
+
22
+
23
+ def runtime_arguments(image: str, project: Path | None = None, *, readonly: bool = False,
24
+ entrypoint: str | None = None, create: bool = False,
25
+ name: str | None = None) -> list[str]:
26
+ arguments = ["create" if create else "run"]
27
+ if not create:
28
+ arguments.append("--rm")
29
+ arguments.extend(["--init", "--network", "none", "--read-only", "--cap-drop", "ALL",
30
+ "--security-opt", "no-new-privileges", "--tmpfs", "/tmp:rw,nosuid,nodev,size=256m",
31
+ "--label", "ai-kit.disposable=true"])
32
+ if hasattr(os, "getuid") and os.getuid() != 0:
33
+ arguments.extend(["--user", f"{os.getuid()}:{os.getgid()}"])
34
+ if project is not None:
35
+ source = project.resolve()
36
+ if not source.is_dir():
37
+ raise ValueError("Smoke project must be an existing directory")
38
+ mount = f"type=bind,source={source},target=/workspace" + (",readonly" if readonly else "")
39
+ arguments.extend(["--mount", mount])
40
+ if entrypoint:
41
+ arguments.extend(["--entrypoint", entrypoint])
42
+ if name:
43
+ arguments.extend(["--name", name, "--label", "ai-kit.test-run=" + name])
44
+ if create:
45
+ arguments.append("--interactive")
46
+ return [*arguments, image]
47
+
48
+
49
+ def context_smoke(source: Path, base: Path, builder: str | None) -> None:
50
+ """Use Docker's actual ignore engine on synthetic sensitive files, not user secrets."""
51
+ context = base / "build context"
52
+ output = base / "context export"
53
+ context.mkdir()
54
+ for name in ("Dockerfile", ".dockerignore"):
55
+ (context / name).write_bytes((source / name).read_bytes())
56
+ included = ("VERSION", "scripts/install.py", "template/AGENTS.md", "template/PROJECT_CONTEXT.md",
57
+ "template/WIKI.md", "template/.agents/skills/web-design/SKILL.md",
58
+ "template/ai-kit/vendor/security-audit/LICENSE", "docker/check_version.py")
59
+ excluded = ("AGENTS.md", "PROJECT_CONTEXT.md", "WIKI.md", ".env", ".git/config",
60
+ ".codex/auth.json", ".aws/credentials", "private-helper.py",
61
+ "docs/.private/brief.txt", "docs/auth.json", "template/.env", "ai-kit/.metrics/tasks.jsonl",
62
+ "ai-kit/.design/screenshots/private.png", "ai-kit/.memory/session/imports/private.json",
63
+ "scripts/__pycache__/private.pyc")
64
+ for name in included + excluded:
65
+ path = context / name
66
+ path.parent.mkdir(parents=True, exist_ok=True)
67
+ path.write_bytes(b"Synthetic build-context probe.\n")
68
+ arguments = ["buildx", "build"]
69
+ if builder:
70
+ arguments.extend(["--builder", builder])
71
+ arguments.extend(["--target", "context-audit", "--output", "type=local,dest=" + str(output), str(context)])
72
+ docker(arguments, timeout=300)
73
+ if not all((output / name).is_file() for name in included):
74
+ raise RuntimeError("Docker ignored required kit sources")
75
+ if any((output / name).exists() for name in excluded):
76
+ raise RuntimeError("Docker exposed a synthetic secret or private state in the build context")
77
+ print("Build context: required sources included; synthetic secrets/private state excluded.", flush=True)
78
+
79
+
80
+ def signal_smoke(image: str, project: Path) -> None:
81
+ name = "aikit-cli-signal-" + uuid.uuid4().hex[:12]
82
+ created = False
83
+ try:
84
+ docker([*runtime_arguments(image, project, readonly=True, create=True, name=name),
85
+ "install", "/workspace", "--interactive"])
86
+ created = True
87
+ docker(["start", name])
88
+ deadline = time.monotonic() + 15
89
+ while "AI-KIT interactive installer" not in docker(["logs", name]):
90
+ if time.monotonic() >= deadline:
91
+ raise RuntimeError("Interactive CLI did not reach its input prompt")
92
+ time.sleep(0.2)
93
+ docker(["stop", "--time", "5", name])
94
+ state = json.loads(docker(["inspect", name]))[0]
95
+ if state["State"]["Running"] or state["State"]["ExitCode"] != 143:
96
+ raise RuntimeError("SIGTERM did not terminate the CLI without a forced kill")
97
+ print("SIGTERM reached the foreground CLI; preview terminated with exit 143.", flush=True)
98
+ finally:
99
+ if created:
100
+ record = json.loads(docker(["inspect", name]))[0]
101
+ if record["Config"]["Labels"].get("ai-kit.test-run") != name:
102
+ raise RuntimeError("Refusing cleanup of an unrelated container")
103
+ docker(["rm", "--force", name])
104
+
105
+
106
+ def smoke(image: str, *, source: Path | None = None, builder: str | None = None) -> None:
107
+ inspection = json.loads(docker(["image", "inspect", image]))[0]
108
+ config = inspection["Config"]
109
+ if config["User"] != "10001:10001" or config["Entrypoint"] != ["aikit"]:
110
+ raise RuntimeError("Image must have a non-root user and direct aikit entry point")
111
+ if config.get("ExposedPorts") or config.get("Healthcheck") or config.get("Volumes"):
112
+ raise RuntimeError("A one-shot CLI must not introduce ports, a service probe or anonymous volumes")
113
+ identity_code = ("import json,os,aikit.aikit_cli; "
114
+ "print(json.dumps({'uid':os.getuid(),'version':aikit.aikit_cli.__version__}))")
115
+ identity = json.loads(docker([*runtime_arguments(image, entrypoint="python"), "-c", identity_code]))
116
+ if identity["uid"] == 0 or identity["version"] != config["Labels"]["org.opencontainers.image.version"]:
117
+ raise RuntimeError("Runtime UID/version does not match the image contract")
118
+ docker([*runtime_arguments(image), "--help"])
119
+ docker([*runtime_arguments(image), "route", "implement a feature"])
120
+ docker([*runtime_arguments(image), "check"])
121
+
122
+ with tempfile.TemporaryDirectory(prefix="aikit-container-") as folder:
123
+ base = Path(folder).resolve()
124
+ project = base / "project with spaces"
125
+ project.mkdir(mode=0o777)
126
+ project.chmod(0o777)
127
+ preserved = {"README.md": b"Existing project README.\r\n", ".mcp.json": b'{"mcpServers":{}}\r\n'}
128
+ for name, data in preserved.items():
129
+ (project / name).write_bytes(data)
130
+ preview = json.loads(docker([*runtime_arguments(image, project, readonly=True), "install", "/workspace",
131
+ "--with-web-design"]))
132
+ if not preview["preview"] or (project / "ai-kit").exists():
133
+ raise RuntimeError("Preview changed the mounted project")
134
+ docker([*runtime_arguments(image, project, readonly=True), "install", "/workspace", "--apply"], expected=1)
135
+ if (project / "ai-kit").exists():
136
+ raise RuntimeError("Read-only mount refusal left project writes")
137
+ docker([*runtime_arguments(image, project), "install", "/workspace", "--agent", "codex", "--agent", "claude",
138
+ "--with-web-design", "--with-context7", "--with-security-audit", "--with-strix", "--apply", "--check"])
139
+ if preserved != {name: (project / name).read_bytes() for name in preserved}:
140
+ raise RuntimeError("Installation replaced existing project/client documents")
141
+ report = json.loads(docker([*runtime_arguments(image, project, readonly=True), "doctor", "/workspace"]))
142
+ if report["errors"]:
143
+ raise RuntimeError("Installed project failed doctor: " + str(report["errors"]))
144
+ selection = json.loads(docker([*runtime_arguments(image, project, readonly=True), "context", "select",
145
+ "/workspace", "--workflow", "web-design"]))
146
+ if selection["workflow"] != "web-design":
147
+ raise RuntimeError("Installed design context was not selected")
148
+ repeated = json.loads(docker([*runtime_arguments(image, project), "install", "/workspace", "--apply"]))
149
+ if repeated["writes"]:
150
+ raise RuntimeError("Repeated installation was not stable")
151
+ signal_smoke(image, project)
152
+ if source:
153
+ context_smoke(source, base, builder)
154
+ print("CLI image smoke passed: offline/read-only runtime, preview, refusal, all extras, preservation and rerun.", flush=True)
155
+
156
+
157
+ def main(argv: list[str] | None = None) -> int:
158
+ parser = argparse.ArgumentParser(description=__doc__)
159
+ parser.add_argument("image", help="Already built local image; this command does not publish images")
160
+ parser.add_argument("--context", type=Path, help="Reference checkout for an actual Docker ignore-engine check")
161
+ parser.add_argument("--builder", help="Optional dedicated builder for the context-export check")
162
+ args = parser.parse_args(argv)
163
+ try:
164
+ smoke(args.image, source=args.context, builder=args.builder)
165
+ return 0
166
+ except (OSError, ValueError, RuntimeError, subprocess.SubprocessError) as exc:
167
+ print("Container verification failed: " + str(exc))
168
+ return 1
169
+
170
+
171
+ if __name__ == "__main__":
172
+ raise SystemExit(main())
@@ -0,0 +1,18 @@
1
+ param(
2
+ [Parameter(Mandatory = $true)][string]$LinkPath,
3
+ [Parameter(Mandatory = $true)][string]$TargetPath
4
+ )
5
+
6
+ $ErrorActionPreference = 'Stop'
7
+ try {
8
+ New-Item -ItemType Junction -Path $LinkPath -Target $TargetPath | Out-Null
9
+ } catch {
10
+ $taskError = $_.Exception.GetBaseException()
11
+ if (($taskError -is [System.UnauthorizedAccessException]) -or
12
+ (($taskError -is [System.ComponentModel.Win32Exception]) -and
13
+ ($taskError.NativeErrorCode -in @(5, 1314)))) {
14
+ Write-Error -Message 'Junction creation unavailable: access or privilege denied.' -ErrorAction Continue
15
+ exit 77
16
+ }
17
+ throw
18
+ }
@@ -0,0 +1,58 @@
1
+ {
2
+ "permissions": {
3
+ "ask": [
4
+ "Bash(git commit)",
5
+ "Bash(git commit *)",
6
+ "Bash(git push)",
7
+ "Bash(git push *)",
8
+ "Bash(rm -rf *)",
9
+ "Bash(rm -fr *)",
10
+ "Bash(git reset --hard*)",
11
+ "Bash(git clean *)",
12
+ "Bash(docker system prune*)",
13
+ "Bash(docker volume rm *)",
14
+ "Bash(docker volume prune*)",
15
+ "Bash(php artisan migrate*)",
16
+ "Bash(php artisan db:wipe*)",
17
+ "Bash(php artisan db:seed*)"
18
+ ],
19
+ "allow": [
20
+ "Bash(go test ./... *)",
21
+ "Bash(go vet ./... *)",
22
+ "Bash(go build ./... *)",
23
+ "Bash(php artisan test *)"
24
+ ],
25
+ "deny": [
26
+ "Read(.env)",
27
+ "Read(.env.*)",
28
+ "Read(!.env.example)",
29
+ "Read(!.env.*.example)",
30
+ "Read(!.env.dist)",
31
+ "Read(!.env.*.dist)",
32
+ "Read(.envrc)",
33
+ "Read(.netrc)",
34
+ "Read(*.pem)",
35
+ "Read(*.p12)",
36
+ "Read(*.pfx)",
37
+ "Read(*.private.key)",
38
+ "Read(id_rsa*)",
39
+ "Read(id_ed25519*)",
40
+ "Read(/secrets/**)",
41
+ "Read(/.secrets/**)",
42
+ "Read(auth.json)",
43
+ "Read(/web/storage/*.key)"
44
+ ]
45
+ },
46
+ "hooks": {
47
+ "SessionStart": [
48
+ {
49
+ "hooks": [
50
+ {
51
+ "type": "command",
52
+ "command": "tr -d '\\r' < \"$CLAUDE_PROJECT_DIR/.agents/hooks/session-start.sh\" | sh"
53
+ }
54
+ ]
55
+ }
56
+ ]
57
+ }
58
+ }
@@ -0,0 +1,105 @@
1
+ """Exercise adapter documentation reporting and conservative retirement."""
2
+ import contextlib
3
+ import io
4
+ import json
5
+ from pathlib import Path
6
+ import shutil
7
+ import sys
8
+ import tempfile
9
+ import unittest
10
+ from unittest import mock
11
+
12
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
13
+ import adapters
14
+ import install
15
+
16
+
17
+ class AdapterTests(unittest.TestCase):
18
+ def setUp(self):
19
+ self.temp = tempfile.TemporaryDirectory(prefix="ai-kit-adapters-")
20
+ self.addCleanup(self.temp.cleanup)
21
+ self.base = Path(self.temp.name).resolve()
22
+ self.target = self.base / "project"
23
+ self.source = self.base / "reference"
24
+ shutil.copytree(install.ROOT, self.source, ignore=shutil.ignore_patterns("__pycache__", ".git"))
25
+
26
+ def apply(self, **options):
27
+ plan = install.build_plan(self.target, root=self.source, **options)
28
+ self.assertTrue(install.apply_plan(plan))
29
+ return plan
30
+
31
+ def test_report_exposes_documentation_and_runtime_boundaries(self):
32
+ self.apply(agents=["codex", "cursor"])
33
+ report = adapters.report(root=self.source, project=self.target)
34
+ by_name = {item["client"]: item for item in report["clients"]}
35
+ self.assertEqual(by_name["cursor"]["documentation_status"], "verified")
36
+ self.assertEqual(by_name["cursor"]["activation"], "unverified")
37
+ self.assertTrue(by_name["cursor"]["selected"])
38
+ self.assertIn(".cursor/rules/ai-kit.mdc", by_name["cursor"]["managed_paths"])
39
+ self.assertEqual(by_name["kimi"]["documentation_status"], "unverified")
40
+
41
+ def test_retire_removes_only_unchanged_owned_files_and_updates_state(self):
42
+ self.apply(agents=["codex", "cursor"])
43
+ plan = adapters.retirement_plan(self.target, "cursor", root=self.source)
44
+ paths = {item["path"] for item in plan["removals"]}
45
+ self.assertIn(".cursor/rules/ai-kit.mdc", paths)
46
+ self.assertTrue(any("deselected" in warning for warning in plan["warnings"]))
47
+ self.assertTrue(adapters.apply_retirement(plan))
48
+ self.assertFalse((self.target / ".cursor/rules/ai-kit.mdc").exists())
49
+ backup = self.target / next(item["backup"] for item in plan["removals"]
50
+ if item["path"] == ".cursor/rules/ai-kit.mdc")
51
+ self.assertTrue(backup.is_file())
52
+ state = json.loads((self.target / "ai-kit/.install-state.json").read_text(encoding="utf-8"))
53
+ self.assertEqual(state["agents"], ["codex"])
54
+ rerun = install.build_plan(self.target, root=self.source, agents=["codex"])
55
+ self.assertFalse(rerun["adapter_conflicts"])
56
+
57
+ def test_retire_refuses_modified_adapter_and_preserves_state(self):
58
+ self.apply(agents=["codex", "cursor"])
59
+ path = self.target / ".cursor/rules/ai-kit.mdc"
60
+ path.write_text(path.read_text(encoding="utf-8") + "Local note.\n", encoding="utf-8")
61
+ plan = adapters.retirement_plan(self.target, "cursor", root=self.source)
62
+ self.assertIn(".cursor/rules/ai-kit.mdc", plan["conflicts"])
63
+ self.assertFalse(adapters.apply_retirement(plan))
64
+ self.assertTrue(path.is_file())
65
+ state = json.loads((self.target / "ai-kit/.install-state.json").read_text(encoding="utf-8"))
66
+ self.assertIn("cursor", state["agents"])
67
+
68
+ def test_report_and_retire_preserve_a_file_blocking_adapter_directory(self):
69
+ self.apply(agents=["codex", "cline"])
70
+ directory = self.target / ".clinerules"
71
+ (directory / "ai-kit.md").unlink()
72
+ directory.rmdir()
73
+ directory.write_text("native data\n", encoding="utf-8")
74
+ report = adapters.report(root=self.source, project=self.target)
75
+ cline = next(item for item in report["clients"] if item["client"] == "cline")
76
+ self.assertEqual(cline["blocked_paths"], [".clinerules/ai-kit.md"])
77
+ plan = adapters.retirement_plan(self.target, "cline", root=self.source)
78
+ self.assertEqual(plan["conflicts"], [".clinerules/ai-kit.md"])
79
+ self.assertEqual(directory.read_text(encoding="utf-8"), "native data\n")
80
+
81
+ def test_cli_report_and_retire_preview(self):
82
+ self.apply(agents=["codex", "cursor"])
83
+ with contextlib.redirect_stdout(io.StringIO()) as output:
84
+ self.assertEqual(adapters.main(["report", "--project", str(self.target)]), 0)
85
+ self.assertIn('"client": "cursor"', output.getvalue())
86
+ with contextlib.redirect_stdout(io.StringIO()):
87
+ self.assertEqual(adapters.main(["retire", str(self.target), "--agent", "cursor"]), 0)
88
+ self.assertTrue((self.target / ".cursor/rules/ai-kit.mdc").is_file())
89
+
90
+ def test_audit_obstruction_does_not_hide_a_linked_ancestor(self):
91
+ self.apply(agents=["codex", "cursor"])
92
+ path = self.target / ".cursor/rules/ai-kit.mdc"
93
+ path.unlink()
94
+ path.parent.rmdir()
95
+ path.parent.write_text("native data\n", encoding="utf-8")
96
+ original = install.is_link
97
+ with mock.patch.object(install, "is_link", side_effect=lambda p: p == self.target / ".cursor" or original(p)):
98
+ with self.assertRaisesRegex(ValueError, "linked"):
99
+ adapters.report(root=self.source, project=self.target)
100
+ with self.assertRaisesRegex(ValueError, "linked"):
101
+ adapters.retirement_plan(self.target, "cursor", root=self.source)
102
+
103
+
104
+ if __name__ == "__main__":
105
+ unittest.main()
@@ -0,0 +1,79 @@
1
+ """Exercise the unified aikit CLI dispatcher."""
2
+ import contextlib
3
+ import io
4
+ from pathlib import Path
5
+ import sys
6
+ import unittest
7
+ from unittest import mock
8
+
9
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
10
+ import aikit_cli
11
+
12
+
13
+ class AikitCliTests(unittest.TestCase):
14
+ def test_no_command_prints_help(self):
15
+ with contextlib.redirect_stdout(io.StringIO()):
16
+ self.assertEqual(aikit_cli.main([]), 2)
17
+
18
+ def test_route_dispatches_to_router(self):
19
+ output = io.StringIO()
20
+ with contextlib.redirect_stdout(output):
21
+ self.assertEqual(aikit_cli.main(["route", "implement a feature"]), 0)
22
+ self.assertIn('"level"', output.getvalue())
23
+
24
+ def test_install_help_forwards_arguments(self):
25
+ output = io.StringIO()
26
+ with contextlib.redirect_stdout(output), self.assertRaises(SystemExit) as context:
27
+ aikit_cli.main(["install", "--help"])
28
+ self.assertEqual(context.exception.code, 0)
29
+ self.assertIn("--preset", output.getvalue())
30
+
31
+ def test_benchmark_help_reaches_worker_commands(self):
32
+ output = io.StringIO()
33
+ with contextlib.redirect_stdout(output), self.assertRaises(SystemExit) as context:
34
+ aikit_cli.main(["benchmark", "--help"])
35
+ self.assertEqual(context.exception.code, 0)
36
+ self.assertIn("{run,report,review,export}", output.getvalue())
37
+
38
+ def test_version_reads_from_version_file(self):
39
+ self.assertEqual(aikit_cli.__version__, (Path(__file__).resolve().parents[1] / "VERSION")
40
+ .read_text(encoding="utf-8").strip())
41
+
42
+ def test_version_flag_reports_bundle_without_loading_or_running_a_command(self):
43
+ output = io.StringIO()
44
+ with mock.patch.object(aikit_cli, "_load") as load, contextlib.redirect_stdout(output), self.assertRaises(SystemExit) as context:
45
+ aikit_cli.main(["--version"])
46
+ self.assertEqual(context.exception.code, 0)
47
+ self.assertEqual(output.getvalue().strip(), "aikit " + aikit_cli.__version__)
48
+ load.assert_not_called()
49
+
50
+ def test_memory_help_reaches_offline_commands(self):
51
+ output = io.StringIO()
52
+ with contextlib.redirect_stdout(output), self.assertRaises(SystemExit) as context:
53
+ aikit_cli.main(["memory", "--help"])
54
+ self.assertEqual(context.exception.code, 0)
55
+ self.assertIn("{import,merge,export}", output.getvalue())
56
+
57
+ def test_providers_dispatches_to_router(self):
58
+ output = io.StringIO()
59
+ with contextlib.redirect_stdout(output):
60
+ self.assertEqual(aikit_cli.main(["providers"]), 0)
61
+ self.assertIn('"provider": "gemini"', output.getvalue())
62
+
63
+ def test_adapters_dispatches_to_client_report(self):
64
+ output = io.StringIO()
65
+ with contextlib.redirect_stdout(output):
66
+ self.assertEqual(aikit_cli.main(["adapters", "report"]), 0)
67
+ self.assertIn('"client": "codex"', output.getvalue())
68
+
69
+ def test_loader_prefers_installed_package_module(self):
70
+ import types
71
+ package_module = types.ModuleType("aikit.scripts.install")
72
+ package_module.main = lambda argv=None: 0
73
+ with mock.patch.dict(sys.modules, {"aikit.scripts.install": package_module}):
74
+ self.assertIs(aikit_cli._load("install"), package_module)
75
+ self.assertTrue(callable(aikit_cli._load("install").main))
76
+
77
+
78
+ if __name__ == "__main__":
79
+ unittest.main()