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.
- ai_kit_python-1.4.2.dist-info/METADATA +132 -0
- ai_kit_python-1.4.2.dist-info/RECORD +278 -0
- ai_kit_python-1.4.2.dist-info/WHEEL +5 -0
- ai_kit_python-1.4.2.dist-info/entry_points.txt +2 -0
- ai_kit_python-1.4.2.dist-info/licenses/LICENSE +21 -0
- ai_kit_python-1.4.2.dist-info/licenses/template/ai-kit/vendor/security-audit/LICENSE +21 -0
- ai_kit_python-1.4.2.dist-info/licenses/template/ai-kit/vendor/strix/LICENSE +201 -0
- ai_kit_python-1.4.2.dist-info/top_level.txt +1 -0
- aikit/.agents/skills/go-work/SKILL.md +8 -0
- aikit/.agents/skills/laravel-work/SKILL.md +8 -0
- aikit/.agents/skills/moodle-work/SKILL.md +8 -0
- aikit/.agents/skills/project-continuity/SKILL.md +34 -0
- aikit/.agents/skills/python-work/SKILL.md +8 -0
- aikit/.dockerignore +90 -0
- aikit/.gitattributes +3 -0
- aikit/.github/workflows/check-kit.yml +70 -0
- aikit/.github/workflows/publish-pypi.yml +108 -0
- aikit/.gitignore +60 -0
- aikit/BOOTSTRAP_PROMPT.md +18 -0
- aikit/CHANGELOG.md +85 -0
- aikit/CLAUDE.md +3 -0
- aikit/CONTRIBUTING.md +46 -0
- aikit/Dockerfile +44 -0
- aikit/KIMI.md +3 -0
- aikit/LICENSE +21 -0
- aikit/MANIFEST.in +22 -0
- aikit/MANUS.md +3 -0
- aikit/Makefile +70 -0
- aikit/README.md +1161 -0
- aikit/SECURITY.md +8 -0
- aikit/SKILL.md +105 -0
- aikit/VERSION +1 -0
- aikit/__init__.py +1 -0
- aikit/ai-kit/ADAPTERS.md +5 -0
- aikit/ai-kit/CONTAINERS.md +5 -0
- aikit/ai-kit/ENGINEERING.md +5 -0
- aikit/ai-kit/INTENT.md +5 -0
- aikit/ai-kit/MODELS.md +5 -0
- aikit/ai-kit/SKILLS.md +5 -0
- aikit/ai-kit/stacks/GO.md +5 -0
- aikit/ai-kit/stacks/LARAVEL.md +5 -0
- aikit/ai-kit/stacks/MOODLE.md +5 -0
- aikit/ai-kit/stacks/PYTHON.md +5 -0
- aikit/aikit_cli.py +59 -0
- aikit/deploy/k8s/install-preview-job.yaml +56 -0
- aikit/docker/build-requirements.txt +4 -0
- aikit/docker/check_version.py +29 -0
- aikit/docker/pypi-requirements.txt +3 -0
- aikit/docs/ADAPTER_PATH_FIX.md +23 -0
- aikit/docs/BENCHMARK.md +106 -0
- aikit/docs/CONTEXT7.md +33 -0
- aikit/docs/DECISIONS.md +22 -0
- aikit/docs/DEVELOPMENT_PLAN.md +16 -0
- aikit/docs/DIALOGUE_MEMORY.md +70 -0
- aikit/docs/DOCKER.md +74 -0
- aikit/docs/DOCKER_HUB_OVERVIEW.md +311 -0
- aikit/docs/EVALUATION.md +30 -0
- aikit/docs/IMPLEMENTATION.md +134 -0
- aikit/docs/MANUS_REVIEW.md +46 -0
- aikit/docs/PYPI.md +87 -0
- aikit/docs/PYPI_DESCRIPTION.md +103 -0
- aikit/docs/SECURITY_AUDIT.md +26 -0
- aikit/docs/STRIX.md +28 -0
- aikit/docs/V0_5_0.md +27 -0
- aikit/docs/V0_6_4.md +25 -0
- aikit/docs/V1_4_0.md +73 -0
- aikit/docs/V1_4_1.md +55 -0
- aikit/docs/V1_4_2.md +33 -0
- aikit/docs/WEB_DESIGN.md +72 -0
- aikit/docs/WINDOWS_CI_FIX.md +39 -0
- aikit/docs/adr/0000-template.md +24 -0
- aikit/docs/adr/0001-adaptive-model-routing.md +30 -0
- aikit/docs/adr/0002-template-separation.md +24 -0
- aikit/docs/adr/0003-agent-registry-and-router.md +24 -0
- aikit/docs/adr/0004-onboarding-and-project-facts.md +24 -0
- aikit/docs/adr/0005-client-settings-ownership.md +30 -0
- aikit/docs/adr/0006-routing-evidence-and-local-measurements.md +26 -0
- aikit/docs/adr/0007-generated-client-configuration-and-presets.md +34 -0
- aikit/docs/adr/0008-unified-cli-and-fact-drift.md +20 -0
- aikit/docs/adr/0009-provider-review-doctor-fix-and-ci-self-install.md +20 -0
- aikit/docs/adr/0010-metrics-analysis-and-gemini-verification.md +20 -0
- aikit/docs/adr/0011-evidence-lifecycle-and-evaluation.md +20 -0
- aikit/docs/adr/0012-measurement-led-routing.md +31 -0
- aikit/docs/adr/0013-docker-cli-image.md +24 -0
- aikit/docs/adr/0014-local-dialogue-memory.md +22 -0
- aikit/docs/history/original-bundle-checks.md +11 -0
- aikit/docs/history/v0.1-changelog.txt +37 -0
- aikit/docs/history/v0.10-changelog.md +12 -0
- aikit/docs/history/v0.2-changelog.md +35 -0
- aikit/docs/history/v0.3-changelog.md +25 -0
- aikit/docs/history/v0.4-changelog.md +31 -0
- aikit/docs/history/v0.5-changelog.md +14 -0
- aikit/docs/history/v0.6.4-changelog.md +12 -0
- aikit/docs/history/v0.6.5-v0.9-changelog.md +35 -0
- aikit/docs/history/v1.1.0-changelog.md +12 -0
- aikit/docs/history/v1.2.0-changelog.md +11 -0
- aikit/docs/history/verification-before-measurements.md +53 -0
- aikit/integrations/CONVENTIONS.md +3 -0
- aikit/integrations/README.md +7 -0
- aikit/integrations/agents.json +136 -0
- aikit/integrations/ai-kit-check.yml +66 -0
- aikit/integrations/ai-kit-cline.md +4 -0
- aikit/integrations/ai-kit-roo.md +4 -0
- aikit/integrations/ai-kit-windsurf.md +4 -0
- aikit/integrations/ai-kit.mdc +6 -0
- aikit/integrations/claude-settings.data-ask.json +23 -0
- aikit/integrations/claude-settings.git-ask.json +10 -0
- aikit/integrations/copilot-instructions.md +3 -0
- aikit/integrations/security-audit.json +135 -0
- aikit/integrations/strix.json +69 -0
- aikit/pyproject.toml +60 -0
- aikit/scripts/__init__.py +1 -0
- aikit/scripts/adapters.py +184 -0
- aikit/scripts/adaptive.py +167 -0
- aikit/scripts/adr.py +61 -0
- aikit/scripts/benchmark.py +543 -0
- aikit/scripts/changelog.py +55 -0
- aikit/scripts/check_distribution.py +178 -0
- aikit/scripts/check_kit.py +406 -0
- aikit/scripts/context.py +184 -0
- aikit/scripts/docker_test_usage.py +55 -0
- aikit/scripts/doctor.py +313 -0
- aikit/scripts/evaluate.py +125 -0
- aikit/scripts/install.py +1584 -0
- aikit/scripts/learning.py +260 -0
- aikit/scripts/memory.py +344 -0
- aikit/scripts/memory_import.py +177 -0
- aikit/scripts/memory_store.py +125 -0
- aikit/scripts/metrics.py +351 -0
- aikit/scripts/router.py +399 -0
- aikit/template/.agents/hooks/session-start.md +3 -0
- aikit/template/.agents/hooks/session-start.sh +21 -0
- aikit/template/.agents/skills/ai-kit-bootstrap/SKILL.md +14 -0
- aikit/template/.agents/skills/ai-kit-review/SKILL.md +11 -0
- aikit/template/.agents/skills/ai-kit-sync-context/SKILL.md +12 -0
- aikit/template/.agents/skills/context7-docs/SKILL.md +11 -0
- aikit/template/.agents/skills/design-extract/SKILL.md +9 -0
- aikit/template/.agents/skills/dotnet-work/SKILL.md +9 -0
- aikit/template/.agents/skills/filament-work/SKILL.md +9 -0
- aikit/template/.agents/skills/frontend-work/SKILL.md +9 -0
- aikit/template/.agents/skills/go-work/SKILL.md +9 -0
- aikit/template/.agents/skills/infra-work/SKILL.md +9 -0
- aikit/template/.agents/skills/java-work/SKILL.md +9 -0
- aikit/template/.agents/skills/laravel-blade-ui/SKILL.md +9 -0
- aikit/template/.agents/skills/laravel-work/SKILL.md +10 -0
- aikit/template/.agents/skills/library-work/SKILL.md +9 -0
- aikit/template/.agents/skills/moodle-work/SKILL.md +9 -0
- aikit/template/.agents/skills/php-work/SKILL.md +9 -0
- aikit/template/.agents/skills/project-continuity/SKILL.md +21 -0
- aikit/template/.agents/skills/python-work/SKILL.md +9 -0
- aikit/template/.agents/skills/rust-work/SKILL.md +9 -0
- aikit/template/.agents/skills/security-audit/SKILL.md +16 -0
- aikit/template/.agents/skills/security-audit/agents/openai.yaml +2 -0
- aikit/template/.agents/skills/strix/SKILL.md +16 -0
- aikit/template/.agents/skills/strix/agents/openai.yaml +2 -0
- aikit/template/.agents/skills/ui-review/SKILL.md +9 -0
- aikit/template/.agents/skills/web-design/SKILL.md +9 -0
- aikit/template/AGENTS.md +16 -0
- aikit/template/CHANGELOG.md +6 -0
- aikit/template/CLAUDE.md +5 -0
- aikit/template/GEMINI.md +4 -0
- aikit/template/KIMI.md +4 -0
- aikit/template/MANUS.md +4 -0
- aikit/template/PROJECT_CONTEXT.md +35 -0
- aikit/template/README.md +6 -0
- aikit/template/WIKI.md +37 -0
- aikit/template/ai-kit/ADAPTERS.md +100 -0
- aikit/template/ai-kit/BOOTSTRAP.md +15 -0
- aikit/template/ai-kit/CONTAINERS.md +63 -0
- aikit/template/ai-kit/CONTEXT7.md +96 -0
- aikit/template/ai-kit/CORE.md +23 -0
- aikit/template/ai-kit/ENGINEERING.md +34 -0
- aikit/template/ai-kit/MCP.md +17 -0
- aikit/template/ai-kit/METRICS.md +83 -0
- aikit/template/ai-kit/SECURITY-AUDIT.md +21 -0
- aikit/template/ai-kit/SECURITY.md +34 -0
- aikit/template/ai-kit/SKILLS.md +37 -0
- aikit/template/ai-kit/STRIX.md +44 -0
- aikit/template/ai-kit/UPSTREAM.md +38 -0
- aikit/template/ai-kit/WEB-DESIGN.md +37 -0
- aikit/template/ai-kit/profiles/OWNER.md +19 -0
- aikit/template/ai-kit/project.json +14 -0
- aikit/template/ai-kit/router/POLICY.md +79 -0
- aikit/template/ai-kit/router/providers/anthropic.json +44 -0
- aikit/template/ai-kit/router/providers/anthropic.md +14 -0
- aikit/template/ai-kit/router/providers/gemini.json +13 -0
- aikit/template/ai-kit/router/providers/gemini.md +10 -0
- aikit/template/ai-kit/router/providers/kimi.json +26 -0
- aikit/template/ai-kit/router/providers/kimi.md +13 -0
- aikit/template/ai-kit/router/providers/local.json +18 -0
- aikit/template/ai-kit/router/providers/local.md +10 -0
- aikit/template/ai-kit/router/providers/openai.json +48 -0
- aikit/template/ai-kit/router/providers/openai.md +14 -0
- aikit/template/ai-kit/router/routing.json +11 -0
- aikit/template/ai-kit/router/selection.json +14 -0
- aikit/template/ai-kit/settings.json +11 -0
- aikit/template/ai-kit/stacks/DOTNET.md +13 -0
- aikit/template/ai-kit/stacks/FILAMENT.md +18 -0
- aikit/template/ai-kit/stacks/FRONTEND.md +9 -0
- aikit/template/ai-kit/stacks/GO.md +19 -0
- aikit/template/ai-kit/stacks/INFRA.md +12 -0
- aikit/template/ai-kit/stacks/JAVA.md +13 -0
- aikit/template/ai-kit/stacks/LARAVEL.md +15 -0
- aikit/template/ai-kit/stacks/LIBRARY.md +9 -0
- aikit/template/ai-kit/stacks/MOODLE.md +12 -0
- aikit/template/ai-kit/stacks/PHP.md +11 -0
- aikit/template/ai-kit/stacks/PYTHON.md +11 -0
- aikit/template/ai-kit/stacks/RUST.md +18 -0
- aikit/template/ai-kit/vendor/security-audit/AI-AND-LLM.md +83 -0
- aikit/template/ai-kit/vendor/security-audit/ATTACK-CLASSES.md +130 -0
- aikit/template/ai-kit/vendor/security-audit/CLIENT-SIDE.md +83 -0
- aikit/template/ai-kit/vendor/security-audit/CLOUD-AND-DEPLOYMENT.md +86 -0
- aikit/template/ai-kit/vendor/security-audit/DATA-ISOLATION-AND-LIFECYCLE.md +84 -0
- aikit/template/ai-kit/vendor/security-audit/DESKTOP-MOBILE-AND-LOCAL-IPC.md +89 -0
- aikit/template/ai-kit/vendor/security-audit/HUNTING.md +251 -0
- aikit/template/ai-kit/vendor/security-audit/LICENSE +21 -0
- aikit/template/ai-kit/vendor/security-audit/MEMORY-SAFETY-AND-BINARY.md +101 -0
- aikit/template/ai-kit/vendor/security-audit/PROTOCOLS-RPC-AND-MESSAGING.md +81 -0
- aikit/template/ai-kit/vendor/security-audit/RECONNAISSANCE.md +156 -0
- aikit/template/ai-kit/vendor/security-audit/RESOURCE-EXHAUSTION-AND-AVAILABILITY.md +78 -0
- aikit/template/ai-kit/vendor/security-audit/SKILL.md +192 -0
- aikit/template/ai-kit/vendor/security-audit/SUPPLY-CHAIN-AND-RELEASE.md +73 -0
- aikit/template/ai-kit/vendor/security-audit/VALIDATION-AND-REPORTING.md +186 -0
- aikit/template/ai-kit/vendor/security-audit/WEB-PROTOCOL-AND-AUTH.md +105 -0
- aikit/template/ai-kit/vendor/security-audit/report-schema.json +461 -0
- aikit/template/ai-kit/vendor/security-audit/validate-coverage-ledger.cjs +872 -0
- aikit/template/ai-kit/vendor/security-audit/validate-coverage-ledger.test.cjs +740 -0
- aikit/template/ai-kit/vendor/security-audit/validate-findings.cjs +773 -0
- aikit/template/ai-kit/vendor/security-audit/validate-findings.test.cjs +652 -0
- aikit/template/ai-kit/vendor/strix/LICENSE +201 -0
- aikit/template/ai-kit/vendor/strix/api-security-testing/SKILL.md +61 -0
- aikit/template/ai-kit/vendor/strix/application-security-testing/SKILL.md +66 -0
- aikit/template/ai-kit/vendor/strix/ci-security-scanning-with-strix/SKILL.md +149 -0
- aikit/template/ai-kit/vendor/strix/find-security-vulnerabilities-in-code/SKILL.md +62 -0
- aikit/template/ai-kit/vendor/strix/fix-security-vulnerabilities-with-strix/SKILL.md +77 -0
- aikit/template/ai-kit/vendor/strix/managed-pentesting-with-strix/SKILL.md +321 -0
- aikit/template/ai-kit/vendor/strix/owasp-top-10-testing/SKILL.md +64 -0
- aikit/template/ai-kit/vendor/strix/penetration-testing-with-strix/SKILL.md +162 -0
- aikit/template/ai-kit/vendor/strix/web-app-penetration-testing/SKILL.md +54 -0
- aikit/template/ai-kit/web-design/LARAVEL.md +51 -0
- aikit/template/ai-kit/web-design/LIBRARIES.md +39 -0
- aikit/template/ai-kit/web-design/REVIEW.md +37 -0
- aikit/template/ai-kit/web-design/TOOLS.md +43 -0
- aikit/template/docs/DECISIONS.md +7 -0
- aikit/template/docs/adr/0000-template.md +20 -0
- aikit/template/docs/design/ACCEPTANCE.md +24 -0
- aikit/template/docs/design/BRIEF.md +26 -0
- aikit/template/docs/design/SYSTEM.md +30 -0
- aikit/templates/agent-secrets.ignore +19 -0
- aikit/templates/gitignore.private +160 -0
- aikit/templates/gitignore.team +139 -0
- aikit/tests/build_distribution.py +73 -0
- aikit/tests/docker_smoke.py +172 -0
- aikit/tests/fixtures/create-junction.ps1 +18 -0
- aikit/tests/fixtures/golden/claude-settings-local.json +58 -0
- aikit/tests/test_adapters.py +105 -0
- aikit/tests/test_aikit.py +79 -0
- aikit/tests/test_benchmark.py +241 -0
- aikit/tests/test_check_kit.py +203 -0
- aikit/tests/test_context.py +76 -0
- aikit/tests/test_context7.py +153 -0
- aikit/tests/test_context_selection.py +70 -0
- aikit/tests/test_distribution.py +181 -0
- aikit/tests/test_docker_image.py +63 -0
- aikit/tests/test_docker_test_usage.py +54 -0
- aikit/tests/test_doctor.py +148 -0
- aikit/tests/test_evaluate.py +66 -0
- aikit/tests/test_install.py +939 -0
- aikit/tests/test_learning.py +247 -0
- aikit/tests/test_memory.py +371 -0
- aikit/tests/test_metrics.py +209 -0
- aikit/tests/test_native.py +342 -0
- aikit/tests/test_onboarding.py +301 -0
- aikit/tests/test_router.py +336 -0
- aikit/tests/test_security_audit.py +237 -0
- aikit/tests/test_strix.py +245 -0
- aikit/tests/test_web_design.py +219 -0
- aikit/tests/wheel_smoke.py +104 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""Synthetic workers exercise isolation, execution evidence, semantic review and private export."""
|
|
2
|
+
import copy
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import tempfile
|
|
10
|
+
import unittest
|
|
11
|
+
from unittest import mock
|
|
12
|
+
|
|
13
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
|
|
14
|
+
import benchmark
|
|
15
|
+
import install
|
|
16
|
+
import metrics
|
|
17
|
+
|
|
18
|
+
WORKER = '''import json, os, pathlib, shutil, subprocess, sys
|
|
19
|
+
r = json.load(sys.stdin)
|
|
20
|
+
w = pathlib.Path.cwd()
|
|
21
|
+
assert (w / "value.txt").read_text() == "initial\\n"
|
|
22
|
+
assert "DATABASE_URL" not in os.environ
|
|
23
|
+
assert not (w / ".git").exists()
|
|
24
|
+
git = shutil.which("git")
|
|
25
|
+
if git:
|
|
26
|
+
assert subprocess.run([git, "rev-parse", "--show-toplevel"], capture_output=True).returncode != 0
|
|
27
|
+
assert (w / "ai-kit/CORE.md").exists() == (r["arm"] != "baseline")
|
|
28
|
+
(w / "value.txt").write_text("changed\\n")
|
|
29
|
+
a = dict(r["requested"], disposition="succeeded", input_tokens=100, output_tokens=10,
|
|
30
|
+
cached_input_tokens=0, reasoning_tokens=0,
|
|
31
|
+
cost={"amount": "1.00" if r["arm"] == "baseline" else "0.50", "currency": "USD", "source": "provider-usage"})
|
|
32
|
+
print(json.dumps({"schema": 1, "attempts": [a], "rework": 0, "escalations": 0}))
|
|
33
|
+
'''
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class BenchmarkTests(unittest.TestCase):
|
|
37
|
+
def setUp(self):
|
|
38
|
+
self.temp = tempfile.TemporaryDirectory()
|
|
39
|
+
self.addCleanup(self.temp.cleanup)
|
|
40
|
+
self.base = Path(self.temp.name).resolve()
|
|
41
|
+
self.root = self.base / "project"
|
|
42
|
+
self.root.mkdir()
|
|
43
|
+
self.git = shutil.which("git")
|
|
44
|
+
if not self.git:
|
|
45
|
+
self.skipTest("Git unavailable for immutable fixture snapshots")
|
|
46
|
+
self.git_run("init", "-q")
|
|
47
|
+
(self.root / "value.txt").write_text("initial\n", encoding="utf-8")
|
|
48
|
+
(self.root / "pyproject.toml").write_text('[project]\nname = "fixture"\nrequires-python = ">=3.10"\n', encoding="utf-8")
|
|
49
|
+
self.commit()
|
|
50
|
+
self.revision = self.git_run("rev-parse", "HEAD").strip()
|
|
51
|
+
# Current installed config is outside the fixture's pre-kit Git revision.
|
|
52
|
+
shutil.copytree(install.ROOT / "template/ai-kit", self.root / "ai-kit")
|
|
53
|
+
self.worker = self.base / "worker.py"
|
|
54
|
+
self.worker.write_text(WORKER, encoding="utf-8")
|
|
55
|
+
(self.base / "prompt.txt").write_text("Implement the requested fixture change.", encoding="utf-8")
|
|
56
|
+
(self.base / "rubric.txt").write_text("Manually verify the intended change and absence of unrelated edits.", encoding="utf-8")
|
|
57
|
+
self.pack = {"schema": 1, "experiment": "fixture-only", "revision": self.revision, "repeats": 2,
|
|
58
|
+
"fixed": {"provider": "fixture", "model": "fixture-model", "effort": None},
|
|
59
|
+
"worker": {"protocol": "aikit-worker-v1", "argv": [sys.executable, str(self.worker)]},
|
|
60
|
+
"checks": [[sys.executable, "-c", "from pathlib import Path; assert Path('value.txt').read_text() == 'changed\\n'"]],
|
|
61
|
+
"timeout_seconds": 15, "tasks": [{"task_id": "change", "prompt_file": "prompt.txt",
|
|
62
|
+
"rubric_file": "rubric.txt", "path": ".", "stack": "PYTHON", "operation": "implement",
|
|
63
|
+
"risks": [], "capabilities": []}]}
|
|
64
|
+
self.pack_file = self.base / "pack.json"
|
|
65
|
+
|
|
66
|
+
def git_run(self, *args):
|
|
67
|
+
return subprocess.run([self.git, "-C", str(self.root), *args], capture_output=True, text=True, check=True).stdout
|
|
68
|
+
|
|
69
|
+
def commit(self):
|
|
70
|
+
self.git_run("add", "--", "value.txt", "pyproject.toml")
|
|
71
|
+
self.git_run("-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-qm", "Fixture")
|
|
72
|
+
|
|
73
|
+
def prepare(self):
|
|
74
|
+
self.pack_file.write_text(json.dumps(self.pack), encoding="utf-8")
|
|
75
|
+
return benchmark.prepare(self.root, self.pack_file, git=self.git)
|
|
76
|
+
|
|
77
|
+
def reviews(self, run, *, outcome="correct"):
|
|
78
|
+
value = benchmark.report(run)
|
|
79
|
+
path = self.base / "reviews.json"
|
|
80
|
+
path.write_text(json.dumps({"schema": 1, "reviews": [
|
|
81
|
+
{"task_id": r["record"]["task_id"], "arm": r["record"]["arm"], "review": "manual", "outcome": outcome}
|
|
82
|
+
for r in value["results"]]}), encoding="utf-8")
|
|
83
|
+
return path
|
|
84
|
+
|
|
85
|
+
def test_isolated_repeats_fixed_models_semantic_gate_reports_and_export(self):
|
|
86
|
+
plan = self.prepare()
|
|
87
|
+
self.assertFalse((self.root / plan["relative"]).exists())
|
|
88
|
+
self.assertEqual(len(plan["units"]), 6)
|
|
89
|
+
self.assertEqual(plan["units"][0]["route"], plan["units"][1]["route"])
|
|
90
|
+
(self.root / "value.txt").write_text("uncommitted owner change\n", encoding="utf-8")
|
|
91
|
+
with mock.patch.dict(os.environ, {"DATABASE_URL": "fixture-sensitive-value"}):
|
|
92
|
+
run = benchmark.execute(plan)
|
|
93
|
+
value = benchmark.report(run)
|
|
94
|
+
self.assertTrue(value["complete"])
|
|
95
|
+
self.assertEqual(value["observed_units"], 6)
|
|
96
|
+
self.assertTrue(all(r["record"]["checks"] == "passed" for r in value["results"]))
|
|
97
|
+
self.assertTrue(all(r["record"]["outcome"] == "incomplete" for r in value["results"]))
|
|
98
|
+
self.assertTrue(all(c["savings_percent"] is None for c in value["comparisons"]))
|
|
99
|
+
self.assertEqual((self.root / "value.txt").read_text(), "uncommitted owner change\n")
|
|
100
|
+
with self.assertRaises(ValueError):
|
|
101
|
+
benchmark.export_records(run, self.root)
|
|
102
|
+
reviews = self.reviews(run)
|
|
103
|
+
benchmark.review(run, reviews)
|
|
104
|
+
self.assertEqual(benchmark.report(run)["results"][0]["record"]["outcome"], "incomplete")
|
|
105
|
+
benchmark.review(run, reviews, apply=True)
|
|
106
|
+
report = benchmark.report(run)
|
|
107
|
+
self.assertEqual(report["comparisons"][0]["savings_percent"], "50.0")
|
|
108
|
+
self.assertTrue((run / "results.json").is_file())
|
|
109
|
+
self.assertIn("50.0", (run / "report.md").read_text())
|
|
110
|
+
self.assertEqual(benchmark.export_records(run, self.root, apply=True)["new_records"], 6)
|
|
111
|
+
self.assertEqual(benchmark.export_records(run, self.root, apply=True)["new_records"], 0)
|
|
112
|
+
self.assertEqual(len(metrics.read_records(self.root)), 6)
|
|
113
|
+
again = self.prepare()
|
|
114
|
+
self.assertEqual(benchmark.execute(again), run)
|
|
115
|
+
self.assertEqual(benchmark.report(run)["observed_units"], 6)
|
|
116
|
+
self.pack["repeats"] = 3
|
|
117
|
+
self.pack_file.write_text(json.dumps(self.pack), encoding="utf-8")
|
|
118
|
+
with self.assertRaisesRegex(ValueError, "different plan"):
|
|
119
|
+
benchmark.execute(benchmark.prepare(self.root, self.pack_file, git=self.git))
|
|
120
|
+
|
|
121
|
+
def test_failed_checks_cannot_be_reviewed_as_correct(self):
|
|
122
|
+
self.pack["repeats"] = 1
|
|
123
|
+
self.pack["checks"] = [[sys.executable, "-c", "raise SystemExit(1)"]]
|
|
124
|
+
run = benchmark.execute(self.prepare())
|
|
125
|
+
with self.assertRaises(ValueError):
|
|
126
|
+
benchmark.review(run, self.reviews(run), apply=True)
|
|
127
|
+
benchmark.review(run, self.reviews(run, outcome="incorrect"), apply=True)
|
|
128
|
+
self.assertTrue(all(c["savings_percent"] is None for c in benchmark.report(run)["comparisons"]))
|
|
129
|
+
|
|
130
|
+
def test_unknown_telemetry_and_unconfirmed_execution_block_savings(self):
|
|
131
|
+
self.worker.write_text('import json; print(json.dumps({"schema": 1, "attempts": [{"disposition": "inconclusive"}], "rework": 0, "escalations": 0}))', encoding="utf-8")
|
|
132
|
+
self.pack["repeats"] = 1
|
|
133
|
+
self.pack["checks"] = []
|
|
134
|
+
run = benchmark.execute(self.prepare())
|
|
135
|
+
benchmark.review(run, self.reviews(run), apply=True)
|
|
136
|
+
value = benchmark.report(run)
|
|
137
|
+
self.assertFalse(value["results"][0]["observed_matches_request"])
|
|
138
|
+
self.assertIsNone(value["summary"]["groups"][0]["cost_per_correct_result"])
|
|
139
|
+
self.assertTrue(all(c["savings_percent"] is None for c in value["comparisons"]))
|
|
140
|
+
|
|
141
|
+
def test_invalid_packs_prompt_traversal_secret_revision_and_links_refused(self):
|
|
142
|
+
self.pack["tasks"][0]["prompt_file"] = "../outside.txt"
|
|
143
|
+
with self.assertRaises(ValueError):
|
|
144
|
+
self.prepare()
|
|
145
|
+
self.pack["tasks"][0]["prompt_file"] = "prompt.txt"
|
|
146
|
+
original = install.is_link
|
|
147
|
+
with mock.patch.object(install, "is_link", side_effect=lambda p: p.name == "benchmarks" or original(p)):
|
|
148
|
+
with self.assertRaises(ValueError):
|
|
149
|
+
self.prepare()
|
|
150
|
+
(self.root / ".env").write_text("FIXTURE_SECRET=not-real\n", encoding="utf-8")
|
|
151
|
+
self.git_run("add", "-f", ".env")
|
|
152
|
+
self.git_run("-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-qm", "Secret fixture")
|
|
153
|
+
self.pack["revision"] = self.git_run("rev-parse", "HEAD").strip()
|
|
154
|
+
with self.assertRaisesRegex(ValueError, "secret/runtime"):
|
|
155
|
+
self.prepare()
|
|
156
|
+
|
|
157
|
+
def test_worker_timeout_produces_unknown_attempt_not_a_free_success(self):
|
|
158
|
+
self.pack["repeats"] = 1
|
|
159
|
+
self.pack["checks"] = []
|
|
160
|
+
plan = self.prepare()
|
|
161
|
+
with mock.patch.object(benchmark, "invoke", return_value=(None, "")):
|
|
162
|
+
run = benchmark.execute(plan)
|
|
163
|
+
value = benchmark.report(run)
|
|
164
|
+
self.assertTrue(all(r["worker_exit_code"] is None for r in value["results"]))
|
|
165
|
+
self.assertTrue(all(r["record"]["rework"] is None and r["record"]["escalations"] is None for r in value["results"]))
|
|
166
|
+
self.assertTrue(all(g["rework"] is None and g["escalations"] is None for g in value["summary"]["groups"]))
|
|
167
|
+
with self.assertRaises(ValueError):
|
|
168
|
+
benchmark.review(run, self.reviews(run), apply=True)
|
|
169
|
+
|
|
170
|
+
def test_command_validation_secret_exceptions_and_shell_refusal(self):
|
|
171
|
+
bad = copy.deepcopy(self.pack)
|
|
172
|
+
bad["worker"]["argv"] = "python worker.py"
|
|
173
|
+
with self.assertRaises(ValueError):
|
|
174
|
+
benchmark.validate_pack(bad, self.root)
|
|
175
|
+
self.assertFalse(benchmark.secret_path(".env.example", [".env.*", "!.env.example"]))
|
|
176
|
+
self.assertTrue(benchmark.secret_path("secrets/test.txt", ["/secrets/"]))
|
|
177
|
+
routing = self.root / "ai-kit/router/routing.json"
|
|
178
|
+
config = json.loads(routing.read_text())
|
|
179
|
+
config["project_pin"] = {"provider": "local", "model": "fixture-local", "effort": None}
|
|
180
|
+
routing.write_text(json.dumps(config), encoding="utf-8")
|
|
181
|
+
plan = self.prepare()
|
|
182
|
+
adaptive_unit = next(unit for unit in plan["units"] if unit["arm"] == "kit-adaptive")
|
|
183
|
+
self.assertEqual(adaptive_unit["route"], config["project_pin"])
|
|
184
|
+
duplicate = copy.deepcopy(self.pack["tasks"][0])
|
|
185
|
+
duplicate["task_id"] = "renamed-same-case"
|
|
186
|
+
self.pack["tasks"].append(duplicate)
|
|
187
|
+
with self.assertRaisesRegex(ValueError, "Duplicate task material"):
|
|
188
|
+
self.prepare()
|
|
189
|
+
listing = b"100644 blob " + b"a" * 40 + b"\tCase.txt\x00" + b"100644 blob " + b"b" * 40 + b"\tcase.txt\x00"
|
|
190
|
+
with mock.patch.object(benchmark, "git_command", side_effect=[str(self.root).encode() + b"\n", self.revision.encode() + b"\n", listing]):
|
|
191
|
+
with self.assertRaisesRegex(ValueError, "Case-colliding"):
|
|
192
|
+
benchmark.snapshot_files(self.root, self.revision)
|
|
193
|
+
|
|
194
|
+
def test_task_path_is_probed_against_the_project(self):
|
|
195
|
+
bad = copy.deepcopy(self.pack)
|
|
196
|
+
bad["tasks"][0]["path"] = "../outside"
|
|
197
|
+
with self.assertRaises(ValueError):
|
|
198
|
+
benchmark.validate_pack(bad, self.root)
|
|
199
|
+
benchmark.validate_pack(copy.deepcopy(self.pack), self.root)
|
|
200
|
+
|
|
201
|
+
def test_kit_arm_agents_and_preset_are_configurable_and_validated(self):
|
|
202
|
+
self.pack["repeats"] = 1
|
|
203
|
+
self.pack["kit_agents"] = ["not-a-client"]
|
|
204
|
+
with self.assertRaises(ValueError):
|
|
205
|
+
self.prepare()
|
|
206
|
+
self.pack["kit_agents"] = ["claude"]
|
|
207
|
+
self.pack["kit_preset"] = "unknown-preset"
|
|
208
|
+
with self.assertRaises(ValueError):
|
|
209
|
+
self.prepare()
|
|
210
|
+
self.pack["kit_preset"] = "strict"
|
|
211
|
+
run = benchmark.execute(self.prepare())
|
|
212
|
+
workspaces = sorted(run.glob("unit-*/workspace"))
|
|
213
|
+
kit_workspaces = [w for w in workspaces if (w / "ai-kit/CORE.md").exists()]
|
|
214
|
+
baseline_workspaces = [w for w in workspaces if not (w / "ai-kit/CORE.md").exists()]
|
|
215
|
+
self.assertEqual(len(kit_workspaces), 2)
|
|
216
|
+
self.assertEqual(len(baseline_workspaces), 1)
|
|
217
|
+
self.assertTrue(all((w / "CLAUDE.md").is_file() for w in kit_workspaces))
|
|
218
|
+
self.assertFalse((baseline_workspaces[0] / "CLAUDE.md").exists())
|
|
219
|
+
|
|
220
|
+
def test_unit_failures_are_recorded_and_runs_resume_without_retry(self):
|
|
221
|
+
self.pack["repeats"] = 1
|
|
222
|
+
# The fixture evidences PYTHON only, so both kit arms fail before any paid worker call.
|
|
223
|
+
self.pack["tasks"][0]["stack"] = "GO"
|
|
224
|
+
plan = self.prepare()
|
|
225
|
+
run = benchmark.execute(plan)
|
|
226
|
+
value = benchmark.report(run)
|
|
227
|
+
self.assertTrue(value["complete"])
|
|
228
|
+
self.assertEqual(value["observed_units"], 3)
|
|
229
|
+
failed = [r for r in value["results"] if not r["telemetry_valid"]]
|
|
230
|
+
self.assertEqual(len(failed), 2)
|
|
231
|
+
self.assertTrue(all(r["record"]["arm"] in {"kit-fixed", "kit-adaptive"} for r in failed))
|
|
232
|
+
self.assertTrue(all("error" in r for r in failed))
|
|
233
|
+
baseline = next(r for r in value["results"] if r["record"]["arm"] == "baseline")
|
|
234
|
+
self.assertTrue(baseline["telemetry_valid"])
|
|
235
|
+
self.assertTrue(all(c["savings_percent"] is None for c in value["comparisons"]))
|
|
236
|
+
self.assertEqual(benchmark.execute(plan), run)
|
|
237
|
+
self.assertEqual(benchmark.report(run)["observed_units"], 3)
|
|
238
|
+
self.pack["repeats"] = 2
|
|
239
|
+
self.pack_file.write_text(json.dumps(self.pack), encoding="utf-8")
|
|
240
|
+
with self.assertRaisesRegex(ValueError, "different plan"):
|
|
241
|
+
benchmark.execute(benchmark.prepare(self.root, self.pack_file, git=self.git))
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Catch concrete metadata, link, template, and checkout regressions."""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import json
|
|
4
|
+
import shutil
|
|
5
|
+
import sys
|
|
6
|
+
import tempfile
|
|
7
|
+
import unittest
|
|
8
|
+
from unittest.mock import patch
|
|
9
|
+
|
|
10
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
|
|
11
|
+
import check_kit
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CheckerTests(unittest.TestCase):
|
|
15
|
+
def setUp(self):
|
|
16
|
+
self.temp = tempfile.TemporaryDirectory(prefix="ai-kit-check-")
|
|
17
|
+
self.addCleanup(self.temp.cleanup)
|
|
18
|
+
self.root = Path(self.temp.name) / "reference"
|
|
19
|
+
shutil.copytree(check_kit.ROOT, self.root, ignore=shutil.ignore_patterns("__pycache__", ".git"))
|
|
20
|
+
|
|
21
|
+
def check(self):
|
|
22
|
+
# Real Git visibility is exercised by the top-level checker/CI invocation.
|
|
23
|
+
with patch.object(check_kit, "visibility", return_value=[]):
|
|
24
|
+
return check_kit.check(self.root, git="not-invoked")[0]
|
|
25
|
+
|
|
26
|
+
def test_clean_distribution(self):
|
|
27
|
+
self.assertEqual(self.check(), [])
|
|
28
|
+
|
|
29
|
+
def test_distribution_does_not_require_local_maintainer_files(self):
|
|
30
|
+
for name in check_kit.LOCAL_MAINTAINER_FILES:
|
|
31
|
+
(self.root / name).unlink(missing_ok=True)
|
|
32
|
+
self.assertEqual(self.check(), [])
|
|
33
|
+
|
|
34
|
+
def test_private_root_files_are_excluded_without_hiding_project_templates(self):
|
|
35
|
+
for name in check_kit.LOCAL_MAINTAINER_FILES:
|
|
36
|
+
(self.root / name).write_bytes(b"\xff\x00LOCAL_MAINTAINER_DATA")
|
|
37
|
+
self.assertEqual(self.check(), [])
|
|
38
|
+
sources = {p.relative_to(self.root).as_posix() for p in check_kit.source_files(self.root)}
|
|
39
|
+
self.assertTrue(check_kit.LOCAL_MAINTAINER_FILES.isdisjoint(sources))
|
|
40
|
+
for name in check_kit.LOCAL_MAINTAINER_FILES:
|
|
41
|
+
self.assertIn("template/" + name, sources)
|
|
42
|
+
|
|
43
|
+
def test_public_document_cannot_depend_on_optional_private_file(self):
|
|
44
|
+
with (self.root / "README.md").open("a", encoding="utf-8") as stream:
|
|
45
|
+
stream.write("\n[Private map](WIKI.md)\n")
|
|
46
|
+
self.assertTrue(any("public link depends on local maintainer file" in error for error in self.check()))
|
|
47
|
+
|
|
48
|
+
def test_modified_pinned_package_is_reported(self):
|
|
49
|
+
path = self.root / "template/ai-kit/vendor/security-audit/SKILL.md"
|
|
50
|
+
path.write_bytes(path.read_bytes() + b"\nUnexpected edit.\n")
|
|
51
|
+
self.assertTrue(any("integrity mismatch" in error for error in self.check()))
|
|
52
|
+
|
|
53
|
+
def test_upstream_fixture_language_exception_does_not_cover_authored_files(self):
|
|
54
|
+
(self.root / "scripts/unreviewed.txt").write_text("\u043f\u0440\u0438\u0432\u0435\u0442\n", encoding="utf-8")
|
|
55
|
+
self.assertTrue(any("Unexpected Cyrillic" in error for error in self.check()))
|
|
56
|
+
|
|
57
|
+
def test_missing_pinned_manifest_is_reported(self):
|
|
58
|
+
(self.root / "integrations/security-audit.json").unlink()
|
|
59
|
+
self.assertTrue(any("pinned security-audit package" in error for error in self.check()))
|
|
60
|
+
|
|
61
|
+
def test_git_checkout_binary_metadata_is_excluded(self):
|
|
62
|
+
path = self.root / ".git/objects/binary"
|
|
63
|
+
path.parent.mkdir(parents=True)
|
|
64
|
+
path.write_bytes(b"\xff\x00")
|
|
65
|
+
self.assertEqual(self.check(), [])
|
|
66
|
+
|
|
67
|
+
def test_missing_heading_is_reported(self):
|
|
68
|
+
with (self.root / "README.md").open("a", encoding="utf-8") as stream:
|
|
69
|
+
stream.write("\n[Broken](template/PROJECT_CONTEXT.md#nonexistent-heading)\n")
|
|
70
|
+
self.assertTrue(any("missing heading" in error for error in self.check()))
|
|
71
|
+
|
|
72
|
+
def test_user_documentation_has_no_size_quota_while_conditional_guides_keep_theirs(self):
|
|
73
|
+
documents = [self.root / "README.md", self.root / "docs/DOCKER_HUB_OVERVIEW.md"]
|
|
74
|
+
for document in documents:
|
|
75
|
+
document.write_bytes(document.read_bytes() + b"\n" + b"Reader documentation.\n" * 6000)
|
|
76
|
+
guide = self.root / "template/ai-kit/WEB-DESIGN.md"
|
|
77
|
+
guide.write_bytes(guide.read_bytes() + b"\n" + b"Conditional instructions.\n" * 600)
|
|
78
|
+
scoped_overview = self.root / "template/ai-kit/DOCKER_HUB_OVERVIEW.md"
|
|
79
|
+
scoped_overview.write_bytes(b"# Scoped instructions\n\n" + b"Conditional instructions.\n" * 600)
|
|
80
|
+
with patch.object(check_kit, "visibility", return_value=[]):
|
|
81
|
+
errors, warnings = check_kit.check(self.root, git="not-invoked")
|
|
82
|
+
self.assertEqual(errors, [])
|
|
83
|
+
for document in documents:
|
|
84
|
+
self.assertFalse(any(warning.endswith(str(document)) for warning in warnings))
|
|
85
|
+
self.assertTrue(any(warning.endswith(str(guide)) for warning in warnings))
|
|
86
|
+
self.assertTrue(any(warning.endswith(str(scoped_overview)) for warning in warnings))
|
|
87
|
+
|
|
88
|
+
def test_link_cannot_escape_reference(self):
|
|
89
|
+
with (self.root / "README.md").open("a", encoding="utf-8") as stream:
|
|
90
|
+
stream.write("\n[Escape](../../outside.md)\n")
|
|
91
|
+
self.assertTrue(any("escapes reference" in error for error in self.check()))
|
|
92
|
+
|
|
93
|
+
def test_skill_folder_name_mismatch(self):
|
|
94
|
+
path = self.root / "template/.agents/skills/go-work/SKILL.md"
|
|
95
|
+
path.write_text(path.read_text().replace("name: go-work", "name: wrong-name"), encoding="utf-8")
|
|
96
|
+
self.assertTrue(any("name/folder mismatch" in error for error in self.check()))
|
|
97
|
+
|
|
98
|
+
def test_registry_duplicate_is_reported(self):
|
|
99
|
+
path = self.root / "template/ai-kit/SKILLS.md"
|
|
100
|
+
with path.open("a", encoding="utf-8") as stream:
|
|
101
|
+
stream.write("\n[Repeated](../.agents/skills/go-work/SKILL.md)\n")
|
|
102
|
+
self.assertTrue(any("registry differs" in error for error in self.check()))
|
|
103
|
+
|
|
104
|
+
def test_corrupted_strix_reference_is_reported(self):
|
|
105
|
+
path = self.root / "template/ai-kit/vendor/strix/penetration-testing-with-strix/SKILL.md"
|
|
106
|
+
path.write_bytes(path.read_bytes() + b"\nChanged reference.\n")
|
|
107
|
+
self.assertTrue(any("Invalid pinned strix package" in error for error in self.check()))
|
|
108
|
+
|
|
109
|
+
def test_missing_strix_manifest_is_reported(self):
|
|
110
|
+
(self.root / "integrations/strix.json").unlink()
|
|
111
|
+
self.assertTrue(any("Missing: integrations/strix.json" in error for error in self.check()))
|
|
112
|
+
|
|
113
|
+
def test_history_is_not_installable_project_fact(self):
|
|
114
|
+
path = self.root / "template/PROJECT_CONTEXT.md"
|
|
115
|
+
with path.open("a", encoding="utf-8") as stream:
|
|
116
|
+
stream.write("\nKit review date: 2026-10-08.\n")
|
|
117
|
+
self.assertTrue(any("history leaked" in error for error in self.check()))
|
|
118
|
+
|
|
119
|
+
def test_invalid_provider_json_is_failure_not_crash(self):
|
|
120
|
+
path = self.root / "template/ai-kit/router/providers/openai.json"
|
|
121
|
+
path.write_text("{", encoding="utf-8")
|
|
122
|
+
self.assertTrue(any("invalid JSON" in error for error in self.check()))
|
|
123
|
+
|
|
124
|
+
def test_provider_effort_outside_supported_list_is_reported(self):
|
|
125
|
+
path = self.root / "template/ai-kit/router/providers/openai.json"
|
|
126
|
+
config = json.loads(path.read_text(encoding="utf-8"))
|
|
127
|
+
config["roles"]["work"]["effort"] = "ultra"
|
|
128
|
+
path.write_text(json.dumps(config), encoding="utf-8")
|
|
129
|
+
self.assertTrue(any("Unsupported configured effort" in error for error in self.check()))
|
|
130
|
+
|
|
131
|
+
def test_active_model_id_in_markdown_is_reported(self):
|
|
132
|
+
with (self.root / "template/ai-kit/ENGINEERING.md").open("a", encoding="utf-8") as stream:
|
|
133
|
+
stream.write("\nRoute this step to claude-opus-5-5.\n")
|
|
134
|
+
self.assertTrue(any("duplicated outside provider configuration" in error
|
|
135
|
+
for error in self.check()))
|
|
136
|
+
|
|
137
|
+
def test_provider_without_verified_date_is_reported(self):
|
|
138
|
+
path = self.root / "template/ai-kit/router/providers/anthropic.json"
|
|
139
|
+
config = json.loads(path.read_text(encoding="utf-8"))
|
|
140
|
+
del config["verified_documentation_date"]
|
|
141
|
+
path.write_text(json.dumps(config), encoding="utf-8")
|
|
142
|
+
self.assertTrue(any("verified_documentation_date" in error for error in self.check()))
|
|
143
|
+
|
|
144
|
+
def test_source_enumeration_skips_git_but_keeps_hidden_instructions(self):
|
|
145
|
+
measurement = self.root / "ai-kit/.metrics/private-notes.md"
|
|
146
|
+
measurement.parent.mkdir(parents=True)
|
|
147
|
+
measurement.write_bytes(b"Private runtime data\n")
|
|
148
|
+
sources = {p.relative_to(self.root).as_posix() for p in check_kit.source_files(self.root)}
|
|
149
|
+
self.assertIn(".agents/skills/project-continuity/SKILL.md", sources)
|
|
150
|
+
self.assertIn("template/.agents/skills/go-work/SKILL.md", sources)
|
|
151
|
+
self.assertFalse(any(p.startswith(".git/") for p in sources))
|
|
152
|
+
self.assertFalse(any(p.startswith("ai-kit/.metrics/") for p in sources))
|
|
153
|
+
self.assertIn("template/ai-kit/METRICS.md", sources)
|
|
154
|
+
|
|
155
|
+
def test_agent_registry_missing_optional_source_is_reported(self):
|
|
156
|
+
path = self.root / "integrations/agents.json"
|
|
157
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
158
|
+
data["agents"]["aider"]["optional"] = [{"source": "missing.md", "destination": "CONVENTIONS.md"}]
|
|
159
|
+
path.write_text(json.dumps(data) + "\n", encoding="utf-8")
|
|
160
|
+
self.assertTrue(any("optional source missing" in error for error in self.check()))
|
|
161
|
+
|
|
162
|
+
def test_agent_registry_entry_missing_from_template_is_reported(self):
|
|
163
|
+
path = self.root / "integrations/agents.json"
|
|
164
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
165
|
+
data["agents"]["gemini"]["entries"] = ["NOPE.md"]
|
|
166
|
+
path.write_text(json.dumps(data) + "\n", encoding="utf-8")
|
|
167
|
+
self.assertTrue(any("entry missing from template" in error for error in self.check()))
|
|
168
|
+
|
|
169
|
+
def test_agent_registry_invalid_documentation_metadata_is_reported(self):
|
|
170
|
+
path = self.root / "integrations/agents.json"
|
|
171
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
172
|
+
data["agents"]["cursor"]["documentation"]["sources"] = []
|
|
173
|
+
path.write_text(json.dumps(data) + "\n", encoding="utf-8")
|
|
174
|
+
self.assertTrue(any("Invalid agent registry" in error for error in self.check()))
|
|
175
|
+
|
|
176
|
+
def test_agent_registry_missing_documentation_metadata_is_reported(self):
|
|
177
|
+
path = self.root / "integrations/agents.json"
|
|
178
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
179
|
+
del data["agents"]["cursor"]["documentation"]
|
|
180
|
+
path.write_text(json.dumps(data) + "\n", encoding="utf-8")
|
|
181
|
+
self.assertTrue(any("Invalid agent registry" in error for error in self.check()))
|
|
182
|
+
|
|
183
|
+
def test_project_template_requires_an_empty_evidence_snapshot(self):
|
|
184
|
+
path = self.root / "template/ai-kit/project.json"
|
|
185
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
186
|
+
data["evidence"]["modules"] = [{"path": "/", "manifests": {"go.mod": "hash"}}]
|
|
187
|
+
path.write_text(json.dumps(data) + "\n", encoding="utf-8")
|
|
188
|
+
self.assertTrue(any("must start empty" in error for error in self.check()))
|
|
189
|
+
|
|
190
|
+
def test_pending_provider_is_warning_not_error(self):
|
|
191
|
+
path = self.root / "template/ai-kit/router/providers/gemini.json"
|
|
192
|
+
config = json.loads(path.read_text(encoding="utf-8"))
|
|
193
|
+
config.pop("verified_documentation_date", None)
|
|
194
|
+
config["verification_status"] = "pending"
|
|
195
|
+
path.write_text(json.dumps(config) + "\n", encoding="utf-8")
|
|
196
|
+
with patch.object(check_kit, "visibility", return_value=[]):
|
|
197
|
+
errors, warnings = check_kit.check(self.root, git="not-invoked")
|
|
198
|
+
self.assertEqual(errors, [])
|
|
199
|
+
self.assertTrue(any("pending" in warning for warning in warnings))
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
if __name__ == "__main__":
|
|
203
|
+
unittest.main()
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Exercise manifest-evidence snapshots and doctor freshness warnings."""
|
|
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
|
+
|
|
11
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
|
|
12
|
+
import context
|
|
13
|
+
import doctor
|
|
14
|
+
import install
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ContextFreshnessTests(unittest.TestCase):
|
|
18
|
+
def setUp(self):
|
|
19
|
+
self.temp = tempfile.TemporaryDirectory(prefix="ai-kit-context-")
|
|
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 write(self, relative, data):
|
|
27
|
+
path = self.target / relative
|
|
28
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
path.write_text(data, encoding="utf-8")
|
|
30
|
+
|
|
31
|
+
def apply(self):
|
|
32
|
+
plan = install.build_plan(self.target, root=self.source)
|
|
33
|
+
self.assertTrue(install.apply_plan(plan))
|
|
34
|
+
|
|
35
|
+
def test_fresh_install_captures_manifest_evidence(self):
|
|
36
|
+
self.write("go.mod", "module example.test/project\n\ngo 1.23\n")
|
|
37
|
+
self.apply()
|
|
38
|
+
project = json.loads((self.target / "ai-kit/project.json").read_text(encoding="utf-8"))
|
|
39
|
+
self.assertEqual(project["evidence"]["schema"], 1)
|
|
40
|
+
self.assertIn("go.mod", project["evidence"]["modules"][0]["manifests"])
|
|
41
|
+
report = doctor.examine(self.target, root=self.source)
|
|
42
|
+
self.assertFalse(any("evidence" in warning for warning in report["warnings"]))
|
|
43
|
+
|
|
44
|
+
def test_changed_manifest_and_new_module_are_reported(self):
|
|
45
|
+
self.write("go.mod", "module example.test/project\n\ngo 1.23\n")
|
|
46
|
+
self.apply()
|
|
47
|
+
self.write("go.mod", "module example.test/project\n\ngo 1.24\n")
|
|
48
|
+
self.write("api/pyproject.toml", '[project]\nname = "api"\nrequires-python = ">=3.10"\n')
|
|
49
|
+
warnings = doctor.examine(self.target, root=self.source)["warnings"]
|
|
50
|
+
self.assertTrue(any("Manifest evidence changed at /" in warning for warning in warnings))
|
|
51
|
+
self.assertTrue(any("Detected module missing from project facts: /api/" in warning for warning in warnings))
|
|
52
|
+
|
|
53
|
+
def test_changed_detected_command_is_reported(self):
|
|
54
|
+
self.write("package.json", '{"scripts": {"test": "vitest"}}\n')
|
|
55
|
+
self.apply()
|
|
56
|
+
self.write("package.json", '{"scripts": {"lint": "eslint ."}}\n')
|
|
57
|
+
warnings = doctor.examine(self.target, root=self.source)["warnings"]
|
|
58
|
+
self.assertTrue(any("Detected commands changed at /" in warning for warning in warnings))
|
|
59
|
+
|
|
60
|
+
def test_snapshot_preserves_project_facts_and_refreshes_evidence_only_on_apply(self):
|
|
61
|
+
self.write("go.mod", "module example.test/project\n\ngo 1.23\n")
|
|
62
|
+
self.apply()
|
|
63
|
+
self.write("go.mod", "module example.test/project\n\ngo 1.24\n")
|
|
64
|
+
before = json.loads((self.target / "ai-kit/project.json").read_text(encoding="utf-8"))
|
|
65
|
+
plan = context.snapshot_plan(self.target)
|
|
66
|
+
self.assertTrue(plan["changed"])
|
|
67
|
+
self.assertEqual(json.loads(plan["data"])["modules"], before["modules"])
|
|
68
|
+
self.assertTrue(context.apply_snapshot(plan))
|
|
69
|
+
report = doctor.examine(self.target, root=self.source)
|
|
70
|
+
self.assertFalse(any("Manifest evidence changed" in warning for warning in report["warnings"]))
|
|
71
|
+
with contextlib.redirect_stdout(io.StringIO()):
|
|
72
|
+
self.assertEqual(context.main(["snapshot", str(self.target)]), 0)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
unittest.main()
|