agent-lifecycle-kit 1.50.1__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 (227) hide show
  1. agent_lifecycle/__init__.py +5 -0
  2. agent_lifecycle/__main__.py +5 -0
  3. agent_lifecycle/_version.py +3 -0
  4. agent_lifecycle/adapter_sessions/__init__.py +41 -0
  5. agent_lifecycle/adapter_sessions/contracts.py +133 -0
  6. agent_lifecycle/adapter_sessions/env.py +44 -0
  7. agent_lifecycle/adapter_sessions/launcher.py +101 -0
  8. agent_lifecycle/adapter_sessions/process.py +39 -0
  9. agent_lifecycle/adapter_sessions/redaction.py +30 -0
  10. agent_lifecycle/adapter_sessions/session_store.py +64 -0
  11. agent_lifecycle/adapter_sessions/task_intake.py +450 -0
  12. agent_lifecycle/adapter_sessions/workflow_bridge.py +154 -0
  13. agent_lifecycle/audit/__init__.py +67 -0
  14. agent_lifecycle/audit/bug_forensics.py +94 -0
  15. agent_lifecycle/audit/implementation.py +516 -0
  16. agent_lifecycle/audit/ownership.py +119 -0
  17. agent_lifecycle/audit/proof_integrity.py +814 -0
  18. agent_lifecycle/audit/review_verdict.py +116 -0
  19. agent_lifecycle/changesets/__init__.py +5 -0
  20. agent_lifecycle/changesets/git.py +35 -0
  21. agent_lifecycle/cli/__init__.py +5 -0
  22. agent_lifecycle/cli/adapter.py +360 -0
  23. agent_lifecycle/cli/dispatch.py +1220 -0
  24. agent_lifecycle/cli/followup.py +89 -0
  25. agent_lifecycle/cli/main.py +41 -0
  26. agent_lifecycle/cli/metrics_parser.py +49 -0
  27. agent_lifecycle/cli/parsers.py +618 -0
  28. agent_lifecycle/cli/policy.py +101 -0
  29. agent_lifecycle/cli/progress_hooks.py +117 -0
  30. agent_lifecycle/cli/worktree.py +74 -0
  31. agent_lifecycle/compiler/__init__.py +17 -0
  32. agent_lifecycle/compiler/small_model_packets.py +322 -0
  33. agent_lifecycle/compiler/task_packets.py +229 -0
  34. agent_lifecycle/context/__init__.py +20 -0
  35. agent_lifecycle/context/episode_retrieval.py +30 -0
  36. agent_lifecycle/context/external_memory.py +71 -0
  37. agent_lifecycle/context/profiles.py +104 -0
  38. agent_lifecycle/context/rendering.py +226 -0
  39. agent_lifecycle/contracts/__init__.py +25 -0
  40. agent_lifecycle/contracts/adapter_contract_schemas.py +130 -0
  41. agent_lifecycle/contracts/adapter_event_schemas.py +118 -0
  42. agent_lifecycle/contracts/adapter_session_schemas.py +130 -0
  43. agent_lifecycle/contracts/adapter_task_schemas.py +90 -0
  44. agent_lifecycle/contracts/audit_schemas.py +133 -0
  45. agent_lifecycle/contracts/bug_forensics_schemas.py +400 -0
  46. agent_lifecycle/contracts/canonical.py +55 -0
  47. agent_lifecycle/contracts/compatibility.py +242 -0
  48. agent_lifecycle/contracts/context_model_schemas.py +265 -0
  49. agent_lifecycle/contracts/core_schemas.py +252 -0
  50. agent_lifecycle/contracts/cross_check_schemas.py +121 -0
  51. agent_lifecycle/contracts/errors.py +26 -0
  52. agent_lifecycle/contracts/evidence_import_schemas.py +152 -0
  53. agent_lifecycle/contracts/host_capability_schemas.py +64 -0
  54. agent_lifecycle/contracts/import_dialect_schemas.py +166 -0
  55. agent_lifecycle/contracts/metric_schemas.py +315 -0
  56. agent_lifecycle/contracts/paths.py +31 -0
  57. agent_lifecycle/contracts/plan_contract_schemas.py +148 -0
  58. agent_lifecycle/contracts/policy_schemas.py +350 -0
  59. agent_lifecycle/contracts/progress_bridge_schemas.py +114 -0
  60. agent_lifecycle/contracts/progress_hook_schemas.py +119 -0
  61. agent_lifecycle/contracts/proof_integrity_schemas.py +240 -0
  62. agent_lifecycle/contracts/release_contract_schemas.py +476 -0
  63. agent_lifecycle/contracts/review_mesh_recommendation_schemas.py +64 -0
  64. agent_lifecycle/contracts/review_mesh_schemas.py +222 -0
  65. agent_lifecycle/contracts/review_quality_schemas.py +109 -0
  66. agent_lifecycle/contracts/runner_schemas.py +91 -0
  67. agent_lifecycle/contracts/runner_worktree_schemas.py +389 -0
  68. agent_lifecycle/contracts/sandbox_schemas.py +159 -0
  69. agent_lifecycle/contracts/schema_builders.py +23 -0
  70. agent_lifecycle/contracts/schemas.py +93 -0
  71. agent_lifecycle/contracts/status_goal_schemas.py +469 -0
  72. agent_lifecycle/contracts/task_template_schemas.py +96 -0
  73. agent_lifecycle/contracts/usage_export_schemas.py +68 -0
  74. agent_lifecycle/diagnostics/__init__.py +17 -0
  75. agent_lifecycle/diagnostics/bundles.py +124 -0
  76. agent_lifecycle/diagnostics/readiness.py +733 -0
  77. agent_lifecycle/evidence_index/__init__.py +39 -0
  78. agent_lifecycle/evidence_index/core.py +272 -0
  79. agent_lifecycle/evidence_index/episode_index.py +329 -0
  80. agent_lifecycle/evidence_index/external_context.py +276 -0
  81. agent_lifecycle/followup/__init__.py +23 -0
  82. agent_lifecycle/followup/records.py +404 -0
  83. agent_lifecycle/freeze/__init__.py +5 -0
  84. agent_lifecycle/freeze/locks.py +23 -0
  85. agent_lifecycle/goal/__init__.py +15 -0
  86. agent_lifecycle/goal/records.py +244 -0
  87. agent_lifecycle/goal/view.py +109 -0
  88. agent_lifecycle/host_protocol/__init__.py +65 -0
  89. agent_lifecycle/host_protocol/acp_capability.py +195 -0
  90. agent_lifecycle/host_protocol/capabilities.py +268 -0
  91. agent_lifecycle/host_protocol/contracts.py +214 -0
  92. agent_lifecycle/host_protocol/event_capture.py +390 -0
  93. agent_lifecycle/host_protocol/events.py +196 -0
  94. agent_lifecycle/host_protocol/inspection.py +194 -0
  95. agent_lifecycle/host_protocol/inspection_common.py +310 -0
  96. agent_lifecycle/host_protocol/inspection_cursor.py +164 -0
  97. agent_lifecycle/host_protocol/inspection_gemini_cli.py +163 -0
  98. agent_lifecycle/host_protocol/inspection_hermes.py +161 -0
  99. agent_lifecycle/host_protocol/inspection_kimi_code.py +169 -0
  100. agent_lifecycle/host_protocol/inspection_opencode.py +147 -0
  101. agent_lifecycle/host_protocol/inspection_qwen_code.py +143 -0
  102. agent_lifecycle/host_protocol/receipts.py +58 -0
  103. agent_lifecycle/host_protocol/scaffold.py +329 -0
  104. agent_lifecycle/host_protocol/validation.py +229 -0
  105. agent_lifecycle/imports/__init__.py +76 -0
  106. agent_lifecycle/imports/agentskills_profile.py +54 -0
  107. agent_lifecycle/imports/bmad_profile.py +49 -0
  108. agent_lifecycle/imports/constitution_adr.py +91 -0
  109. agent_lifecycle/imports/dialect_profiles.py +121 -0
  110. agent_lifecycle/imports/external_agent.py +28 -0
  111. agent_lifecycle/imports/external_dialects.py +486 -0
  112. agent_lifecycle/imports/external_workflow.py +28 -0
  113. agent_lifecycle/imports/multi_markdown.py +111 -0
  114. agent_lifecycle/imports/openspec_profile.py +49 -0
  115. agent_lifecycle/imports/planning.py +536 -0
  116. agent_lifecycle/imports/spec_kit_profile.py +49 -0
  117. agent_lifecycle/imports/spec_kitty_profile.py +49 -0
  118. agent_lifecycle/metrics/__init__.py +60 -0
  119. agent_lifecycle/metrics/cost_collection.py +315 -0
  120. agent_lifecycle/metrics/costs.py +203 -0
  121. agent_lifecycle/metrics/outcome_index.py +330 -0
  122. agent_lifecycle/metrics/phase_resources.py +208 -0
  123. agent_lifecycle/metrics/recommendations.py +507 -0
  124. agent_lifecycle/metrics/regression_signals.py +64 -0
  125. agent_lifecycle/metrics/usage_export.py +390 -0
  126. agent_lifecycle/model_routing/__init__.py +19 -0
  127. agent_lifecycle/model_routing/profiles.py +323 -0
  128. agent_lifecycle/model_routing/receipts.py +140 -0
  129. agent_lifecycle/model_routing/resolver.py +566 -0
  130. agent_lifecycle/neutrality/__init__.py +12 -0
  131. agent_lifecycle/neutrality/__main__.py +6 -0
  132. agent_lifecycle/neutrality/authority.py +265 -0
  133. agent_lifecycle/neutrality/canonical.py +28 -0
  134. agent_lifecycle/neutrality/cli.py +294 -0
  135. agent_lifecycle/neutrality/ed25519.py +124 -0
  136. agent_lifecycle/neutrality/errors.py +89 -0
  137. agent_lifecycle/neutrality/gate.py +269 -0
  138. agent_lifecycle/neutrality/paths.py +92 -0
  139. agent_lifecycle/neutrality/policy.py +76 -0
  140. agent_lifecycle/neutrality/receipt.py +135 -0
  141. agent_lifecycle/neutrality/scanner.py +310 -0
  142. agent_lifecycle/neutrality/test_runner.py +116 -0
  143. agent_lifecycle/planning/__init__.py +47 -0
  144. agent_lifecycle/planning/acceptance_markdown.py +159 -0
  145. agent_lifecycle/planning/completeness.py +362 -0
  146. agent_lifecycle/planning/continuity.py +255 -0
  147. agent_lifecycle/planning/templates.py +294 -0
  148. agent_lifecycle/planning/tiering.py +87 -0
  149. agent_lifecycle/planning/validation.py +72 -0
  150. agent_lifecycle/policy/__init__.py +37 -0
  151. agent_lifecycle/policy/adaptive_lifecycle.py +416 -0
  152. agent_lifecycle/policy/apply.py +44 -0
  153. agent_lifecycle/policy/proposals.py +189 -0
  154. agent_lifecycle/policy/quality_floor.py +144 -0
  155. agent_lifecycle/policy/runtime_receipts.py +160 -0
  156. agent_lifecycle/py.typed +1 -0
  157. agent_lifecycle/quality/__init__.py +89 -0
  158. agent_lifecycle/quality/bug_forensics.py +727 -0
  159. agent_lifecycle/quality/bug_forensics_advisor.py +88 -0
  160. agent_lifecycle/quality/bug_forensics_recipes.py +239 -0
  161. agent_lifecycle/quality/cross_check.py +437 -0
  162. agent_lifecycle/quality/failure_classification.py +318 -0
  163. agent_lifecycle/quality/packs.py +265 -0
  164. agent_lifecycle/reporting/__init__.py +37 -0
  165. agent_lifecycle/reporting/change_summary.py +156 -0
  166. agent_lifecycle/reporting/event_feed.py +101 -0
  167. agent_lifecycle/reporting/progress_bridge.py +153 -0
  168. agent_lifecycle/reporting/progress_hooks.py +161 -0
  169. agent_lifecycle/reporting/progress_terminal.py +95 -0
  170. agent_lifecycle/reporting/progress_view.py +284 -0
  171. agent_lifecycle/reporting/status_view.py +125 -0
  172. agent_lifecycle/reporting/usage_export.py +59 -0
  173. agent_lifecycle/review/__init__.py +5 -0
  174. agent_lifecycle/review/validation.py +29 -0
  175. agent_lifecycle/review_mesh/__init__.py +83 -0
  176. agent_lifecycle/review_mesh/assignments.py +160 -0
  177. agent_lifecycle/review_mesh/contracts.py +685 -0
  178. agent_lifecycle/review_mesh/operator_templates.py +320 -0
  179. agent_lifecycle/review_mesh/quorum.py +38 -0
  180. agent_lifecycle/review_mesh/recommendation.py +487 -0
  181. agent_lifecycle/review_mesh/results.py +98 -0
  182. agent_lifecycle/review_mesh/synthesis.py +102 -0
  183. agent_lifecycle/runner/__init__.py +47 -0
  184. agent_lifecycle/runner/attempt_snapshots.py +244 -0
  185. agent_lifecycle/runner/core.py +653 -0
  186. agent_lifecycle/runner/sandbox_receipts.py +628 -0
  187. agent_lifecycle/specification/__init__.py +23 -0
  188. agent_lifecycle/specification/completion_check.py +135 -0
  189. agent_lifecycle/specification/completion_gate.py +501 -0
  190. agent_lifecycle/specification/completion_signal.py +75 -0
  191. agent_lifecycle/specification/validation.py +48 -0
  192. agent_lifecycle/workflow/__init__.py +79 -0
  193. agent_lifecycle/workflow/artifacts.py +74 -0
  194. agent_lifecycle/workflow/budget_decisions.py +370 -0
  195. agent_lifecycle/workflow/budget_policy.py +140 -0
  196. agent_lifecycle/workflow/budget_receipts.py +70 -0
  197. agent_lifecycle/workflow/bug_forensics_gates.py +284 -0
  198. agent_lifecycle/workflow/controller.py +45 -0
  199. agent_lifecycle/workflow/events.py +50 -0
  200. agent_lifecycle/workflow/final_proof_integrity.py +74 -0
  201. agent_lifecycle/workflow/finalization.py +422 -0
  202. agent_lifecycle/workflow/gates.py +185 -0
  203. agent_lifecycle/workflow/implementation_audit_gate.py +126 -0
  204. agent_lifecycle/workflow/leases.py +220 -0
  205. agent_lifecycle/workflow/lineage.py +108 -0
  206. agent_lifecycle/workflow/managed_runner.py +167 -0
  207. agent_lifecycle/workflow/model_usage.py +106 -0
  208. agent_lifecycle/workflow/next_action.py +86 -0
  209. agent_lifecycle/workflow/operation_kernel.py +98 -0
  210. agent_lifecycle/workflow/plan_adoption.py +439 -0
  211. agent_lifecycle/workflow/query.py +52 -0
  212. agent_lifecycle/workflow/review_mesh_gate.py +93 -0
  213. agent_lifecycle/workflow/reviews.py +168 -0
  214. agent_lifecycle/workflow/run_transitions.py +163 -0
  215. agent_lifecycle/workflow/sandbox_policy.py +214 -0
  216. agent_lifecycle/workflow/selectors.py +57 -0
  217. agent_lifecycle/workflow/state.py +130 -0
  218. agent_lifecycle/workflow/task_transitions.py +421 -0
  219. agent_lifecycle/worktree/__init__.py +17 -0
  220. agent_lifecycle/worktree/isolation.py +407 -0
  221. agent_lifecycle_kit-1.50.1.dist-info/METADATA +201 -0
  222. agent_lifecycle_kit-1.50.1.dist-info/RECORD +227 -0
  223. agent_lifecycle_kit-1.50.1.dist-info/WHEEL +5 -0
  224. agent_lifecycle_kit-1.50.1.dist-info/entry_points.txt +3 -0
  225. agent_lifecycle_kit-1.50.1.dist-info/licenses/LICENSE +201 -0
  226. agent_lifecycle_kit-1.50.1.dist-info/licenses/NOTICE +5 -0
  227. agent_lifecycle_kit-1.50.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,143 @@
1
+ """Safe host inspection probes for Qwen Code."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from agent_lifecycle.host_protocol.inspection_common import (
9
+ CommandRunner,
10
+ _check_cursor_plugin_config,
11
+ _check_hermes_registry,
12
+ _check_hermes_skills_config,
13
+ _check_hermes_slash_commands,
14
+ _check_json_plugin_config,
15
+ _check_scaffold_projection_files,
16
+ _cursor_model_count,
17
+ _cursor_subscription_tier,
18
+ _display_binary,
19
+ _first_non_empty_line,
20
+ _missing_markers,
21
+ _run_command_check,
22
+ _run_command_check_with_text,
23
+ )
24
+
25
+ def _inspect_qwen_code(
26
+ *,
27
+ descriptor_path: Path | None,
28
+ host_bin: str,
29
+ project_root: Path,
30
+ timeout_seconds: float,
31
+ command_runner: CommandRunner,
32
+ ) -> tuple[list[dict[str, Any]], dict[str, Any], list[dict[str, Any]]]:
33
+ checks: list[dict[str, Any]] = []
34
+ blockers: list[dict[str, Any]] = []
35
+ capabilities: dict[str, Any] = {
36
+ "hostCommands": {
37
+ "status": "CHECKED",
38
+ "binary": _display_binary(host_bin),
39
+ "profile": "qwen-code-safe-cli",
40
+ },
41
+ "headlessExecution": {"status": "UNKNOWN"},
42
+ "eventStream": {"status": "UNKNOWN"},
43
+ "usageAttestation": {"status": "UNKNOWN", "requiresLiveReceipt": True},
44
+ "permissionMode": {"status": "UNKNOWN"},
45
+ "modelSelection": {"status": "UNKNOWN"},
46
+ "resourceCaps": {"status": "UNKNOWN"},
47
+ "extensionDiscovery": {"status": "UNKNOWN"},
48
+ "mcpDiscovery": {"status": "UNKNOWN"},
49
+ "authState": {"status": "NOT_PROBED", "reason": "credential-state-redacted"},
50
+ "configuration": {},
51
+ }
52
+
53
+ if descriptor_path is not None:
54
+ projection = _check_scaffold_projection_files(descriptor_path.parent, project_root=project_root, host="qwen-code")
55
+ checks.append(projection)
56
+ if projection["status"] == "FAIL":
57
+ blockers.append({"code": "qwen-code-projection-unavailable", "message": "Qwen Code scaffold projection files are missing or invalid"})
58
+ capabilities["configuration"]["projection"] = projection["details"]
59
+
60
+ version_check, version_text = _run_command_check(
61
+ "qwen-code-version",
62
+ [host_bin, "--version"],
63
+ timeout_seconds=timeout_seconds,
64
+ command_runner=command_runner,
65
+ )
66
+ checks.append(version_check)
67
+ if version_check["status"] == "FAIL":
68
+ blockers.append({"code": "qwen-code-binary-unavailable", "message": "Qwen Code binary is unavailable or failed version probing"})
69
+ capabilities["hostVersion"] = version_text
70
+
71
+ help_check, _ = _run_command_check(
72
+ "qwen-code-help",
73
+ [host_bin, "--help"],
74
+ timeout_seconds=timeout_seconds,
75
+ command_runner=command_runner,
76
+ required_markers=[
77
+ "--prompt",
78
+ "--output-format",
79
+ "stream-json",
80
+ "--model",
81
+ "--fallback-model",
82
+ "--safe-mode",
83
+ "--sandbox",
84
+ "--continue",
85
+ "--resume",
86
+ "mcp",
87
+ "extensions",
88
+ "sessions",
89
+ "serve",
90
+ ],
91
+ )
92
+ checks.append(help_check)
93
+ if help_check["status"] == "FAIL":
94
+ blockers.append({"code": "qwen-code-help-unavailable", "message": "Qwen Code help does not expose required adapter surfaces"})
95
+ else:
96
+ capabilities["headlessExecution"] = {"status": "SUPPORTED", "command": "--prompt", "streamJson": "--output-format stream-json"}
97
+ capabilities["eventStream"] = {"status": "DISCOVERED", "source": "stream-json-output", "requiresReceiptValidation": True}
98
+ capabilities["usageAttestation"] = {
99
+ "status": "UNPROVEN",
100
+ "source": "stream-json-output",
101
+ "requiresLiveReceipt": True,
102
+ }
103
+ capabilities["permissionMode"] = {
104
+ "status": "DISCOVERED",
105
+ "safeModeFlag": "--safe-mode",
106
+ "sandboxFlag": "--sandbox",
107
+ "adapterPolicy": "fail-closed",
108
+ }
109
+ capabilities["modelSelection"] = {
110
+ "status": "DISCOVERED",
111
+ "selector": "--model",
112
+ "fallbackSelector": "--fallback-model",
113
+ }
114
+ capabilities["resourceCaps"] = {"status": "NOT_DISCOVERED", "reason": "root-help-does-not-expose-bounded-cap-flags"}
115
+ capabilities["authState"] = {"status": "NOT_PROBED", "reason": "auth-command-marked-removed"}
116
+
117
+ extensions_help, _ = _run_command_check(
118
+ "qwen-code-extensions-help",
119
+ [host_bin, "extensions", "--help"],
120
+ timeout_seconds=timeout_seconds,
121
+ command_runner=command_runner,
122
+ required_markers=["install", "list", "enable", "disable"],
123
+ )
124
+ checks.append(extensions_help)
125
+ if extensions_help["status"] == "FAIL":
126
+ blockers.append({"code": "qwen-code-extensions-surface-unavailable", "message": "Qwen Code extensions command surface is unavailable"})
127
+ else:
128
+ capabilities["extensionDiscovery"] = {"status": "DISCOVERED", "source": "extensions-command"}
129
+
130
+ mcp_help, _ = _run_command_check(
131
+ "qwen-code-mcp-help",
132
+ [host_bin, "mcp", "--help"],
133
+ timeout_seconds=timeout_seconds,
134
+ command_runner=command_runner,
135
+ required_markers=["add", "list", "approve", "reject"],
136
+ )
137
+ checks.append(mcp_help)
138
+ if mcp_help["status"] == "FAIL":
139
+ blockers.append({"code": "qwen-code-mcp-surface-unavailable", "message": "Qwen Code MCP command surface is unavailable"})
140
+ else:
141
+ capabilities["mcpDiscovery"] = {"status": "DISCOVERED", "source": "mcp-command"}
142
+
143
+ return checks, capabilities, blockers
@@ -0,0 +1,58 @@
1
+ """Host-operation receipt normalization helpers for adapters."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from agent_lifecycle.host_protocol.contracts import HostOperationReceipt
8
+
9
+ REDACTED_VALUE = "<redacted>"
10
+ SENSITIVE_EXACT_KEYS = {
11
+ "api_key",
12
+ "apikey",
13
+ "access_token",
14
+ "authorization",
15
+ "credential",
16
+ "id_token",
17
+ "password",
18
+ "refresh_token",
19
+ "secret",
20
+ "session_token",
21
+ "token",
22
+ }
23
+ SENSITIVE_KEY_PARTS = (
24
+ "api_key",
25
+ "apikey",
26
+ "authorization",
27
+ "credential",
28
+ "password",
29
+ "secret",
30
+ )
31
+
32
+
33
+ def normalize_host_operation_receipt(payload: dict[str, Any], *, redact_sensitive: bool = True) -> dict[str, Any]:
34
+ """Return a closed host-operation receipt with optional sensitive redaction."""
35
+
36
+ receipt = HostOperationReceipt.from_json(payload).to_json()
37
+ if not redact_sensitive:
38
+ return receipt
39
+ return _redact(receipt)
40
+
41
+
42
+ def _redact(value: Any) -> Any:
43
+ if isinstance(value, dict):
44
+ redacted: dict[str, Any] = {}
45
+ for key, item in value.items():
46
+ if _is_sensitive_key(key):
47
+ redacted[key] = REDACTED_VALUE
48
+ else:
49
+ redacted[key] = _redact(item)
50
+ return redacted
51
+ if isinstance(value, list):
52
+ return [_redact(item) for item in value]
53
+ return value
54
+
55
+
56
+ def _is_sensitive_key(key: str) -> bool:
57
+ normalized = key.replace("-", "_").lower()
58
+ return normalized in SENSITIVE_EXACT_KEYS or any(part in normalized for part in SENSITIVE_KEY_PARTS)
@@ -0,0 +1,329 @@
1
+ """Adapter projection scaffold generation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import re
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from agent_lifecycle.contracts import LifecycleError, canonical_bytes, sha256_hex, write_json_create
12
+ from agent_lifecycle.host_protocol.capabilities import build_capability_manifest
13
+ from agent_lifecycle.host_protocol.validation import REQUIRED_OPERATION_NAMES
14
+
15
+ HOST_ID_RE = re.compile(r"^[a-z][a-z0-9-]{1,31}$")
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class ScaffoldFile:
20
+ path: Path
21
+ role: str
22
+ payload: dict[str, Any] | str
23
+
24
+
25
+ def scaffold_adapter(
26
+ *,
27
+ host: str,
28
+ target: Path,
29
+ maturity: str = "EXPERIMENTAL",
30
+ dry_run: bool = False,
31
+ ) -> dict[str, Any]:
32
+ """Create an EXPERIMENTAL adapter projection skeleton under target."""
33
+
34
+ _validate_options(host=host, maturity=maturity)
35
+ files = _scaffold_files(host, target)
36
+ conflicts = [item.path.as_posix() for item in files if item.path.exists()]
37
+ if conflicts:
38
+ raise LifecycleError("adapter-scaffold-target-exists", "adapter scaffold target already contains generated files", {"paths": conflicts})
39
+ records = [_file_record(item) for item in files]
40
+ if not dry_run:
41
+ for item in files:
42
+ if isinstance(item.payload, str):
43
+ _write_text_create(item.path, item.payload)
44
+ else:
45
+ write_json_create(item.path, item.payload)
46
+ return {
47
+ "schemaVersion": "agent-adapter-scaffold-result.v1",
48
+ "status": "DRY_RUN" if dry_run else "PASS",
49
+ "host": host,
50
+ "target": target.as_posix(),
51
+ "maturity": maturity,
52
+ "files": records,
53
+ "productionPromotionClaimed": False,
54
+ }
55
+
56
+
57
+ def _validate_options(*, host: str, maturity: str) -> None:
58
+ if not HOST_ID_RE.fullmatch(host):
59
+ raise LifecycleError("invalid-adapter-host", "adapter host id must match ^[a-z][a-z0-9-]{1,31}$")
60
+ if maturity != "EXPERIMENTAL":
61
+ raise LifecycleError("adapter-scaffold-verified-forbidden", "adapter scaffold can only create EXPERIMENTAL projections")
62
+
63
+
64
+ def _scaffold_files(host: str, target: Path) -> list[ScaffoldFile]:
65
+ descriptor_path = target / "adapters" / host / "adapter.descriptor.json"
66
+ capability_path = target / "adapters" / host / "capabilities.manifest.json"
67
+ projection_path = target / "adapters" / host / "projection.manifest.json"
68
+ event_bridge_path = target / "adapters" / host / "event-bridge.md"
69
+ runner_path = target / "adapters" / host / "runner.py"
70
+ receipt_normalizer_path = target / "adapters" / host / "receipt_normalizer.py"
71
+ validation_path = target / "adapters" / host / "validation.md"
72
+ conformance_path = target / "conformance" / "adapters" / host / "offline-baseline.json"
73
+ docs_path = target / "docs" / "adapters" / f"{host}.md"
74
+ support_stub_path = target / "docs" / "adapters" / f"{host}.support.md"
75
+ test_path = target / "tests" / "adapters" / host / f"test_{host.replace('-', '_')}_adapter.py"
76
+ descriptor = _descriptor(host)
77
+ return [
78
+ ScaffoldFile(descriptor_path, "adapter-descriptor", descriptor),
79
+ ScaffoldFile(capability_path, "capability-manifest", build_capability_manifest(descriptor)),
80
+ ScaffoldFile(
81
+ projection_path,
82
+ "projection-manifest",
83
+ _projection_manifest(
84
+ host=host,
85
+ descriptor_path=descriptor_path,
86
+ capability_path=capability_path,
87
+ conformance_path=conformance_path,
88
+ docs_path=docs_path,
89
+ event_bridge_path=event_bridge_path,
90
+ runner_path=runner_path,
91
+ receipt_normalizer_path=receipt_normalizer_path,
92
+ validation_path=validation_path,
93
+ ),
94
+ ),
95
+ ScaffoldFile(event_bridge_path, "event-bridge-placeholder", _event_bridge(host)),
96
+ ScaffoldFile(runner_path, "runner-skeleton", _runner(host)),
97
+ ScaffoldFile(receipt_normalizer_path, "receipt-normalizer", _receipt_normalizer(host)),
98
+ ScaffoldFile(validation_path, "validation-instructions", _validation(host)),
99
+ ScaffoldFile(conformance_path, "offline-conformance-baseline", _offline_baseline(host, descriptor_path)),
100
+ ScaffoldFile(docs_path, "adapter-doc", _docs(host)),
101
+ ScaffoldFile(support_stub_path, "support-matrix-stub", _support_stub(host)),
102
+ ScaffoldFile(test_path, "adapter-test-skeleton", _test_skeleton(host)),
103
+ ]
104
+
105
+
106
+ def _descriptor(host: str) -> dict[str, Any]:
107
+ return {
108
+ "schemaVersion": "agent-lifecycle-host-adapter.v1",
109
+ "adapterId": host,
110
+ "host": host,
111
+ "nativeProjection": "host-local-projection",
112
+ "capabilityManifest": f"adapters/{host}/capabilities.manifest.json",
113
+ "maturity": "EXPERIMENTAL",
114
+ "liveTestedHostRange": None,
115
+ "contractCompatibility": {
116
+ "rangeKind": "closed-offline",
117
+ "minimum": "adapter-contract.v1",
118
+ "maximum": "adapter-contract.v1",
119
+ "authoritativeSource": {
120
+ "sourceId": "normalized-contract-inventory-r02",
121
+ "sha256": "c332a29de6eed7dcf1ff93cfa5cb0868557f31d1f7ff635ba2df2c7695ed3d4a",
122
+ },
123
+ },
124
+ "unsupportedOperationPolicy": "fail-closed",
125
+ "coreSemantics": "delegated-to-agent-lifecycle-core",
126
+ "managedLaunch": {
127
+ "status": "UNSUPPORTED",
128
+ "reason": "Scaffolded adapter has no managed native launch profile yet.",
129
+ "shell": False,
130
+ "timeoutSeconds": 30.0,
131
+ "env": {"allow": [], "allowPatterns": [], "projectPolicyAllowed": True},
132
+ "writesNativeConfig": False,
133
+ "promptInjectionDefault": False,
134
+ },
135
+ "modelRouting": {
136
+ "status": "workflow-enforced",
137
+ "profileSupport": "host-local",
138
+ "providerModelNamesInCore": False,
139
+ "attemptRoutePolicy": "must-execute-or-fail-closed",
140
+ "usageReceiptRequired": True,
141
+ "unsupportedClassPolicy": "fail-closed",
142
+ "criticalReviewPolicy": "requires-strong-or-calibrated-local-review",
143
+ "liveVerified": False,
144
+ },
145
+ "operations": [
146
+ {
147
+ "name": name,
148
+ "mapping": "host-local-fail-closed",
149
+ "offlineConformance": "synthetic",
150
+ }
151
+ for name in sorted(REQUIRED_OPERATION_NAMES)
152
+ ],
153
+ }
154
+
155
+
156
+ def _projection_manifest(
157
+ *,
158
+ host: str,
159
+ descriptor_path: Path,
160
+ capability_path: Path,
161
+ conformance_path: Path,
162
+ docs_path: Path,
163
+ event_bridge_path: Path,
164
+ runner_path: Path,
165
+ receipt_normalizer_path: Path,
166
+ validation_path: Path,
167
+ ) -> dict[str, Any]:
168
+ return {
169
+ "schemaVersion": "agent-adapter-projection-manifest.v1",
170
+ "host": host,
171
+ "maturity": "EXPERIMENTAL",
172
+ "descriptor": descriptor_path.as_posix(),
173
+ "capabilityManifest": capability_path.as_posix(),
174
+ "offlineBaseline": conformance_path.as_posix(),
175
+ "docs": docs_path.as_posix(),
176
+ "eventBridge": {
177
+ "path": event_bridge_path.as_posix(),
178
+ "status": "placeholder",
179
+ "portableEventSchema": "agent-adapter-event.v1",
180
+ "runtimeDispatch": "not-implemented-fail-closed",
181
+ },
182
+ "runner": {
183
+ "path": runner_path.as_posix(),
184
+ "status": "fail-closed-skeleton",
185
+ },
186
+ "receiptNormalizer": {
187
+ "path": receipt_normalizer_path.as_posix(),
188
+ "status": "contract-normalization-skeleton",
189
+ "portableReceiptSchema": "agent-host-operation-receipt.v1",
190
+ },
191
+ "validation": {
192
+ "path": validation_path.as_posix(),
193
+ "descriptorCommand": f"agent-lifecycle adapter validate --descriptor adapters/{host}/adapter.descriptor.json --baseline conformance/core/adapter-baseline.v1.json",
194
+ "inspectCommand": f"agent-lifecycle adapter inspect --descriptor adapters/{host}/adapter.descriptor.json --skip-host-commands",
195
+ "supportMatrixCommand": "python tools/release/validate_support_matrix.py --support-matrix docs/adapters/support-matrix.md --profile plans/standalone-v1/.agent-plan/standalone-v1/ci-matrix-profile.v2.json --evidence <support-matrix-evidence.json>",
196
+ },
197
+ "providerModelNamesInCore": False,
198
+ "productionPromotionClaimed": False,
199
+ "publicDirectoryApprovalClaimed": False,
200
+ }
201
+
202
+
203
+ def _offline_baseline(host: str, descriptor_path: Path) -> dict[str, Any]:
204
+ return {
205
+ "schemaVersion": "agent-lifecycle-adapter-conformance.v1",
206
+ "adapterId": host,
207
+ "host": host,
208
+ "baselineId": "offline-adapter-baseline-v1",
209
+ "adapterDescriptor": descriptor_path.as_posix(),
210
+ "capabilityManifest": descriptor_path.with_name("capabilities.manifest.json").as_posix(),
211
+ "nativeManifest": None,
212
+ "sharedBaseline": "conformance/core/adapter-baseline.v1.json",
213
+ "requiredMaturity": "EXPERIMENTAL",
214
+ "liveRuntimeRequired": False,
215
+ "expectedResult": "PASS",
216
+ }
217
+
218
+
219
+ def _event_bridge(host: str) -> str:
220
+ return (
221
+ f"# {host} event bridge\n\n"
222
+ "This is an EXPERIMENTAL event bridge placeholder. A real host adapter\n"
223
+ "must translate host lifecycle callbacks into `agent-adapter-event.v1`\n"
224
+ "records and validate them with `agent-lifecycle adapter event-check`.\n\n"
225
+ "The scaffold does not implement runtime dispatch. Unsupported operations\n"
226
+ "must fail closed until host-specific live conformance evidence exists.\n"
227
+ )
228
+
229
+
230
+ def _runner(host: str) -> str:
231
+ return (
232
+ '"""Fail-closed runner skeleton for the adapter projection."""\n\n'
233
+ "from __future__ import annotations\n\n"
234
+ "from typing import Any\n\n"
235
+ "from agent_lifecycle.contracts import LifecycleError\n\n\n"
236
+ "def run_operation(request: dict[str, Any]) -> dict[str, Any]:\n"
237
+ " capability = request.get(\"capability\")\n"
238
+ " raise LifecycleError(\n"
239
+ " \"adapter-operation-not-implemented\",\n"
240
+ f" \"{host} adapter runner is not implemented for live execution\",\n"
241
+ f" {{\"host\": \"{host}\", \"capability\": capability}},\n"
242
+ " )\n"
243
+ )
244
+
245
+
246
+ def _receipt_normalizer(host: str) -> str:
247
+ return (
248
+ '"""Receipt normalizer for the adapter projection."""\n\n'
249
+ "from __future__ import annotations\n\n"
250
+ "from typing import Any\n\n"
251
+ "from agent_lifecycle.host_protocol import normalize_host_operation_receipt\n\n\n"
252
+ "def normalize_receipt(payload: dict[str, Any]) -> dict[str, Any]:\n"
253
+ f" \"\"\"Normalize one {host} host receipt into the portable contract.\"\"\"\n"
254
+ " return normalize_host_operation_receipt(payload)\n"
255
+ )
256
+
257
+
258
+ def _validation(host: str) -> str:
259
+ return (
260
+ f"# {host} validation\n\n"
261
+ "Minimum offline checks before committing the projection:\n\n"
262
+ "```bash\n"
263
+ f"agent-lifecycle adapter validate --descriptor adapters/{host}/adapter.descriptor.json --baseline conformance/core/adapter-baseline.v1.json\n"
264
+ f"agent-lifecycle adapter inspect --descriptor adapters/{host}/adapter.descriptor.json --skip-host-commands\n"
265
+ "agent-lifecycle adapter event-check --event <adapter-event-1.json> --event <adapter-event-2.json>\n"
266
+ "python tools/release/validate_support_matrix.py --support-matrix docs/adapters/support-matrix.md --profile plans/standalone-v1/.agent-plan/standalone-v1/ci-matrix-profile.v2.json --evidence <support-matrix-evidence.json>\n"
267
+ "```\n\n"
268
+ "These checks prove only an EXPERIMENTAL source projection. Promotion to\n"
269
+ "`VERIFIED` requires bounded live host conformance, live calibration,\n"
270
+ "and lifecycle proof evidence bound to the tested host version.\n"
271
+ )
272
+
273
+
274
+ def _docs(host: str) -> str:
275
+ return (
276
+ f"# {host} adapter\n\n"
277
+ "This scaffold is an EXPERIMENTAL host projection skeleton. It contains\n"
278
+ "no lifecycle semantics, no concrete provider model names, and no\n"
279
+ "`VERIFIED` or production-promotion claim.\n\n"
280
+ "Before promotion, add host-local runtime evidence and validate it through\n"
281
+ "the release support matrix, live host conformance validator, live\n"
282
+ "calibration validator, and final lifecycle proof.\n"
283
+ )
284
+
285
+
286
+ def _support_stub(host: str) -> str:
287
+ return (
288
+ f"# {host} support matrix stub\n\n"
289
+ "Initial maturity: `EXPERIMENTAL`.\n\n"
290
+ "Do not claim `VERIFIED`, public directory approval, or production\n"
291
+ "promotion until bounded live host conformance, usage calibration,\n"
292
+ "redaction scan, and support-matrix review all pass for a concrete host\n"
293
+ "version range.\n"
294
+ )
295
+
296
+
297
+ def _test_skeleton(host: str) -> str:
298
+ return (
299
+ "from __future__ import annotations\n\n"
300
+ "import json\n"
301
+ "from pathlib import Path\n\n"
302
+ "from agent_lifecycle.host_protocol import build_capability_manifest, validate_adapter_descriptor, validate_capability_manifest\n\n\n"
303
+ "ROOT = Path(__file__).resolve().parents[3]\n\n\n"
304
+ "def test_descriptor_and_capability_manifest_pass_offline_contracts() -> None:\n"
305
+ f" descriptor_path = ROOT / \"adapters/{host}/adapter.descriptor.json\"\n"
306
+ " descriptor = json.loads(descriptor_path.read_text(encoding=\"utf-8\"))\n"
307
+ " baseline = json.loads((ROOT / \"conformance/core/adapter-baseline.v1.json\").read_text(encoding=\"utf-8\"))\n"
308
+ " manifest = build_capability_manifest(descriptor)\n\n"
309
+ " assert validate_adapter_descriptor(descriptor, baseline=baseline)[\"status\"] == \"PASS\"\n"
310
+ " assert validate_capability_manifest(manifest, descriptor=descriptor)[\"status\"] == \"PASS\"\n"
311
+ f" assert manifest[\"adapterId\"] == \"{host}\"\n"
312
+ )
313
+
314
+
315
+ def _file_record(item: ScaffoldFile) -> dict[str, Any]:
316
+ payload = item.payload
317
+ data = payload.encode("utf-8") if isinstance(payload, str) else canonical_bytes(payload) + b"\n"
318
+ return {
319
+ "path": item.path.as_posix(),
320
+ "role": item.role,
321
+ "sha256": sha256_hex(data),
322
+ }
323
+
324
+
325
+ def _write_text_create(path: Path, text: str) -> None:
326
+ path.parent.mkdir(parents=True, exist_ok=True)
327
+ flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
328
+ with os.fdopen(os.open(path, flags, 0o644), "w", encoding="utf-8") as handle:
329
+ handle.write(text)