getcodexy 1.6.2__tar.gz → 1.7.0__tar.gz

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 (81) hide show
  1. getcodexy-1.7.0/PKG-INFO +83 -0
  2. getcodexy-1.7.0/README.md +73 -0
  3. {getcodexy-1.6.2 → getcodexy-1.7.0}/pyproject.toml +4 -3
  4. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/_version_data/uv.lock +1 -1
  5. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component-manifest.json +15 -8
  6. getcodexy-1.7.0/src/codexy_runtime_tools/component_capability_observation.py +106 -0
  7. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_capability_probe.py +35 -95
  8. getcodexy-1.7.0/src/codexy_runtime_tools/component_capability_probe_process.py +250 -0
  9. getcodexy-1.7.0/src/codexy_runtime_tools/component_core_hooks.py +27 -0
  10. getcodexy-1.7.0/src/codexy_runtime_tools/component_health.py +146 -0
  11. getcodexy-1.6.2/src/codexy_runtime_tools/component_health.py → getcodexy-1.7.0/src/codexy_runtime_tools/component_health_support.py +11 -116
  12. getcodexy-1.7.0/src/codexy_runtime_tools/component_hook_activation.py +94 -0
  13. getcodexy-1.7.0/src/codexy_runtime_tools/component_hook_activation_expected.py +140 -0
  14. getcodexy-1.7.0/src/codexy_runtime_tools/component_hook_activation_host.py +240 -0
  15. getcodexy-1.7.0/src/codexy_runtime_tools/component_hook_activation_validation.py +156 -0
  16. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_inspection.py +43 -20
  17. getcodexy-1.7.0/src/codexy_runtime_tools/component_inspection_types.py +18 -0
  18. getcodexy-1.7.0/src/codexy_runtime_tools/component_interactive_probe.py +115 -0
  19. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_lifecycle_admission.py +1 -1
  20. getcodexy-1.7.0/src/codexy_runtime_tools/component_lifecycle_finish.py +72 -0
  21. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_lifecycle_operation.py +5 -6
  22. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_lifecycle_recovery.py +42 -51
  23. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_manifest.py +4 -2
  24. getcodexy-1.7.0/src/codexy_runtime_tools/component_mcp_probe.py +89 -0
  25. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_registration_health.py +55 -63
  26. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_transition_journal.py +5 -2
  27. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_transition_model.py +1 -1
  28. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_transition_receipt.py +32 -7
  29. getcodexy-1.7.0/src/codexy_runtime_tools/component_watcher_probe.py +174 -0
  30. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/github_checks.py +13 -10
  31. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/installer.py +17 -8
  32. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/marketplace_identity.py +8 -2
  33. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/package.py +9 -2
  34. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/release_contract_validation.py +26 -26
  35. getcodexy-1.7.0/src/codexy_runtime_tools/release_provenance.py +32 -0
  36. getcodexy-1.7.0/src/codexy_runtime_tools/release_watcher_contract.py +34 -0
  37. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/runtime.py +5 -4
  38. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/runtime_configuration.py +10 -2
  39. getcodexy-1.7.0/src/codexy_runtime_tools/title_policy.py +124 -0
  40. {getcodexy-1.6.2 → getcodexy-1.7.0}/uv.lock +1 -1
  41. getcodexy-1.6.2/PKG-INFO +0 -8
  42. {getcodexy-1.6.2 → getcodexy-1.7.0}/build_backend.py +0 -0
  43. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/__init__.py +0 -0
  44. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/activation_transaction.py +0 -0
  45. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/cache.py +0 -0
  46. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_cli.py +0 -0
  47. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_integrity.py +0 -0
  48. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_inventory_classification.py +0 -0
  49. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_inventory_records.py +0 -0
  50. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_lifecycle.py +0 -0
  51. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_lifecycle_interlock.py +0 -0
  52. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_lifecycle_preflight.py +0 -0
  53. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_lifecycle_support.py +0 -0
  54. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_lifecycle_terminal.py +0 -0
  55. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_manifest_dependencies.py +0 -0
  56. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_manifest_values.py +0 -0
  57. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_observed_inventory.py +0 -0
  58. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_resolver.py +0 -0
  59. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_transaction_durability.py +0 -0
  60. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_transaction_identity.py +0 -0
  61. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_transaction_receipts.py +0 -0
  62. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_transaction_snapshot.py +0 -0
  63. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_transaction_state.py +0 -0
  64. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/component_transition_rejections.py +0 -0
  65. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/contract.py +0 -0
  66. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/github_pre_session.py +0 -0
  67. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/identity.py +0 -0
  68. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/local_marketplace.py +0 -0
  69. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/marketplace_repin.py +0 -0
  70. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/monolith_baseline.py +0 -0
  71. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/monolith_classifier.py +0 -0
  72. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/monolith_migration.py +0 -0
  73. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/monolith_migration_host.py +0 -0
  74. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/monolith_migration_plan.py +0 -0
  75. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/monolith_migration_state.py +0 -0
  76. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/package_archive.py +0 -0
  77. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/plugin_resolution.py +0 -0
  78. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/pre_session.py +0 -0
  79. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/source.py +0 -0
  80. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/updater.py +0 -0
  81. {getcodexy-1.6.2 → getcodexy-1.7.0}/src/codexy_runtime_tools/version_lock.py +0 -0
@@ -0,0 +1,83 @@
1
+ Metadata-Version: 2.4
2
+ Name: getcodexy
3
+ Version: 1.7.0
4
+ Summary: A Codex harness for coordinating agents through planning, implementation, verification, and review.
5
+ Author: Eunsoo Lee
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.10
8
+ Project-URL: Repository, https://github.com/eunsoogi/codexy
9
+ Description-Content-Type: text/markdown
10
+
11
+ # Codexy
12
+
13
+ Codexy is a Codex harness for coordinating agents through planning,
14
+ implementation, verification, and review.
15
+
16
+ It helps Codex turn a broad repository request into an owned plan, divide work
17
+ among agents, implement changes, verify actual outcomes, and keep the next
18
+ handoff tied to current evidence.
19
+
20
+ ## Components
21
+
22
+ Codexy is delivered as three cooperating components. `github` and `devtools`
23
+ each depend on `core`; the installer adds those dependencies automatically.
24
+
25
+ | Component | Plugin | What it adds |
26
+ | ---------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------- |
27
+ | `core` | `codexy` | Orchestration, finite goals and plans, worktree ownership, specialist roles, instruction hooks, and proof-driven completion. |
28
+ | `github` | `codexy-github` | Issue-to-merge workflows for branches, pull requests, CI, review feedback, releases, and GitHub safety checks. |
29
+ | `devtools` | `codexy-devtools` | Local Codegraph and LSP servers, configuration, wrappers, and developer-tool guidance. |
30
+
31
+ The component model lets each repository install the capabilities it needs
32
+ without losing a single ownership and verification contract.
33
+
34
+ ## Install
35
+
36
+ Install the `getcodexy` command, then install the complete Codexy product:
37
+
38
+ ```sh
39
+ uv tool install getcodexy
40
+ uv tool update-shell
41
+ ```
42
+
43
+ Start a new shell so the updated tool path is available, then install Codexy:
44
+
45
+ ```sh
46
+ getcodexy install
47
+ ```
48
+
49
+ The default selection installs `core`, `github`, and `devtools`. To choose a
50
+ smaller dependency closure, name the optional components you need:
51
+
52
+ ```sh
53
+ getcodexy install core
54
+ getcodexy install github
55
+ getcodexy install devtools
56
+ getcodexy install github devtools
57
+ ```
58
+
59
+ Open a fresh Codex session after installation or update so the host can expose
60
+ the installed plugins, skills, agents, hooks, and MCP servers.
61
+
62
+ ## Maintain and check Codexy
63
+
64
+ Keep the installer and installed component selection current, inspect health, or
65
+ remove an optional component:
66
+
67
+ ```sh
68
+ uv tool upgrade getcodexy
69
+ getcodexy update
70
+ getcodexy status
71
+ getcodexy doctor
72
+ getcodexy remove github
73
+ ```
74
+
75
+ `status` reports the installed inventory and its consistency. `doctor` checks
76
+ host readiness and the health of each installed component. Update and removal
77
+ operations preserve the dependency closure and reject inconsistent selections.
78
+
79
+ ## Project links
80
+
81
+ - [Source repository](https://github.com/eunsoogi/codexy)
82
+ - [Issue tracker](https://github.com/eunsoogi/codexy/issues)
83
+ - [License](https://github.com/eunsoogi/codexy/blob/main/LICENSE)
@@ -0,0 +1,73 @@
1
+ # Codexy
2
+
3
+ Codexy is a Codex harness for coordinating agents through planning,
4
+ implementation, verification, and review.
5
+
6
+ It helps Codex turn a broad repository request into an owned plan, divide work
7
+ among agents, implement changes, verify actual outcomes, and keep the next
8
+ handoff tied to current evidence.
9
+
10
+ ## Components
11
+
12
+ Codexy is delivered as three cooperating components. `github` and `devtools`
13
+ each depend on `core`; the installer adds those dependencies automatically.
14
+
15
+ | Component | Plugin | What it adds |
16
+ | ---------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------- |
17
+ | `core` | `codexy` | Orchestration, finite goals and plans, worktree ownership, specialist roles, instruction hooks, and proof-driven completion. |
18
+ | `github` | `codexy-github` | Issue-to-merge workflows for branches, pull requests, CI, review feedback, releases, and GitHub safety checks. |
19
+ | `devtools` | `codexy-devtools` | Local Codegraph and LSP servers, configuration, wrappers, and developer-tool guidance. |
20
+
21
+ The component model lets each repository install the capabilities it needs
22
+ without losing a single ownership and verification contract.
23
+
24
+ ## Install
25
+
26
+ Install the `getcodexy` command, then install the complete Codexy product:
27
+
28
+ ```sh
29
+ uv tool install getcodexy
30
+ uv tool update-shell
31
+ ```
32
+
33
+ Start a new shell so the updated tool path is available, then install Codexy:
34
+
35
+ ```sh
36
+ getcodexy install
37
+ ```
38
+
39
+ The default selection installs `core`, `github`, and `devtools`. To choose a
40
+ smaller dependency closure, name the optional components you need:
41
+
42
+ ```sh
43
+ getcodexy install core
44
+ getcodexy install github
45
+ getcodexy install devtools
46
+ getcodexy install github devtools
47
+ ```
48
+
49
+ Open a fresh Codex session after installation or update so the host can expose
50
+ the installed plugins, skills, agents, hooks, and MCP servers.
51
+
52
+ ## Maintain and check Codexy
53
+
54
+ Keep the installer and installed component selection current, inspect health, or
55
+ remove an optional component:
56
+
57
+ ```sh
58
+ uv tool upgrade getcodexy
59
+ getcodexy update
60
+ getcodexy status
61
+ getcodexy doctor
62
+ getcodexy remove github
63
+ ```
64
+
65
+ `status` reports the installed inventory and its consistency. `doctor` checks
66
+ host readiness and the health of each installed component. Update and removal
67
+ operations preserve the dependency closure and reject inconsistent selections.
68
+
69
+ ## Project links
70
+
71
+ - [Source repository](https://github.com/eunsoogi/codexy)
72
+ - [Issue tracker](https://github.com/eunsoogi/codexy/issues)
73
+ - [License](https://github.com/eunsoogi/codexy/blob/main/LICENSE)
@@ -5,8 +5,9 @@ backend-path = ["."]
5
5
 
6
6
  [project]
7
7
  name = "getcodexy"
8
- version = "1.6.2"
9
- description = "Version-pinned MCP runtime bootstrap tools for Codexy"
8
+ version = "1.7.0"
9
+ description = "A Codex harness for coordinating agents through planning, implementation, verification, and review."
10
+ readme = { file = "README.md", content-type = "text/markdown" }
10
11
  requires-python = ">=3.10"
11
12
  license = "MIT"
12
13
  authors = [{ name = "Eunsoo Lee" }]
@@ -23,4 +24,4 @@ codexy-github-check = "codexy_runtime_tools.github_checks:main"
23
24
  [tool.uv.build-backend]
24
25
  module-name = "codexy_runtime_tools"
25
26
  module-root = "src"
26
- source-include = ["build_backend.py", "uv.lock"]
27
+ source-include = ["README.md", "build_backend.py", "uv.lock"]
@@ -4,5 +4,5 @@ requires-python = ">=3.10"
4
4
 
5
5
  [[package]]
6
6
  name = "getcodexy"
7
- version = "1.6.2"
7
+ version = "1.7.0"
8
8
  source = { editable = "." }
@@ -10,6 +10,7 @@
10
10
  "conflicting-component-request": "A component request contains a duplicate or conflicting operand.",
11
11
  "conflicting-installed-state": "An installed record does not match its official marketplace asset.",
12
12
  "dependency-protected-removal": "A retained component still requires a requested removal.",
13
+ "hook-state-unavailable": "The trusted host did not return an inspectable effective Codexy hook state.",
13
14
  "incompatible-component-selection": "A component selection is not in the manifest compatibility set.",
14
15
  "inconsistent-installed-state": "The installed inventory violates component dependencies.",
15
16
  "installed-state-mismatch": "The fresh installed inventory differs from the expected selection.",
@@ -18,6 +19,9 @@
18
19
  "mixed-version-state": "Installed components do not share one lockstep version.",
19
20
  "no-recorded-selection": "update requires a recorded installed component inventory.",
20
21
  "operation-failed": "A mutation did not complete and its pre-operation selection was restored.",
22
+ "required-hook-disabled": "A required Codexy hook is present but disabled in the host activation state.",
23
+ "required-hook-trust-missing": "A required Codexy hook is not present in the host trusted activation state.",
24
+ "required-hook-trust-stale": "A required Codexy hook trust entry no longer matches the installed hook.",
21
25
  "unknown-component": "A named component is not core, github, or devtools.",
22
26
  "unknown-installed-component": "The official Codexy marketplace inventory contains an unrecognized product."
23
27
  },
@@ -25,7 +29,7 @@
25
29
  {
26
30
  "id": "core",
27
31
  "plugin": "codexy",
28
- "version": "1.6.2",
32
+ "version": "1.7.0",
29
33
  "dependencies": [],
30
34
  "asset": {
31
35
  "pluginId": "codexy@codexy",
@@ -33,6 +37,9 @@
33
37
  "requiredPaths": [
34
38
  ".codex-plugin/plugin.json",
35
39
  "assets/codexy-icon.png",
40
+ ".mcp.json",
41
+ "mcp/codexy-mcp-watcher.sh",
42
+ "mcp/codexy-mcp-watcher.cmd",
36
43
  "skills/dreaming/scripts/resumable-context-capsule.sh",
37
44
  "skills/dreaming/scripts/resumable-context-capsule.cmd",
38
45
  "skills/dreaming/scripts/resumable_context_capsule.py"
@@ -42,7 +49,7 @@
42
49
  {
43
50
  "id": "github",
44
51
  "plugin": "codexy-github",
45
- "version": "1.6.2",
52
+ "version": "1.7.0",
46
53
  "dependencies": [
47
54
  "core"
48
55
  ],
@@ -58,7 +65,7 @@
58
65
  {
59
66
  "id": "devtools",
60
67
  "plugin": "codexy-devtools",
61
- "version": "1.6.2",
68
+ "version": "1.7.0",
62
69
  "dependencies": [
63
70
  "core"
64
71
  ],
@@ -75,27 +82,27 @@
75
82
  "compatibleCombinations": [
76
83
  {
77
84
  "components": [],
78
- "version": "1.6.2"
85
+ "version": "1.7.0"
79
86
  },
80
87
  {
81
88
  "components": [
82
89
  "core"
83
90
  ],
84
- "version": "1.6.2"
91
+ "version": "1.7.0"
85
92
  },
86
93
  {
87
94
  "components": [
88
95
  "core",
89
96
  "github"
90
97
  ],
91
- "version": "1.6.2"
98
+ "version": "1.7.0"
92
99
  },
93
100
  {
94
101
  "components": [
95
102
  "core",
96
103
  "devtools"
97
104
  ],
98
- "version": "1.6.2"
105
+ "version": "1.7.0"
99
106
  },
100
107
  {
101
108
  "components": [
@@ -103,7 +110,7 @@
103
110
  "github",
104
111
  "devtools"
105
112
  ],
106
- "version": "1.6.2"
113
+ "version": "1.7.0"
107
114
  }
108
115
  ]
109
116
  }
@@ -0,0 +1,106 @@
1
+ """Additive capability observations for the read-only doctor report."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Mapping
6
+
7
+
8
+ UNKNOWN = "unknown"
9
+ _DIRECT_SOURCE = "getcodexy-direct-probe"
10
+ _DIRECT_SCOPE = "plugin-subprocess"
11
+ _REGISTRATION_SOURCE = "getcodexy-registration-check"
12
+ _REGISTRATION_SCOPE = "current-doctor-invocation"
13
+
14
+ CAPABILITIES = {
15
+ "core": (
16
+ "hook:codexy-thread-delivery",
17
+ "specialist:codexy-architect",
18
+ "specialist:codexy-auditor",
19
+ "specialist:codexy-cartographer",
20
+ "specialist:codexy-inspector",
21
+ "specialist:codexy-sentinel",
22
+ "specialist:codexy-shipwright",
23
+ "specialist:codexy-warden",
24
+ ),
25
+ "github": ("hook:codexy-github-workflow-context", "specialist:codexy-weaver"),
26
+ "devtools": ("mcp:codegraph", "mcp:lsp"),
27
+ }
28
+
29
+
30
+ def component_observations(
31
+ component: str,
32
+ configured: bool,
33
+ probe: Mapping[str, object] | None = None,
34
+ ) -> dict[str, dict[str, object]]:
35
+ """Return per-capability states without claiming native host execution."""
36
+ observations = {
37
+ name: _entry(configured, source=_REGISTRATION_SOURCE, scope=_REGISTRATION_SCOPE)
38
+ for name in CAPABILITIES.get(component, ())
39
+ }
40
+ if not probe:
41
+ return observations
42
+ direct = probe.get("_capability_probes")
43
+ if not isinstance(direct, dict):
44
+ direct = {}
45
+ for name in observations:
46
+ details = direct.get(name)
47
+ if isinstance(details, dict):
48
+ observations[name] = _entry(
49
+ configured,
50
+ started=details.get("started") is True,
51
+ callable=details.get("callable") is True,
52
+ source=_DIRECT_SOURCE,
53
+ scope=_DIRECT_SCOPE,
54
+ )
55
+ return observations
56
+
57
+
58
+ def probe_observation(
59
+ capability: str,
60
+ configured: bool,
61
+ started: bool,
62
+ callable_: bool,
63
+ ) -> dict[str, dict[str, object]]:
64
+ """Describe one direct probe for health's additive observation mapping."""
65
+ return {
66
+ capability: {
67
+ "configured": configured,
68
+ "started": started,
69
+ "callable": callable_,
70
+ }
71
+ }
72
+
73
+
74
+ def record_probe(
75
+ target: dict[str, object],
76
+ capability: str,
77
+ configured: bool,
78
+ started: bool,
79
+ callable_: bool,
80
+ ) -> None:
81
+ """Attach one current direct-probe result to an internal probe payload."""
82
+ probes = target.setdefault("_capability_probes", {})
83
+ if isinstance(probes, dict):
84
+ probes.update(probe_observation(capability, configured, started, callable_))
85
+
86
+
87
+ def _entry(
88
+ configured: bool,
89
+ *,
90
+ started: bool = False,
91
+ callable: bool = False,
92
+ source: str,
93
+ scope: str,
94
+ ) -> dict[str, object]:
95
+ return {
96
+ "states": {
97
+ "configured": "configured" if configured else UNKNOWN,
98
+ "loaded": "loaded" if started else UNKNOWN,
99
+ "callable": "callable" if callable else UNKNOWN,
100
+ "verified": UNKNOWN,
101
+ },
102
+ "source": source if configured or started or callable else UNKNOWN,
103
+ "scope": scope if configured or started or callable else UNKNOWN,
104
+ "host_id": None,
105
+ "session_id": None,
106
+ }
@@ -2,10 +2,18 @@
2
2
 
3
3
  import json
4
4
  import os
5
- from collections import namedtuple
6
5
  import shlex
7
6
  import subprocess
8
7
 
8
+ from .component_hook_activation import ACTIVATION_REPAIRS
9
+ from .component_capability_observation import record_probe
10
+ from .component_capability_probe_process import (
11
+ _RUN_OPTIONS,
12
+ _RunResult,
13
+ _probe_diagnostics,
14
+ _run,
15
+ )
16
+ from .component_watcher_probe import probe_watcher as _probe_watcher
9
17
  from .version_lock import default_package_version
10
18
 
11
19
 
@@ -16,8 +24,6 @@ _START_REPAIR = f"repair the installed launcher/runtime, then {_RERUN}"
16
24
  _CALL_REPAIR = f"use the reported safe component fallback and {_RERUN}"
17
25
  _EXPOSED_REPAIR = "repair the Codexy registration, then restart Codex"
18
26
  _IDENTITY_REPAIR = "reinstall the selected release, then restart Codex"
19
- _RUN_OPTIONS = {"capture_output": True, "text": True, "timeout": 5}
20
- _RunResult = namedtuple("_RunResult", "returncode stdout category")
21
27
  FAILURES = {
22
28
  "trusted-inventory-unavailable": (_INVENTORY_REPAIR, False),
23
29
  "component-not-installed": ("getcodexy bootstrap", True),
@@ -27,6 +33,7 @@ FAILURES = {
27
33
  "capability-call-failed": (_CALL_REPAIR, False),
28
34
  "runtime-identity-mismatch": (_IDENTITY_REPAIR, True),
29
35
  "artifact-authority-invalid": ("reinstall from a trusted release artifact", True),
36
+ **ACTIVATION_REPAIRS,
30
37
  }
31
38
  HOOK_SPECS = {
32
39
  "core": ("PermissionRequest", "codexy-thread-delivery"),
@@ -53,17 +60,34 @@ def probe_component(component, plugin, record):
53
60
  base = _base(record)
54
61
  if plugin is None:
55
62
  return base
63
+ if component == "core":
64
+ return _probe_core(plugin, base)
56
65
  probe = _probe_hook if component in HOOK_SPECS else _probe_devtools
57
66
  return probe(component, plugin, base)
58
67
 
59
68
 
69
+ def _probe_core(plugin, base):
70
+ hook = _probe_hook("core", plugin, base)
71
+ if not hook.get("started") or not hook.get("callable"):
72
+ return hook
73
+ watcher = _probe_watcher(plugin, base)
74
+ probes = dict(hook.get("_capability_probes", {}))
75
+ probes.update(watcher.get("_capability_probes", {}))
76
+ watcher["_capability_probes"] = probes
77
+ if not watcher.get("started") or not watcher.get("callable"):
78
+ return watcher
79
+ return _outcome(base, _capability_probes=probes)
80
+
81
+
60
82
  def _probe_hook(component, plugin, base):
61
83
  event, marker = HOOK_SPECS[component]
84
+ capability = f"hook:{marker}"
62
85
  payload = {"prompt": "review GitHub issue 723"}
63
86
  if component == "core":
64
87
  payload = {"tool_name": "codex_app__send_message_to_thread", "tool_input": {}}
65
88
  command = _registered_hook(plugin, event, marker)
66
89
  if not command:
90
+ record_probe(base, capability, False, False, False)
67
91
  return _failure(base, "capability-not-exposed")
68
92
  result = _run(
69
93
  _argv(command, plugin),
@@ -71,10 +95,13 @@ def _probe_hook(component, plugin, base):
71
95
  json.dumps(payload),
72
96
  os.environ | {"PLUGIN_ROOT": str(plugin)},
73
97
  )
98
+ base["_capability_probe"] = _probe_diagnostics(result)
74
99
  base["_category"] = result.category
75
100
  if result.category == "missing-launcher":
101
+ record_probe(base, capability, True, False, False)
76
102
  return _failure(base, "component-start-failed", started=False)
77
103
  if result.category in {"timeout", "nonzero-exit"}:
104
+ record_probe(base, capability, True, True, False)
78
105
  return _failure(base, "capability-call-failed")
79
106
  try:
80
107
  output = json.loads(result.stdout.strip().splitlines()[-1])[
@@ -87,6 +114,8 @@ def _probe_hook(component, plugin, base):
87
114
  valid = False
88
115
  if not valid:
89
116
  base["_category"] = "malformed-output"
117
+ base["_capability_probe"]["category"] = "malformed-output"
118
+ record_probe(base, capability, True, True, valid)
90
119
  reason = None if valid else "capability-not-exposed"
91
120
  return _outcome(base, callable=valid, reason_code=reason)
92
121
 
@@ -108,71 +137,6 @@ def _registered_hook(plugin, event, marker):
108
137
  return None
109
138
 
110
139
 
111
- def _probe_devtools(_component, plugin, base):
112
- try:
113
- config = json.loads((plugin / ".mcp.json").read_text(encoding="utf-8"))
114
- except (OSError, ValueError, json.JSONDecodeError):
115
- return _failure(base, "capability-not-exposed")
116
- probes = []
117
- for server in MCP_SPECS:
118
- server_config = config.get(server) if isinstance(config, dict) else None
119
- result = probe_server(server, plugin, server_config)
120
- if not result.get("started") or not result.get("callable"):
121
- return result
122
- probes.append(result)
123
- return _outcome(
124
- base,
125
- runtime_name=",".join(str(item["runtime_name"]) for item in probes),
126
- runtime_version=probes[0]["runtime_version"],
127
- runtime_names=[item["runtime_name"] for item in probes],
128
- runtime_versions=[item["runtime_version"] for item in probes],
129
- )
130
-
131
-
132
- def probe_server(server, plugin, config):
133
- base = _base()
134
- if not isinstance(config, dict) or not isinstance(config.get("command"), str):
135
- return _failure(base, "capability-not-exposed")
136
- target, extra = MCP_SPECS[server]
137
- arguments = {"root": str(plugin), **extra}
138
- requests = (
139
- _request("initialize", 1, _INITIALIZE_PARAMS),
140
- _request("notifications/initialized"),
141
- _request("tools/list", 2),
142
- _request("tools/call", 3, {"name": target, "arguments": arguments}),
143
- )
144
- returncode, responses, timed = _rpc(
145
- _argv(config["command"], plugin, config.get("args", ())), plugin, requests
146
- )
147
- if 1 not in responses:
148
- failed = bool(returncode or timed)
149
- reason = "component-start-failed" if failed else "capability-not-exposed"
150
- return _failure(base, reason, started=not failed)
151
- initialized = responses[1].get("result", {})
152
- listed = responses.get(2, {}).get("result", {})
153
- info = initialized.get("serverInfo", {}) if isinstance(initialized, dict) else {}
154
- tools = listed.get("tools", []) if isinstance(listed, dict) else []
155
- if (
156
- not isinstance(info, dict)
157
- or not isinstance(tools, list)
158
- or not any(
159
- isinstance(tool, dict) and tool.get("name") == target for tool in tools
160
- )
161
- ):
162
- return _failure(base, "capability-not-exposed")
163
- call = responses.get(3, {})
164
- result = call.get("result") if isinstance(call, dict) else None
165
- if (
166
- not isinstance(result, dict)
167
- or any((call.get("error"), call.get("isError"), result.get("isError")))
168
- or not isinstance(result.get("content"), list)
169
- ):
170
- return _failure(base, "capability-call-failed")
171
- return _outcome(
172
- base, runtime_name=info.get("name"), runtime_version=info.get("version")
173
- )
174
-
175
-
176
140
  def _base(record=None):
177
141
  return dict(
178
142
  started=False,
@@ -190,33 +154,6 @@ def _outcome(base, **fields):
190
154
  return {**base, "started": True, "callable": True, **fields}
191
155
 
192
156
 
193
- def _run(argv, cwd, input_text, env=None):
194
- try:
195
- result = subprocess.run(
196
- argv, input=input_text, cwd=cwd, env=env, **_RUN_OPTIONS
197
- )
198
- except subprocess.TimeoutExpired as error:
199
- return _RunResult(-1, error.stdout or "", "timeout")
200
- except OSError:
201
- return _RunResult(-1, "", "missing-launcher")
202
- category = "success" if result.returncode == 0 else "nonzero-exit"
203
- category = "missing-launcher" if result.returncode == 127 else category
204
- return _RunResult(result.returncode, result.stdout, category)
205
-
206
-
207
- def _rpc(argv, cwd, requests):
208
- run = _run(argv, cwd, "\n".join(json.dumps(request) for request in requests) + "\n")
209
- values = {}
210
- for line in (run.stdout or "").splitlines():
211
- try:
212
- value = json.loads(line)
213
- except (ValueError, json.JSONDecodeError):
214
- continue
215
- if isinstance(value, dict) and isinstance(value.get("id"), int):
216
- values[value["id"]] = value
217
- return run.returncode, values, run.category == "timeout"
218
-
219
-
220
157
  def _argv(command, plugin, args=()):
221
158
  values = shlex.split(command.replace("${PLUGIN_ROOT}", str(plugin))) + [
222
159
  str(value) for value in (args if isinstance(args, list) else ())
@@ -232,6 +169,9 @@ def _argv(command, plugin, args=()):
232
169
  return values
233
170
 
234
171
 
172
+ from .component_mcp_probe import _probe_devtools, probe_server # noqa: E402
173
+
174
+
235
175
  def probe_reason(probe, default):
236
176
  reason = probe.get("reason_code")
237
177
  return reason if isinstance(reason, str) and reason in FAILURES else default